1 Commits

Author SHA1 Message Date
yuanyuanxiang
a8684753a9 Feat: Android client Phase 0-3 full implementation 2026-06-21 22:52:43 +02:00
80 changed files with 267 additions and 2581 deletions

View File

@@ -12,7 +12,7 @@
<a href="https://git.simpleremoter.com/yuanyuanxiang/SimpleRemoter/releases"> <a href="https://git.simpleremoter.com/yuanyuanxiang/SimpleRemoter/releases">
<img src="https://img.shields.io/gitea/v/release/yuanyuanxiang/SimpleRemoter?gitea_url=https%3A%2F%2Fgit.simpleremoter.com&style=flat-square&logo=gitea" alt="Gitea Release"> <img src="https://img.shields.io/gitea/v/release/yuanyuanxiang/SimpleRemoter?gitea_url=https%3A%2F%2Fgit.simpleremoter.com&style=flat-square&logo=gitea" alt="Gitea Release">
</a> </a>
<img src="https://img.shields.io/badge/client-Windows%20%7C%20Linux%20%7C%20macOS%20%7C%20Android-blue?style=flat-square" alt="Client Platforms"> <img src="https://img.shields.io/badge/client-Windows%20%7C%20Linux%20%7C%20macOS-blue?style=flat-square" alt="Client Platforms">
<img src="https://img.shields.io/badge/server-Windows%20%7C%20Linux%20%7C%20macOS-success?style=flat-square" alt="Server Platforms"> <img src="https://img.shields.io/badge/server-Windows%20%7C%20Linux%20%7C%20macOS-success?style=flat-square" alt="Server Platforms">
<img src="https://img.shields.io/badge/language-C%2B%2B17%20%2F%20Go-orange?style=flat-square&logo=cplusplus" alt="Language"> <img src="https://img.shields.io/badge/language-C%2B%2B17%20%2F%20Go-orange?style=flat-square&logo=cplusplus" alt="Language">
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License"> <img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
@@ -94,7 +94,6 @@
| **Windows** | ✅ 完整功能 | ✅ MFC `YAMA.exe`(推荐)/ Go | | **Windows** | ✅ 完整功能 | ✅ MFC `YAMA.exe`(推荐)/ Go |
| **Linux** (X11) | ✅ 屏幕 + 终端 + 文件 + 剪贴板 | ✅ Go | | **Linux** (X11) | ✅ 屏幕 + 终端 + 文件 + 剪贴板 | ✅ Go |
| **macOS** (Intel + Apple Silicon) | ✅ 屏幕 + 终端 + 文件 + 剪贴板 | ✅ Go | | **macOS** (Intel + Apple Silicon) | ✅ 屏幕 + 终端 + 文件 + 剪贴板 | ✅ Go |
| **Android** (v1.3.7+) | ✅ 屏幕 + 触控/按键控制 | ❌ 不适用 |
--- ---
@@ -207,21 +206,6 @@ Please read and obey the instructions in [SECURITY_AI.md](./docs/SECURITY_AI.md)
**编译**`cd linux && cmake . && make` **编译**`cd linux && cmake . && make`
### Android 客户端v1.3.7+
**系统要求**Android 5.0 (API 21) 及以上
| 功能 | 状态 | 实现 |
|---|---|---|
| 远程桌面 | ✅ | MediaProjection + MediaCodec H.264 硬件编码 |
| 触控/按键控制 | ✅ | AccessibilityService 注入,支持 D-pad 导航 |
| 心跳/RTT | ✅ | RFC 6298 RTT 估算 |
| 设备分组 | ✅ | szGroupName 编译期 patch / 服务端动态修改 |
**生成客户端**:在主控 BuildDlg「生成」→ 选 `ghost - Google Android`,填入服务端 IP / 端口,生成 APK使用 `android/sign_apk.bat` 重签后安装。
**编译**:在 WSL 或 Linux 中 `cd android && ./build_apk.sh`
### macOS 客户端v1.3.2+ ### macOS 客户端v1.3.2+
**系统要求** **系统要求**
@@ -274,13 +258,13 @@ Please read and obey the instructions in [SECURITY_AI.md](./docs/SECURITY_AI.md)
│ TCP (自定义二进制协议) │ TCP (设备) + WS (浏览器) │ TCP (自定义二进制协议) │ TCP (设备) + WS (浏览器)
└────────┬─────────────────┘ └────────┬─────────────────┘
┌──────────────┼──────────────┬────────────── ┌──────────────┼──────────────┐
▼ ▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Windows │ │ Linux │ │ macOS │ │ Android │ │ Windows │ │ Linux │ │ macOS │
│ 客户端 │ │ 客户端 │ │ 客户端 │ │ 客户端 │ │ 客户端 │ │ 客户端 │ │ 客户端 │
│ (DXGI) │ │ (X11) │ │ (CG) │ │ (MP/MC) │ │ (DXGI) │ │ (X11) │ │ (CG) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
``` ```
### 多层授权(简化视图) ### 多层授权(简化视图)
@@ -356,7 +340,6 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
- **C++ 主控 & Windows 客户端**VS 2019/2022/2026 打开 `SimpleRemoter.sln` → Release | x64 - **C++ 主控 & Windows 客户端**VS 2019/2022/2026 打开 `SimpleRemoter.sln` → Release | x64
- **Linux 客户端**`cd linux && cmake . && make` - **Linux 客户端**`cd linux && cmake . && make`
- **macOS 客户端**`cd macos && ./build.sh` - **macOS 客户端**`cd macos && ./build.sh`
- **Android 客户端**`cd android && ./build_apk.sh`WSL/Linux
- **Go 主控**`cd server/go && go build ./cmd` - **Go 主控**`cd server/go && go build ./cmd`
--- ---
@@ -378,31 +361,6 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
## 更新日志 ## 更新日志
### v1.3.7 (2026.6.26)
**Android 客户端 & 窗口捕获增强 & 安全加固**
**新功能:**
- **Android 客户端**:首个 Android 受控端,支持 Android 5.0+arm64-v8a / armeabi-v7aMediaProjection + MediaCodec H.264 推流AccessibilityService 触控 / 按键注入D-pad 焦点导航完整控制 TV / 机顶盒
- **设备分组**:编译期 patch 预设 / 服务端动态修改(`CMD_SET_GROUP`);分组名持久化至 `filesDir/yama_group`App 重启后保留
- **APK 生成与重签**BuildDlg 新增 `ghost - Google Android`,与 Linux / macOS ghost 流程一致;配套 `sign_apk.bat` 自动检测 SDK 完成重签
- **服务端删除客户端**BYE 指令后 Android Service 通过 JNI 正常停止
- **前台窗口精准捕获**PrintWindow + 服务端 HWND by clientID 路由,遮挡情况下仍可捕获完整窗口
- **在线主机右键查看活动窗口**:实时查询目标机器前台窗口标题
- **TOKEN_AUTH 响应 ECDSA 签名**V2 私钥签名服务端响应,防伪服务端(`TOKEN_SERVER_VERIFY = 251`
- **反破解版本绑定**:版本与核心库绑定,阻断替换 DLL 的破解路径
- **子授权连接数限制**`LicenseLimit` 字段 + `CLicenseDlg` 右键菜单实时设置
**改进:**
- H264 模式跳过首帧 ~8MB 原始巨帧,服务端在首 IDR 帧后解锁画面
- 自适应尺寸渲染改进,减少尺寸切换抖动
**Bug 修复:**
- `ARGBToNV12` 奇数尺寸窗口堆溢出(维度钳制到偶数对齐)
- 连接初始化时多余屏幕重启(客户端 `ScreenManager.cpp` + 服务端 `ScreenSpyDlg.cpp` 双端修复)
- 启动时内存 DLL 未正确还原
- 授权客户端键盘日志目录冲突(各实例使用独立目录)
### v1.3.6 (2026.6.14) ### v1.3.6 (2026.6.14)
**ROI 区域捕获 & Web 音频流 & 主界面可用性全面提升** **ROI 区域捕获 & Web 音频流 & 主界面可用性全面提升**

View File

@@ -12,7 +12,7 @@
<a href="https://git.simpleremoter.com/yuanyuanxiang/SimpleRemoter/releases"> <a href="https://git.simpleremoter.com/yuanyuanxiang/SimpleRemoter/releases">
<img src="https://img.shields.io/gitea/v/release/yuanyuanxiang/SimpleRemoter?gitea_url=https%3A%2F%2Fgit.simpleremoter.com&style=flat-square&logo=gitea" alt="Gitea Release"> <img src="https://img.shields.io/gitea/v/release/yuanyuanxiang/SimpleRemoter?gitea_url=https%3A%2F%2Fgit.simpleremoter.com&style=flat-square&logo=gitea" alt="Gitea Release">
</a> </a>
<img src="https://img.shields.io/badge/client-Windows%20%7C%20Linux%20%7C%20macOS%20%7C%20Android-blue?style=flat-square" alt="Client Platforms"> <img src="https://img.shields.io/badge/client-Windows%20%7C%20Linux%20%7C%20macOS-blue?style=flat-square" alt="Client Platforms">
<img src="https://img.shields.io/badge/server-Windows%20%7C%20Linux%20%7C%20macOS-success?style=flat-square" alt="Server Platforms"> <img src="https://img.shields.io/badge/server-Windows%20%7C%20Linux%20%7C%20macOS-success?style=flat-square" alt="Server Platforms">
<img src="https://img.shields.io/badge/language-C%2B%2B17%20%2F%20Go-orange?style=flat-square&logo=cplusplus" alt="Language"> <img src="https://img.shields.io/badge/language-C%2B%2B17%20%2F%20Go-orange?style=flat-square&logo=cplusplus" alt="Language">
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License"> <img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
@@ -94,7 +94,6 @@ This release (v1.3.4) adds the last missing piece — the **Go master**: a **del
| **Windows** | ✅ All features | ✅ MFC `YAMA.exe` (recommended) / Go | | **Windows** | ✅ All features | ✅ MFC `YAMA.exe` (recommended) / Go |
| **Linux** (X11) | ✅ Screen + terminal + files + clipboard | ✅ Go | | **Linux** (X11) | ✅ Screen + terminal + files + clipboard | ✅ Go |
| **macOS** (Intel + Apple Silicon) | ✅ Screen + terminal + files + clipboard | ✅ Go | | **macOS** (Intel + Apple Silicon) | ✅ Screen + terminal + files + clipboard | ✅ Go |
| **Android** (v1.3.7+) | ✅ Screen + touch/key injection | ❌ N/A |
--- ---
@@ -207,21 +206,6 @@ Unless an offline license has been obtained, the master program exchanges necess
**Build**: `cd linux && cmake . && make` **Build**: `cd linux && cmake . && make`
### Android Client (v1.3.7+)
**Requirements**: Android 5.0 (API 21) or later
| Feature | Status | Implementation |
|---|---|---|
| Remote desktop | ✅ | MediaProjection + MediaCodec H.264 hardware encoding |
| Touch / key injection | ✅ | AccessibilityService, supports D-pad TV navigation |
| Heartbeat / RTT | ✅ | RFC 6298 RTT estimation |
| Device grouping | ✅ | Build-time patch or server-side `CMD_SET_GROUP`; persisted across restarts |
**Generate client**: In the master BuildDlg *Build* dialog, select `ghost - Google Android`, enter the server IP / port, and the APK is generated. Re-sign with `android/sign_apk.bat` before installing.
**Build**: in WSL or Linux — `cd android && ./build_apk.sh`
### macOS Client (v1.3.2+) ### macOS Client (v1.3.2+)
**Requirements**: **Requirements**:
@@ -274,13 +258,13 @@ Unless an offline license has been obtained, the master program exchanges necess
│ TCP (custom binary proto)│ TCP (devices) + WS (browsers) │ TCP (custom binary proto)│ TCP (devices) + WS (browsers)
└────────┬─────────────────┘ └────────┬─────────────────┘
┌──────────────┼──────────────┬────────────── ┌──────────────┼──────────────┐
▼ ▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Windows │ │ Linux │ │ macOS │ │ Android │ │ Windows │ │ Linux │ │ macOS │
│ client │ │ client │ │ client │ │ client │ │ client │ │ client │ │ client │
│ (DXGI) │ │ (X11) │ │ (CG) │ │ (MP/MC) │ │ (DXGI) │ │ (X11) │ │ (CG) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
``` ```
### Multi-Layer Authorization (simplified view) ### Multi-Layer Authorization (simplified view)
@@ -356,7 +340,6 @@ Valid : 2026-02-01 to 2028-02-01
- **C++ master & Windows client**: open `SimpleRemoter.sln` in VS 2019 / 2022 / 2026 → Release | x64 - **C++ master & Windows client**: open `SimpleRemoter.sln` in VS 2019 / 2022 / 2026 → Release | x64
- **Linux client**: `cd linux && cmake . && make` - **Linux client**: `cd linux && cmake . && make`
- **macOS client**: `cd macos && ./build.sh` - **macOS client**: `cd macos && ./build.sh`
- **Android client**: `cd android && ./build_apk.sh` (WSL / Linux)
- **Go master**: `cd server/go && go build ./cmd` - **Go master**: `cd server/go && go build ./cmd`
--- ---
@@ -378,31 +361,6 @@ Valid : 2026-02-01 to 2028-02-01
## Changelog ## Changelog
### v1.3.7 (2026.6.26)
**Android Client & Window Capture & Security Hardening**
**New features:**
- **Android client**: first Android controlled-side client, Android 5.0+ (arm64-v8a / armeabi-v7a); MediaProjection + MediaCodec H.264 streaming; AccessibilityService touch / key injection; D-pad focus navigation for full TV / set-top-box control
- **Device grouping**: group name embedded at build time via APK binary patch or changed live by the server (`CMD_SET_GROUP`); persisted to `filesDir/yama_group`, survives app restarts
- **APK build & re-sign**: BuildDlg adds `ghost - Google Android`, same workflow as Linux / macOS ghost; `sign_apk.bat` auto-detects SDK and re-signs
- **Clean exit on server delete**: BYE command triggers JNI `onNativeExit()``stopSelf()`
- **Foreground window capture**: PrintWindow + server-side HWND routing by clientID, captures the target window even when occluded
- **View active window from host list**: right-click menu queries and shows the foreground window title of the target machine in real time
- **TOKEN_AUTH ECDSA signature**: server signs auth responses with its V2 P-256 key to prevent fake-server attacks (`TOKEN_SERVER_VERIFY = 251`)
- **Anti-cracking version binding**: binary binds to core lib version; mismatched DLL replacement is rejected at load time
- **Sub-license connection cap**: `LicenseLimit` field in `licenses.ini` + real-time set / clear from `CLicenseDlg` right-click menu
**Improvements:**
- H264 mode skips the initial ~8 MB raw first-frame; server unlocks display on the first IDR instead
- Adaptive-size rendering improvements, fewer dimension-change artifacts
**Bug fixes:**
- `ARGBToNV12` heap overflow on odd-sized windows (clamp dims to even-aligned width/height)
- Extra screen restarts on connection init (dual fix: client `ScreenManager.cpp` + server `ScreenSpyDlg.cpp`)
- Memory DLL not restored on client startup
- Authorization client keyboard log directory conflict (each instance gets its own directory)
### v1.3.6 (2026.6.14) ### v1.3.6 (2026.6.14)
**ROI region capture & Web audio streaming & master-UI usability overhaul** **ROI region capture & Web audio streaming & master-UI usability overhaul**

View File

@@ -12,7 +12,7 @@
<a href="https://git.simpleremoter.com/yuanyuanxiang/SimpleRemoter/releases"> <a href="https://git.simpleremoter.com/yuanyuanxiang/SimpleRemoter/releases">
<img src="https://img.shields.io/gitea/v/release/yuanyuanxiang/SimpleRemoter?gitea_url=https%3A%2F%2Fgit.simpleremoter.com&style=flat-square&logo=gitea" alt="Gitea Release"> <img src="https://img.shields.io/gitea/v/release/yuanyuanxiang/SimpleRemoter?gitea_url=https%3A%2F%2Fgit.simpleremoter.com&style=flat-square&logo=gitea" alt="Gitea Release">
</a> </a>
<img src="https://img.shields.io/badge/client-Windows%20%7C%20Linux%20%7C%20macOS%20%7C%20Android-blue?style=flat-square" alt="Client Platforms"> <img src="https://img.shields.io/badge/client-Windows%20%7C%20Linux%20%7C%20macOS-blue?style=flat-square" alt="Client Platforms">
<img src="https://img.shields.io/badge/server-Windows%20%7C%20Linux%20%7C%20macOS-success?style=flat-square" alt="Server Platforms"> <img src="https://img.shields.io/badge/server-Windows%20%7C%20Linux%20%7C%20macOS-success?style=flat-square" alt="Server Platforms">
<img src="https://img.shields.io/badge/language-C%2B%2B17%20%2F%20Go-orange?style=flat-square&logo=cplusplus" alt="Language"> <img src="https://img.shields.io/badge/language-C%2B%2B17%20%2F%20Go-orange?style=flat-square&logo=cplusplus" alt="Language">
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License"> <img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
@@ -94,7 +94,6 @@
| **Windows** | ✅ 完整功能 | ✅ MFC `YAMA.exe`(推薦)/ Go | | **Windows** | ✅ 完整功能 | ✅ MFC `YAMA.exe`(推薦)/ Go |
| **Linux** (X11) | ✅ 螢幕 + 終端 + 檔案 + 剪貼簿 | ✅ Go | | **Linux** (X11) | ✅ 螢幕 + 終端 + 檔案 + 剪貼簿 | ✅ Go |
| **macOS** (Intel + Apple Silicon) | ✅ 螢幕 + 終端 + 檔案 + 剪貼簿 | ✅ Go | | **macOS** (Intel + Apple Silicon) | ✅ 螢幕 + 終端 + 檔案 + 剪貼簿 | ✅ Go |
| **Android** (v1.3.7+) | ✅ 螢幕 + 觸控/按鍵注入 | ❌ 不適用 |
--- ---
@@ -207,21 +206,6 @@ Please read and obey the instructions in [SECURITY_AI.md](./docs/SECURITY_AI.md)
**編譯**`cd linux && cmake . && make` **編譯**`cd linux && cmake . && make`
### Android 用戶端v1.3.7+
**系統需求**Android 5.0 (API 21) 及以上
| 功能 | 狀態 | 實作 |
|---|---|---|
| 遠端桌面 | ✅ | MediaProjection + MediaCodec H.264 硬體編碼 |
| 觸控 / 按鍵注入 | ✅ | AccessibilityService支援 D-pad TV 導航 |
| 心跳 / RTT | ✅ | RFC 6298 RTT 估算 |
| 裝置分組 | ✅ | 編譯期 patch 或服務端 `CMD_SET_GROUP` 動態修改;重啟後仍保留 |
**產生用戶端**:在主控 BuildDlg「產生」選 `ghost - Google Android`,填入伺服端 IP / 埠,生成 APK使用 `android/sign_apk.bat` 重簽後安裝。
**編譯**:在 WSL 或 Linux 中 `cd android && ./build_apk.sh`
### macOS 用戶端v1.3.2+ ### macOS 用戶端v1.3.2+
**系統需求** **系統需求**
@@ -274,13 +258,13 @@ Please read and obey the instructions in [SECURITY_AI.md](./docs/SECURITY_AI.md)
│ TCP (自訂二進位協定) │ TCP (裝置) + WS (瀏覽器) │ TCP (自訂二進位協定) │ TCP (裝置) + WS (瀏覽器)
└────────┬─────────────────┘ └────────┬─────────────────┘
┌──────────────┼──────────────┬────────────── ┌──────────────┼──────────────┐
▼ ▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Windows │ │ Linux │ │ macOS │ │ Android │ │ Windows │ │ Linux │ │ macOS │
│ 用戶端 │ │ 用戶端 │ │ 用戶端 │ │ 用戶端 │ │ 用戶端 │ │ 用戶端 │ │ 用戶端 │
│ (DXGI) │ │ (X11) │ │ (CG) │ │ (MP/MC) │ │ (DXGI) │ │ (X11) │ │ (CG) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
``` ```
### 多層授權(簡化視圖) ### 多層授權(簡化視圖)
@@ -356,7 +340,6 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
- **C++ 主控 & Windows 用戶端**VS 2019/2022/2026 開啟 `SimpleRemoter.sln` → Release | x64 - **C++ 主控 & Windows 用戶端**VS 2019/2022/2026 開啟 `SimpleRemoter.sln` → Release | x64
- **Linux 用戶端**`cd linux && cmake . && make` - **Linux 用戶端**`cd linux && cmake . && make`
- **macOS 用戶端**`cd macos && ./build.sh` - **macOS 用戶端**`cd macos && ./build.sh`
- **Android 用戶端**`cd android && ./build_apk.sh`WSL/Linux
- **Go 主控**`cd server/go && go build ./cmd` - **Go 主控**`cd server/go && go build ./cmd`
--- ---
@@ -378,31 +361,6 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
## 更新日誌 ## 更新日誌
### v1.3.7 (2026.6.26)
**Android 用戶端 & 視窗擷取增強 & 安全加固**
**新功能:**
- **Android 用戶端**:首個 Android 受控端,支援 Android 5.0+arm64-v8a / armeabi-v7aMediaProjection + MediaCodec H.264 推流AccessibilityService 觸控 / 按鍵注入D-pad 焦點導航完整控制 TV / 機上盒
- **裝置分組**:編譯期 patch 預設 / 伺服端動態修改(`CMD_SET_GROUP`);分組名持久化至 `filesDir/yama_group`App 重啟後保留
- **APK 產生與重簽**BuildDlg 新增 `ghost - Google Android`,與 Linux / macOS ghost 流程一致;配套 `sign_apk.bat` 自動偵測 SDK 完成重簽
- **伺服端刪除用戶端**BYE 指令後 Android Service 透過 JNI 正常停止
- **前台視窗精準擷取**PrintWindow + 伺服端 HWND by clientID 路由,遮蓋情況下仍可擷取完整視窗
- **線上主機右鍵查看活動視窗**:即時查詢目標機器前台視窗標題
- **TOKEN_AUTH 回應 ECDSA 簽章**V2 私鑰簽名伺服端回應,防偽造伺服端(`TOKEN_SERVER_VERIFY = 251`
- **反破解版本綁定**:版本與核心函式庫綁定,阻斷替換 DLL 的破解路徑
- **子授權連線數限制**`LicenseLimit` 欄位 + `CLicenseDlg` 右鍵選單即時設定
**改進:**
- H264 模式跳過首幀 ~8MB 原始巨幀,伺服端在首 IDR 幀後解鎖畫面
- 自適應尺寸渲染改進,減少尺寸切換閃爍
**Bug 修復:**
- `ARGBToNV12` 奇數尺寸視窗堆積溢位(維度鉗制到偶數對齊)
- 連線初始化時多餘螢幕重啟(用戶端 `ScreenManager.cpp` + 伺服端 `ScreenSpyDlg.cpp` 雙端修復)
- 啟動時記憶體 DLL 未正確還原
- 授權用戶端鍵盤日誌目錄衝突(各實例使用獨立目錄)
### v1.3.6 (2026.6.14) ### v1.3.6 (2026.6.14)
**ROI 區域擷取 & Web 音訊串流 & 主控介面可用性全面提升** **ROI 區域擷取 & Web 音訊串流 & 主控介面可用性全面提升**

View File

@@ -3,9 +3,6 @@ plugins {
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
} }
// 模拟器调试时传 -PincludeEmulatorAbis=truebuild_apk.sh 发布时强制传 false
def INCLUDE_EMULATOR_ABIS = project.findProperty('includeEmulatorAbis')?.toBoolean() ?: false
android { android {
namespace 'com.yama.client' namespace 'com.yama.client'
compileSdk 35 compileSdk 35
@@ -19,11 +16,7 @@ android {
versionName "1.0" versionName "1.0"
ndk { ndk {
if (INCLUDE_EMULATOR_ABIS) { abiFilters 'arm64-v8a', 'armeabi-v7a'
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
} else {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
} }
externalNativeBuild { externalNativeBuild {
@@ -60,13 +53,6 @@ android {
} }
} }
packagingOptions {
jniLibs {
// .so 文件不压缩ZIP_STORED使服务端 patch 工具可直接在 APK 中搜索 FLAG_GHOST
useLegacyPackaging false
}
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17

View File

@@ -8,7 +8,6 @@
<application <application
android:allowBackup="false" android:allowBackup="false"
android:extractNativeLibs="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:label="YAMA" android:label="YAMA"

View File

@@ -24,7 +24,6 @@ set(CORE_SOURCES
${YAMA_ROOT}/client/Buffer.cpp ${YAMA_ROOT}/client/Buffer.cpp
${YAMA_ROOT}/client/IOCPClient.cpp ${YAMA_ROOT}/client/IOCPClient.cpp
${YAMA_ROOT}/client/sign_shim_unix.cpp ${YAMA_ROOT}/client/sign_shim_unix.cpp
${YAMA_ROOT}/common/logger.cpp
${YAMA_ROOT}/common/ikcp.c ${YAMA_ROOT}/common/ikcp.c
) )
@@ -35,11 +34,12 @@ set(ANDROID_SOURCES
add_library(yama SHARED ${CORE_SOURCES} ${ANDROID_SOURCES}) add_library(yama SHARED ${CORE_SOURCES} ${ANDROID_SOURCES})
# ---- 预编译静态库(由 android/build_android_libs.ps1 产出)---- # ---- 预编译静态库(由 SimplePlugins/sign_lib/build_android.sh 产出)----
# 支持 arm64-v8a / armeabi-v7a真机和 x86 / x86_64Android 模拟器)
target_link_libraries(yama PRIVATE target_link_libraries(yama PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/lib/${ANDROID_ABI}/libsign.a" "${CMAKE_CURRENT_SOURCE_DIR}/lib/${ANDROID_ABI}/libsign.a"
) )
# ---- zstd由 android/build_zstd_android.sh 产出)----
target_link_libraries(yama PRIVATE target_link_libraries(yama PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/lib/${ANDROID_ABI}/libzstd.a" "${CMAKE_CURRENT_SOURCE_DIR}/lib/${ANDROID_ABI}/libzstd.a"
) )

View File

@@ -1,7 +1,6 @@
#include <jni.h> #include <jni.h>
#include <android/log.h> #include <android/log.h>
#include <thread> #include <thread>
#include <chrono>
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <set> #include <set>
@@ -10,9 +9,7 @@
#include <cinttypes> #include <cinttypes>
#include <cstdio> #include <cstdio>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/select.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netdb.h> #include <netdb.h>
@@ -25,18 +22,13 @@
#include "ScreenHandler.h" #include "ScreenHandler.h"
#define XXH_INLINE_ALL #define XXH_INLINE_ALL
#include "common/xxhash.h" #include "common/xxhash.h"
#include "common/logger.h"
#define LOG_TAG "YAMA" #define LOG_TAG "YAMA"
#undef Mprintf
#define Mprintf(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// 1 = 状态提示显示到电视屏幕0 = 只写 logcat不显示到屏幕
#define SCREEN_STATUS_ENABLED 0
extern "C" int signMessage_c(const char* pk, int pkLen, const unsigned char* msg,
int msgLen, char* buf, int bufSize);
// ---- 设备信息 helper登录时填充仅调用一次---- // ---- 设备信息 helper登录时填充仅调用一次----
static int GetCpuCores() { static int GetCpuCores() {
@@ -117,31 +109,23 @@ static std::string JsonGetStr(const std::string& json, const char* key) {
// 查询 ip-api.com一次请求同时获取公网 IP 和地理位置(与 Windows 客户端同源) // 查询 ip-api.com一次请求同时获取公网 IP 和地理位置(与 Windows 客户端同源)
// 在连接线程中同步调用,超时 3 秒 // 在连接线程中同步调用,超时 3 秒
struct GeoResult { static bool FetchGeoInfo(std::string& pubIp, std::string& location) {
std::string pubIp, location; pubIp.clear(); location.clear();
std::atomic<bool> ready{false};
};
static void FetchGeoInfoImpl(std::shared_ptr<GeoResult> out) {
struct addrinfo hints = {}, *res = nullptr; struct addrinfo hints = {}, *res = nullptr;
hints.ai_family = AF_INET; hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo("ip-api.com", "80", &hints, &res) != 0 || !res) { if (getaddrinfo("ip-api.com", "80", &hints, &res) != 0 || !res) return false;
out->ready.store(true, std::memory_order_release); return;
}
int fd = ::socket(AF_INET, SOCK_STREAM, 0); int fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { freeaddrinfo(res); out->ready.store(true, std::memory_order_release); return; } if (fd < 0) { freeaddrinfo(res); return false; }
struct timeval tv = {3, 0}; struct timeval tv = {3, 0};
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
connect(fd, res->ai_addr, res->ai_addrlen);
fd_set wfds; FD_ZERO(&wfds); FD_SET(fd, &wfds);
bool connected = (select(fd + 1, nullptr, &wfds, nullptr, &tv) == 1);
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
if (connected) { bool ok = false;
if (connect(fd, res->ai_addr, res->ai_addrlen) == 0) {
const char* req = "GET /json/?fields=status,query,country,city HTTP/1.0\r\n" const char* req = "GET /json/?fields=status,query,country,city HTTP/1.0\r\n"
"Host: ip-api.com\r\n" "Host: ip-api.com\r\n"
"Connection: close\r\n\r\n"; "Connection: close\r\n\r\n";
@@ -156,37 +140,21 @@ static void FetchGeoInfoImpl(std::shared_ptr<GeoResult> out) {
if (sep != std::string::npos) { if (sep != std::string::npos) {
const std::string body = resp.substr(sep + 4); const std::string body = resp.substr(sep + 4);
if (JsonGetStr(body, "status") == "success") { if (JsonGetStr(body, "status") == "success") {
out->pubIp = JsonGetStr(body, "query"); pubIp = JsonGetStr(body, "query");
std::string city = JsonGetStr(body, "city"); std::string city = JsonGetStr(body, "city");
std::string country = JsonGetStr(body, "country"); std::string country = JsonGetStr(body, "country");
if (!city.empty() && !country.empty()) out->location = city + ", " + country; if (!city.empty() && !country.empty()) location = city + ", " + country;
else if (!country.empty()) out->location = country; else if (!country.empty()) location = country;
else if (!city.empty()) out->location = city; else if (!city.empty()) location = city;
ok = true;
} }
} }
} }
close(fd); close(fd);
freeaddrinfo(res); freeaddrinfo(res);
out->ready.store(true, std::memory_order_release); return ok;
} }
// getaddrinfo 在某些设备上会永久阻塞,用独立线程 + 5 秒轮询超时保护 ConnectionThread
static bool FetchGeoInfo(std::string& pubIp, std::string& location) {
pubIp.clear(); location.clear();
auto result = std::make_shared<GeoResult>();
std::thread(FetchGeoInfoImpl, result).detach();
for (int i = 0; i < 50 && !result->ready.load(std::memory_order_acquire); i++)
usleep(100000); // 100ms × 50 = 5 秒
if (!result->ready.load(std::memory_order_acquire)) return false;
pubIp = result->pubIp;
location = result->location;
return !pubIp.empty();
}
// 服务端通过 FLAG_GHOST"Hello, World!"定位此变量patch szServerIP/szPort 后重签 APK
// 偏移szServerIP at +32, szPort at +132见 commands.h CONNECT_ADDRESS 定义)
CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_ANDROID };
// ---- 全局状态 ---- // ---- 全局状态 ----
// CPP-06: g_bExit 是全局变量,跨调用边界不会被编译器缓存进寄存器; // CPP-06: g_bExit 是全局变量,跨调用边界不会被编译器缓存进寄存器;
// IOCPClient 构造接受 const State& 持有引用,不能改为 atomic/volatile // IOCPClient 构造接受 const State& 持有引用,不能改为 atomic/volatile
@@ -205,8 +173,6 @@ static jmethodID g_getActiveWindowMid = nullptr;
static jclass g_captureClass = nullptr; static jclass g_captureClass = nullptr;
static jmethodID g_requestIdrMid = nullptr; static jmethodID g_requestIdrMid = nullptr;
static jmethodID g_forceFirstFrameMid = nullptr; static jmethodID g_forceFirstFrameMid = nullptr;
static jmethodID g_statusMid = nullptr; // CaptureService.onNativeStatus(String)
static jmethodID g_nativeExitMid = nullptr; // CaptureService.onNativeExit() — 服务端主动断开时停止服务
// 心跳日志限流:每 60 秒最多打一次 // 心跳日志限流:每 60 秒最多打一次
static std::atomic<uint64_t> g_lastHbLogMs{0}; static std::atomic<uint64_t> g_lastHbLogMs{0};
@@ -275,35 +241,6 @@ static std::string g_androidVersion;
static std::string g_screenRes; static std::string g_screenRes;
static std::string g_username; static std::string g_username;
static std::string g_apkPath; static std::string g_apkPath;
static std::string g_filesDir; // context.filesDir无需权限用于持久化分组名
static void SaveGroupName() {
if (g_filesDir.empty()) return;
std::string path = g_filesDir + "/yama_group";
FILE* f = fopen(path.c_str(), "w");
if (!f) { LOGI("SaveGroupName: cannot open %s", path.c_str()); return; }
fputs(g_SETTINGS.szGroupName, f);
fclose(f);
LOGI("Group saved: %s", g_SETTINGS.szGroupName);
}
static void LoadGroupName() {
if (g_filesDir.empty()) return;
std::string path = g_filesDir + "/yama_group";
FILE* f = fopen(path.c_str(), "r");
if (!f) return;
char buf[24] = {};
if (fgets(buf, sizeof(buf), f)) {
size_t len = strlen(buf);
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) buf[--len] = '\0';
if (len > 0) {
memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName));
strncpy(g_SETTINGS.szGroupName, buf, sizeof(g_SETTINGS.szGroupName) - 1);
LOGI("Group loaded from file: %s", buf);
}
}
fclose(f);
}
// 屏幕尺寸Java 侧 MediaCodec 配置后通过 nativeSetScreenSize 设置) // 屏幕尺寸Java 侧 MediaCodec 配置后通过 nativeSetScreenSize 设置)
// 初始化为 0ScreenSpyThread 等到非零后再读,避免在 nativeSetScreenSize 之前 // 初始化为 0ScreenSpyThread 等到非零后再读,避免在 nativeSetScreenSize 之前
@@ -340,29 +277,6 @@ static void RequestIdrFromJava() { CallCaptureStaticVoid(g_requestIdrMid);
// 强制 VirtualDisplay 推一帧(静止屏幕绕过 SurfaceFlinger 空闲优化) // 强制 VirtualDisplay 推一帧(静止屏幕绕过 SurfaceFlinger 空闲优化)
static void ForceFirstFrameFromJava() { CallCaptureStaticVoid(g_forceFirstFrameMid); } static void ForceFirstFrameFromJava() { CallCaptureStaticVoid(g_forceFirstFrameMid); }
// 向 CaptureService.onNativeStatus() 发送状态 Toast连接线程诊断用
static void PostStatus(const char* msg) {
LOGI("STATUS: %s", msg);
#if SCREEN_STATUS_ENABLED
if (!g_jvm || !g_captureClass || !g_statusMid) return;
JNIEnv* jenv = nullptr;
bool attached = false;
if (g_jvm->GetEnv((void**)&jenv, JNI_VERSION_1_6) == JNI_EDETACHED) {
g_jvm->AttachCurrentThread(&jenv, nullptr);
attached = true;
}
if (jenv) {
jstring js = jenv->NewStringUTF(msg);
if (js) {
jenv->CallStaticVoidMethod(g_captureClass, g_statusMid, js);
jenv->DeleteLocalRef(js);
}
if (jenv->ExceptionOccurred()) jenv->ExceptionClear();
}
if (attached) g_jvm->DetachCurrentThread();
#endif
}
static void ScreenSpyThread() static void ScreenSpyThread()
{ {
// 等待 Java 侧 startCapture() 调用 nativeSetScreenSize 设置真实分辨率。 // 等待 Java 侧 startCapture() 调用 nativeSetScreenSize 设置真实分辨率。
@@ -385,36 +299,32 @@ static void ScreenSpyThread()
std::unique_ptr<IOCPClient> client; std::unique_ptr<IOCPClient> client;
std::unique_ptr<AndroidScreenHandler> handler; std::unique_ptr<AndroidScreenHandler> handler;
try { try {
LOGI("SST[%d]: creating IOCPClient", attempt);
client = std::make_unique<IOCPClient>(g_bExit, true); client = std::make_unique<IOCPClient>(g_bExit, true);
client->EnableSubConnAuth(true, g_myClientID); client->EnableSubConnAuth(true, g_myClientID);
LOGI("SST[%d]: connecting %s:%d", attempt, g_serverIp.c_str(), g_serverPort);
if (!client->ConnectServer(g_serverIp.c_str(), g_serverPort)) { if (!client->ConnectServer(g_serverIp.c_str(), g_serverPort)) {
LOGI("ScreenSpyThread: connect failed (attempt %d/20), retry 2s", attempt); LOGI("ScreenSpyThread: connect failed (attempt %d/20), retry 2s", attempt);
Sleep(2000); Sleep(2000);
continue; continue;
} }
LOGI("SST[%d]: connected, creating handler w=%d h=%d", attempt, w, h);
handler = std::make_unique<AndroidScreenHandler>(client.get(), w, h); handler = std::make_unique<AndroidScreenHandler>(client.get(), w, h);
LOGI("SST[%d]: handler created, inserting to set", attempt);
{ {
std::lock_guard<std::mutex> lk(g_shMutex); std::lock_guard<std::mutex> lk(g_shMutex);
g_screenHandlers.insert(handler.get()); g_screenHandlers.insert(handler.get());
} }
LOGI("SST[%d]: setManagerCallBack", attempt);
client->setManagerCallBack(handler.get(), client->setManagerCallBack(handler.get(),
IOCPManager::DataProcess, IOCPManager::DataProcess,
IOCPManager::ReconnectProcess); IOCPManager::ReconnectProcess);
LOGI("SST[%d]: SendBitmapInfo", attempt);
handler->SendBitmapInfo(); handler->SendBitmapInfo();
LOGI("SST[%d]: ForceFirstFrame", attempt); // 子连接刚建立,浏览器 initDecoder 完成后需要关键帧才能开始显示。
// ForceFirstFrameFromJava短暂 resize VirtualDisplay 强制 SurfaceFlinger
// 推一帧给编码器——静止屏幕时 SurfaceFlinger 有空闲优化不主动推帧,
// 导致 REQUEST_SYNC_FRAME 排队等待,造成 10-60 秒黑屏。
// RequestIdrFromJava 确保此帧被编码为 IDR。
ForceFirstFrameFromJava(); ForceFirstFrameFromJava();
LOGI("SST[%d]: RequestIdr", attempt);
RequestIdrFromJava(); RequestIdrFromJava();
LOGI("SST[%d]: entering wait loop", attempt);
while (client->IsRunning() && client->IsConnected() && S_CLIENT_NORMAL == g_bExit) while (client->IsRunning() && client->IsConnected() && S_CLIENT_NORMAL == g_bExit)
Sleep(200); Sleep(200);
@@ -444,28 +354,24 @@ static void ScreenSpyThread()
int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength) int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength)
{ {
if (!szBuffer || !ulLength) return TRUE; if (!szBuffer || !ulLength) return TRUE;
int allowed = (int)ClientAuth::IsCommandAllowed(szBuffer[0]);
if (!allowed) { if (!ClientAuth::IsCommandAllowed(szBuffer[0]))
LOGI("DataProcess cmd=%d len=%lu allowed=%d",
(int)(unsigned char)szBuffer[0], (unsigned long)ulLength, allowed);
return TRUE; return TRUE;
}
switch (szBuffer[0]) { switch (szBuffer[0]) {
case COMMAND_BYE: case COMMAND_BYE:
PostStatus("BYE from server"); LOGI("COMMAND_BYE");
g_bExit = S_CLIENT_EXIT; g_bExit = S_CLIENT_EXIT;
break; break;
case CMD_HEARTBEAT_ACK: case CMD_HEARTBEAT_ACK:
if (ulLength >= 1 + (ULONG)sizeof(HeartbeatACK)) { if (ulLength >= 1 + (ULONG)sizeof(HeartbeatACK)) {
HeartbeatACK ack; HeartbeatACK* ack = (HeartbeatACK*)(szBuffer + 1);
memcpy(&ack, szBuffer + 1, sizeof(HeartbeatACK));
uint64_t now = GetUnixMs(); uint64_t now = GetUnixMs();
g_lastHeartbeatAckMs.store(now, std::memory_order_relaxed); g_lastHeartbeatAckMs.store(now, std::memory_order_relaxed);
int64_t rtt = (int64_t)now - (int64_t)ack.Time; int64_t rtt = (int64_t)now - (int64_t)ack->Time;
if (ack.ProcessingMs > 0 && (int64_t)ack.ProcessingMs < rtt) if (ack->ProcessingMs > 0 && (int64_t)ack->ProcessingMs < rtt)
rtt -= ack.ProcessingMs; rtt -= ack->ProcessingMs;
g_rttEstimator.update_from_sample((double)rtt); g_rttEstimator.update_from_sample((double)rtt);
if (now - g_lastHbLogMs.load(std::memory_order_relaxed) >= HB_LOG_INTERVAL_MS) { if (now - g_lastHbLogMs.load(std::memory_order_relaxed) >= HB_LOG_INTERVAL_MS) {
g_lastHbLogMs.store(now, std::memory_order_relaxed); g_lastHbLogMs.store(now, std::memory_order_relaxed);
@@ -480,9 +386,6 @@ int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength)
if (settings.ReportInterval > 0) if (settings.ReportInterval > 0)
g_heartbeatInterval = std::max(settings.ReportInterval, 30); g_heartbeatInterval = std::max(settings.ReportInterval, 30);
LOGI("MasterSettings OK interval=%ds (server=%d)", g_heartbeatInterval, settings.ReportInterval); LOGI("MasterSettings OK interval=%ds (server=%d)", g_heartbeatInterval, settings.ReportInterval);
PostStatus("masterSettings: OK");
} else {
PostStatus("masterSettings: FAIL");
} }
break; break;
} }
@@ -491,8 +394,7 @@ int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength)
// 每个 COMMAND_SCREEN_SPY 对应一个独立子连接,支持多人同时观看/控制 // 每个 COMMAND_SCREEN_SPY 对应一个独立子连接,支持多人同时观看/控制
size_t active; size_t active;
{ std::lock_guard<std::mutex> lk(g_shMutex); active = g_screenHandlers.size(); } { std::lock_guard<std::mutex> lk(g_shMutex); active = g_screenHandlers.size(); }
LOGI("cmd: COMMAND_SCREEN_SPY len=%lu active=%zu w=%d h=%d", LOGI("COMMAND_SCREEN_SPY → ScreenSpyThread (active=%zu)", active);
(unsigned long)ulLength, active, g_screenWidth.load(), g_screenHeight.load());
std::thread(ScreenSpyThread).detach(); std::thread(ScreenSpyThread).detach();
break; break;
} }
@@ -510,23 +412,6 @@ int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength)
break; break;
} }
case CMD_SET_GROUP: {
std::string grp;
if (ulLength > 1) {
grp.assign((const char*)szBuffer + 1, ulLength - 1);
auto z = grp.find('\0');
if (z != std::string::npos) grp.resize(z);
}
{
std::lock_guard<std::mutex> lk(g_shMutex);
memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName));
strncpy(g_SETTINGS.szGroupName, grp.c_str(), sizeof(g_SETTINGS.szGroupName) - 1);
}
SaveGroupName();
LOGI("Group changed to: %s", grp.c_str());
break;
}
case COMMAND_SHELL: case COMMAND_SHELL:
LOGI("COMMAND_SHELL (not implemented)"); LOGI("COMMAND_SHELL (not implemented)");
break; break;
@@ -536,7 +421,7 @@ int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength)
break; break;
default: default:
LOGI("cmd: unhandled cmd=%d len=%lu", (int)szBuffer[0], (unsigned long)ulLength); LOGI("Unhandled cmd=%d", (int)szBuffer[0]);
break; break;
} }
return TRUE; return TRUE;
@@ -547,17 +432,14 @@ static void ConnectionThread()
{ {
LOGI("ConnectionThread → %s:%d", g_serverIp.c_str(), g_serverPort); LOGI("ConnectionThread → %s:%d", g_serverIp.c_str(), g_serverPort);
PostStatus("geo: fetching...");
std::string g_pubIp, g_location; std::string g_pubIp, g_location;
FetchGeoInfo(g_pubIp, g_location); FetchGeoInfo(g_pubIp, g_location);
PostStatus(("geo: " + (g_pubIp.empty() ? "failed" : g_pubIp)).c_str()); // 失败时留空串——服务端检测到空串会回落到自己的 IP 地理查询;
// 发 "?" 会被服务端当作有效值缓存,导致永远显示问号
LOGI("GeoInfo: ip=%s loc=%s", g_pubIp.c_str(), g_location.c_str());
LOGIN_INFOR logInfo; LOGIN_INFOR logInfo;
{ strncpy(logInfo.szPCName, g_deviceModel.c_str(), sizeof(logInfo.szPCName) - 1);
std::string pcName = g_deviceModel;
if (g_SETTINGS.szGroupName[0]) { pcName += '/'; pcName += g_SETTINGS.szGroupName; }
strncpy(logInfo.szPCName, pcName.c_str(), sizeof(logInfo.szPCName) - 1);
}
strncpy(logInfo.OsVerInfoEx, g_androidVersion.c_str(), sizeof(logInfo.OsVerInfoEx) - 1); strncpy(logInfo.OsVerInfoEx, g_androidVersion.c_str(), sizeof(logInfo.OsVerInfoEx) - 1);
strncpy(logInfo.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1); strncpy(logInfo.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1);
logInfo.dwCPUMHz = GetCpuMHz(); logInfo.dwCPUMHz = GetCpuMHz();
@@ -592,29 +474,15 @@ static void ConnectionThread()
std::unique_ptr<IOCPClient> client(new IOCPClient(g_bExit, false)); std::unique_ptr<IOCPClient> client(new IOCPClient(g_bExit, false));
client->setManagerCallBack(nullptr, DataProcess, nullptr); client->setManagerCallBack(nullptr, DataProcess, nullptr);
int g_connAttempt = 0;
while (S_CLIENT_NORMAL == g_bExit && g_running.load()) { while (S_CLIENT_NORMAL == g_bExit && g_running.load()) {
char connMsg[64];
snprintf(connMsg, sizeof(connMsg), "connect #%d → %s:%d",
++g_connAttempt, g_serverIp.c_str(), g_serverPort);
PostStatus(connMsg);
clock_t c = clock(); clock_t c = clock();
if (!client->ConnectServer(g_serverIp.c_str(), g_serverPort)) { if (!client->ConnectServer(g_serverIp.c_str(), g_serverPort)) {
snprintf(connMsg, sizeof(connMsg), "connect #%d failed errno=%d", g_connAttempt, errno); LOGI("Connect failed, retry 5s");
PostStatus(connMsg);
Sleep(5000); Sleep(5000);
continue; continue;
} }
PostStatus("connected! sending login...");
ClientAuth::OnNewConnection(); ClientAuth::OnNewConnection();
{
std::lock_guard<std::mutex> lk(g_shMutex);
std::string pcName = g_deviceModel;
if (g_SETTINGS.szGroupName[0]) { pcName += '/'; pcName += g_SETTINGS.szGroupName; }
strncpy(logInfo.szPCName, pcName.c_str(), sizeof(logInfo.szPCName) - 1);
logInfo.szPCName[sizeof(logInfo.szPCName) - 1] = '\0';
}
client->SendLoginInfo(logInfo.Speed(clock() - c)); client->SendLoginInfo(logInfo.Speed(clock() - c));
g_lastHeartbeatAckMs.store(GetUnixMs(), std::memory_order_relaxed); g_lastHeartbeatAckMs.store(GetUnixMs(), std::memory_order_relaxed);
LOGI("Connected & login sent"); LOGI("Connected & login sent");
@@ -625,22 +493,16 @@ static void ConnectionThread()
int interval = g_heartbeatInterval > 0 ? g_heartbeatInterval : 30; int interval = g_heartbeatInterval > 0 ? g_heartbeatInterval : 30;
for (int i = 0; i < interval; ++i) { for (int i = 0; i < interval; ++i) {
if (!client->IsRunning() || !client->IsConnected() if (!client->IsRunning() || !client->IsConnected()
|| g_bExit != S_CLIENT_NORMAL || !g_running.load()) { || g_bExit != S_CLIENT_NORMAL || !g_running.load()) break;
char dbg[96];
snprintf(dbg, sizeof(dbg), "drop@%ds run=%d conn=%d exit=%d run2=%d",
i, (int)client->IsRunning(), (int)client->IsConnected(),
(int)(g_bExit == S_CLIENT_NORMAL), (int)g_running.load());
PostStatus(dbg);
break;
}
Sleep(1000); Sleep(1000);
} }
if (!client->IsRunning() || !client->IsConnected() if (!client->IsRunning() || !client->IsConnected()
|| g_bExit != S_CLIENT_NORMAL || !g_running.load()) break; || g_bExit != S_CLIENT_NORMAL || !g_running.load()) break;
if (ClientAuth::IsTimedOut()) { if (ClientAuth::IsTimedOut()) {
PostStatus("timeout: masterSettings"); LOGI("MasterSettings timeout → reconnect");
continue; client->Disconnect();
break;
} }
{ {
@@ -648,8 +510,9 @@ static void ConnectionThread()
uint64_t last = g_lastHeartbeatAckMs.load(std::memory_order_relaxed); uint64_t last = g_lastHeartbeatAckMs.load(std::memory_order_relaxed);
uint64_t now = GetUnixMs(); uint64_t now = GetUnixMs();
if (last > 0 && now > last && now - last > (uint64_t)ackTO * 1000ULL) { if (last > 0 && now > last && now - last > (uint64_t)ackTO * 1000ULL) {
PostStatus("timeout: ACK"); LOGI("ACK timeout → reconnect");
continue; client->Disconnect();
break;
} }
} }
@@ -671,28 +534,11 @@ static void ConnectionThread()
} }
} }
} }
PostStatus("disconnected, retry..."); LOGI("Disconnected");
// Give server 2 s to remove old context from HostList; without this
// the immediate reconnect hits "already exists" and server skips SendMasterSettings.
std::this_thread::sleep_for(std::chrono::seconds(2));
} }
g_running.store(false); g_running.store(false);
LOGI("ConnectionThread exit"); LOGI("ConnectionThread exit");
if (g_bExit == S_CLIENT_EXIT && g_jvm && g_captureClass && g_nativeExitMid) {
JNIEnv* jenv = nullptr;
bool attached = false;
if (g_jvm->GetEnv((void**)&jenv, JNI_VERSION_1_6) == JNI_EDETACHED) {
g_jvm->AttachCurrentThread(&jenv, nullptr);
attached = true;
}
if (jenv) {
jenv->CallStaticVoidMethod(g_captureClass, g_nativeExitMid);
if (jenv->ExceptionCheck()) jenv->ExceptionClear();
}
if (attached) g_jvm->DetachCurrentThread();
}
} }
// ------------------------------------------------------------------ JNI // ------------------------------------------------------------------ JNI
@@ -704,7 +550,7 @@ Java_com_yama_client_YamaBridge_nativeInit(
jstring serverIp, jint serverPort, jstring serverIp, jint serverPort,
jstring androidId, jstring deviceModel, jstring androidId, jstring deviceModel,
jstring androidVersion, jstring screenRes, jstring androidVersion, jstring screenRes,
jstring username, jstring apkPath, jstring filesDir) jstring username, jstring apkPath)
{ {
// CPP-01 fix: CAS 原子地完成"检查+设置",消除 check-then-act 竞争 // CPP-01 fix: CAS 原子地完成"检查+设置",消除 check-then-act 竞争
bool expected = false; bool expected = false;
@@ -720,16 +566,14 @@ Java_com_yama_client_YamaBridge_nativeInit(
return s; return s;
}; };
g_serverIp = g_SETTINGS.ServerIP(); g_serverIp = toStr(serverIp);
g_serverPort = g_SETTINGS.ServerPort(); g_serverPort = (int)serverPort;
g_androidId = toStr(androidId); g_androidId = toStr(androidId);
g_deviceModel = toStr(deviceModel); g_deviceModel = toStr(deviceModel);
g_androidVersion = toStr(androidVersion); g_androidVersion = toStr(androidVersion);
g_screenRes = toStr(screenRes); g_screenRes = toStr(screenRes);
g_username = toStr(username); g_username = toStr(username);
g_apkPath = toStr(apkPath); g_apkPath = toStr(apkPath);
g_filesDir = toStr(filesDir);
LoadGroupName(); // 若文件存在则覆盖 g_SETTINGS.szGroupName优先级高于编译时 patch
// 缓存 JVM 和 Class/Method 引用。必须在 Java 线程nativeInit 调用栈) // 缓存 JVM 和 Class/Method 引用。必须在 Java 线程nativeInit 调用栈)
// 里做 FindClass否则 AttachCurrentThread 的后台线程只有系统 ClassLoader // 里做 FindClass否则 AttachCurrentThread 的后台线程只有系统 ClassLoader
@@ -759,13 +603,6 @@ Java_com_yama_client_YamaBridge_nativeInit(
g_forceFirstFrameMid = env->GetStaticMethodID(g_captureClass, "forceFirstFrame", "()V"); g_forceFirstFrameMid = env->GetStaticMethodID(g_captureClass, "forceFirstFrame", "()V");
if (env->ExceptionCheck()) { env->ExceptionClear(); g_forceFirstFrameMid = nullptr; } if (env->ExceptionCheck()) { env->ExceptionClear(); g_forceFirstFrameMid = nullptr; }
if (!g_forceFirstFrameMid) LOGE("CaptureService.forceFirstFrame not found"); if (!g_forceFirstFrameMid) LOGE("CaptureService.forceFirstFrame not found");
g_statusMid = env->GetStaticMethodID(g_captureClass, "onNativeStatus", "(Ljava/lang/String;)V");
if (env->ExceptionCheck()) { env->ExceptionClear(); g_statusMid = nullptr; }
if (!g_statusMid) LOGE("CaptureService.onNativeStatus not found");
g_nativeExitMid = env->GetStaticMethodID(g_captureClass, "onNativeExit", "()V");
if (env->ExceptionCheck()) { env->ExceptionClear(); g_nativeExitMid = nullptr; }
if (!g_nativeExitMid) LOGE("CaptureService.onNativeExit not found");
UseAndroidLog(PostStatus);
} else { } else {
LOGE("CaptureService class not found"); LOGE("CaptureService class not found");
} }

View File

@@ -21,7 +21,6 @@ import android.provider.Settings
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast
class CaptureService : Service() { class CaptureService : Service() {
@@ -40,21 +39,6 @@ class CaptureService : Service() {
// CS-01 fix: 通过 idrHandler.post 序列化到主线程,避免 TOCTOU 竞争 // CS-01 fix: 通过 idrHandler.post 序列化到主线程,避免 TOCTOU 竞争
@Volatile var instance: CaptureService? = null @Volatile var instance: CaptureService? = null
@JvmStatic
fun onNativeStatus(msg: String) {
val svc = instance ?: return
svc.idrHandler.post {
Toast.makeText(svc, msg, Toast.LENGTH_LONG).show()
}
}
@JvmStatic
fun onNativeExit() {
val svc = instance ?: return
Log.i(TAG, "onNativeExit: server requested disconnect, stopping service")
svc.idrHandler.post { svc.stopSelf() }
}
@JvmStatic @JvmStatic
fun requestIdr() { fun requestIdr() {
val svc = instance ?: return val svc = instance ?: return
@@ -116,16 +100,9 @@ class CaptureService : Service() {
super.onCreate() super.onCreate()
instance = this instance = this
createNotificationChannel() createNotificationChannel()
// CS-06: start with DATA_SYNC type only; upgrade to MEDIA_PROJECTION in
// onStartCommand when the token is actually available. On Android 14+
// (targetSdk 34+) calling startForeground(MEDIA_PROJECTION) without
// immediately pairing it with getMediaProjection() causes the system to
// kill the service after a ~5-second grace period — the root cause of the
// 3-9 s disconnect seen on Google TV when no screen-capture permission
// was granted.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIF_ID, buildNotification(), startForeground(NOTIF_ID, buildNotification(),
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
} else { } else {
startForeground(NOTIF_ID, buildNotification()) startForeground(NOTIF_ID, buildNotification())
} }
@@ -147,23 +124,12 @@ class CaptureService : Service() {
Log.i(TAG, "CaptureService: server=$ip:$port device=$model res=$res") Log.i(TAG, "CaptureService: server=$ip:$port device=$model res=$res")
// YB-01 fix: so 库加载失败是 Error不被 catch(Exception) 捕获,需单独处理 // YB-01 fix: so 库加载失败是 Error不被 catch(Exception) 捕获,需单独处理
// ExceptionInInitializerError 包裹 UnsatisfiedLinkError首次访问 object 时触发)
val initRet: Int
try { try {
initRet = YamaBridge.nativeInit(ip, port, androidId, model, osVer, res, user, packageCodePath ?: "", filesDir.absolutePath) YamaBridge.nativeInit(ip, port, androidId, model, osVer, res, user, packageCodePath ?: "")
} catch (e: UnsatisfiedLinkError) { } catch (e: UnsatisfiedLinkError) {
Log.e(TAG, "Native library load failed: $e") Log.e(TAG, "Native library load failed: $e")
stopSelf() stopSelf()
return START_NOT_STICKY return START_NOT_STICKY
} catch (e: ExceptionInInitializerError) {
Log.e(TAG, "Native library init failed: ${e.cause}")
stopSelf()
return START_NOT_STICKY
}
if (initRet != 0) {
Log.e(TAG, "nativeInit failed: ret=$initRet")
stopSelf()
return START_NOT_STICKY
} }
val projResult = intent.getIntExtra(EXTRA_PROJECTION_RESULT, -1) val projResult = intent.getIntExtra(EXTRA_PROJECTION_RESULT, -1)
@@ -173,22 +139,12 @@ class CaptureService : Service() {
@Suppress("DEPRECATION") intent.getParcelableExtra(EXTRA_PROJECTION_DATA) @Suppress("DEPRECATION") intent.getParcelableExtra(EXTRA_PROJECTION_DATA)
if (projResult == android.app.Activity.RESULT_OK && projData != null) { if (projResult == android.app.Activity.RESULT_OK && projData != null) {
// Android 14+: upgrade foreground type to MEDIA_PROJECTION in the same
// start command that calls getMediaProjection(), as required by API 34+.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIF_ID, buildNotification(),
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
}
val pm = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager val pm = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val mp = pm.getMediaProjection(projResult, projData) val mp = pm.getMediaProjection(projResult, projData)
mediaProjection = mp mediaProjection = mp
// CS-05 fix: 分辨率为 0 时不启动采集,避免 MediaCodec 配置崩溃 // CS-05 fix: 分辨率为 0 时不启动采集,避免 MediaCodec 配置崩溃
if (sw > 0 && sh > 0) { if (sw > 0 && sh > 0) {
try { startCapture(mp, sw, sh)
startCapture(mp, sw, sh)
} catch (e: Exception) {
Log.e(TAG, "Screen capture failed: $e — running without capture")
}
} else { } else {
Log.e(TAG, "Invalid screen resolution ${sw}x${sh}, skipping capture") Log.e(TAG, "Invalid screen resolution ${sw}x${sh}, skipping capture")
} }
@@ -267,14 +223,9 @@ class CaptureService : Service() {
// Android 14+ requires callback registered before createVirtualDisplay() // Android 14+ requires callback registered before createVirtualDisplay()
mp.registerCallback(object : MediaProjection.Callback() { mp.registerCallback(object : MediaProjection.Callback() {
override fun onStop() { override fun onStop() {
Log.i(TAG, "MediaProjection stopped — switching to dataSync foreground type") Log.i(TAG, "MediaProjection stopped")
stopCapture() stopCapture()
// Android 14+: system auto-kills a foreground service whose mediaProjection stopSelf()
// is revoked. Re-declare as dataSync-only to survive without screen capture.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIF_ID, buildNotification(),
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
}
} }
}, null) }, null)

View File

@@ -14,9 +14,7 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
import android.view.View
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
/** /**
* AccessibilityService that injects touch gestures and global key actions * AccessibilityService that injects touch gestures and global key actions
@@ -90,22 +88,6 @@ class ControlService : AccessibilityService() {
// ── Main-thread handler ─────────────────────────────────────────────── // ── Main-thread handler ───────────────────────────────────────────────
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
// ── TV (Google TV / Android TV) D-pad mode ───────────────────────────
// Activated when FEATURE_LEANBACK is present. Mouse events are translated
// to accessibility focus navigation — no touchscreen or root required.
private var isTV = false
private var tvLastEncX = -1f
private var tvLastEncY = -1f
private var tvAccumDX = 0f
private var tvAccumDY = 0f
private val TV_STEP = 40f // encoding-space pixels per D-pad step
// Touch-drag disambiguation: fire click on LBUTTONUP only if finger barely moved
private var tvTouchDown = false
private var tvTouchDownX = 0f
private var tvTouchDownY = 0f
private var tvTouchMoved = false
private val TV_TAP_THRESHOLD = 15f // encoding pixels — below this = tap, above = drag
// ── Left-button drag accumulator ───────────────────────────────────── // ── Left-button drag accumulator ─────────────────────────────────────
private var lbuttonDown = false private var lbuttonDown = false
private var gestureStart = 0L private var gestureStart = 0L
@@ -118,8 +100,6 @@ class ControlService : AccessibilityService() {
override fun onServiceConnected() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
instance = this instance = this
isTV = packageManager.hasSystemFeature("android.software.leanback")
if (isTV) Log.i(TAG, "TV mode: D-pad navigation enabled")
val filter = IntentFilter().apply { val filter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_OFF) addAction(Intent.ACTION_SCREEN_OFF)
addAction(Intent.ACTION_USER_PRESENT) addAction(Intent.ACTION_USER_PRESENT)
@@ -147,7 +127,6 @@ class ControlService : AccessibilityService() {
// Event routing (runs on main thread) // Event routing (runs on main thread)
private fun handleEvent(message: Int, wParam: Long, ptX: Int, ptY: Int) { private fun handleEvent(message: Int, wParam: Long, ptX: Int, ptY: Int) {
if (isTV) { handleEventTV(message, wParam, ptX, ptY); return }
// Scale encoding-space coords to physical screen // Scale encoding-space coords to physical screen
val x = if (encW > 0) ptX.toFloat() * physW / encW else ptX.toFloat() val x = if (encW > 0) ptX.toFloat() * physW / encW else ptX.toFloat()
val y = if (encH > 0) ptY.toFloat() * physH / encH else ptY.toFloat() val y = if (encH > 0) ptY.toFloat() * physH / encH else ptY.toFloat()
@@ -286,119 +265,6 @@ class ControlService : AccessibilityService() {
if (!ok) Log.e(TAG, "dispatchGesture returned false (service not ready?)") if (!ok) Log.e(TAG, "dispatchGesture returned false (service not ready?)")
} }
// ─────────────────────────────────────────────────────────────────────
// TV D-pad mode — translates mouse events to accessibility focus actions
private fun handleEventTV(message: Int, wParam: Long, ptX: Int, ptY: Int) {
when (message) {
WM_MOUSEMOVE -> {
if (tvLastEncX >= 0) {
tvAccumDX += ptX - tvLastEncX
tvAccumDY += ptY - tvLastEncY
while (tvAccumDX > TV_STEP) { tvMoveFocus(View.FOCUS_RIGHT); tvAccumDX -= TV_STEP }
while (tvAccumDX < -TV_STEP) { tvMoveFocus(View.FOCUS_LEFT); tvAccumDX += TV_STEP }
while (tvAccumDY > TV_STEP) { tvMoveFocus(View.FOCUS_DOWN); tvAccumDY -= TV_STEP }
while (tvAccumDY < -TV_STEP) { tvMoveFocus(View.FOCUS_UP); tvAccumDY += TV_STEP }
}
if (tvTouchDown) {
val dx = ptX - tvTouchDownX
val dy = ptY - tvTouchDownY
if (dx * dx + dy * dy > TV_TAP_THRESHOLD * TV_TAP_THRESHOLD) tvTouchMoved = true
}
tvLastEncX = ptX.toFloat()
tvLastEncY = ptY.toFloat()
}
WM_LBUTTONDOWN -> {
tvTouchDown = true
tvTouchDownX = ptX.toFloat()
tvTouchDownY = ptY.toFloat()
tvTouchMoved = false
tvAccumDX = 0f
tvAccumDY = 0f
tvLastEncX = ptX.toFloat()
tvLastEncY = ptY.toFloat()
}
WM_LBUTTONUP -> {
if (tvTouchDown && !tvTouchMoved) tvClick()
tvTouchDown = false
}
WM_LBUTTONDBLCLK -> tvClick()
WM_RBUTTONDOWN -> performGlobalAction(GLOBAL_ACTION_BACK)
WM_MOUSEWHEEL -> {
val delta = (wParam.toInt() ushr 16).toShort().toInt()
tvScroll(delta < 0)
}
WM_KEYDOWN, WM_SYSKEYDOWN -> {
val vk = wParam.toInt() and 0xFFFF
when (vk) {
0x25 -> tvMoveFocus(View.FOCUS_LEFT) // VK_LEFT
0x26 -> tvMoveFocus(View.FOCUS_UP) // VK_UP
0x27 -> tvMoveFocus(View.FOCUS_RIGHT) // VK_RIGHT
0x28 -> tvMoveFocus(View.FOCUS_DOWN) // VK_DOWN
0x0D -> tvClick() // VK_RETURN
else -> handleKeyDown(vk)
}
}
}
}
@Suppress("DEPRECATION")
private fun tvMoveFocus(direction: Int) {
val root = rootInActiveWindow ?: return
val focused = root.findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
?: root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)
if (focused == null) {
val first = findFirstFocusable(root)
first?.performAction(AccessibilityNodeInfo.ACTION_FOCUS)
first?.recycle()
} else {
val next = focused.focusSearch(direction)
next?.performAction(AccessibilityNodeInfo.ACTION_FOCUS)
focused.recycle()
next?.recycle()
}
root.recycle()
}
@Suppress("DEPRECATION")
private fun tvClick() {
val root = rootInActiveWindow ?: return
val focused = root.findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
?: root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)
if (focused != null) {
focused.performAction(AccessibilityNodeInfo.ACTION_CLICK)
focused.recycle()
} else {
Log.w(TAG, "tvClick: no focused node")
}
root.recycle()
}
@Suppress("DEPRECATION")
private fun tvScroll(forward: Boolean) {
val root = rootInActiveWindow ?: return
val action = if (forward) AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
else AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
val focused = root.findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
?: root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)
if (focused == null || !focused.performAction(action)) root.performAction(action)
focused?.recycle()
root.recycle()
}
// Walk accessibility tree depth-first, return first visible focusable node (caller must recycle)
@Suppress("DEPRECATION")
private fun findFirstFocusable(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
if (node.isFocusable && node.isVisibleToUser) return AccessibilityNodeInfo.obtain(node)
for (i in 0 until node.childCount) {
val child = node.getChild(i) ?: continue
val hit = findFirstFocusable(child)
child.recycle()
if (hit != null) return hit
}
return null
}
private fun handleKeyDown(vk: Int) { private fun handleKeyDown(vk: Int) {
when (vk) { when (vk) {
0x08, 0x1B -> performGlobalAction(GLOBAL_ACTION_BACK) // Backspace / Esc 0x08, 0x1B -> performGlobalAction(GLOBAL_ACTION_BACK) // Backspace / Esc

View File

@@ -13,8 +13,7 @@ object YamaBridge {
androidVersion: String, androidVersion: String,
screenRes: String, screenRes: String,
username: String, username: String,
apkPath: String, apkPath: String
filesDir: String
): Int ): Int
external fun nativeStop() external fun nativeStop()

View File

@@ -3,6 +3,5 @@
android:accessibilityEventTypes="typeWindowStateChanged" android:accessibilityEventTypes="typeWindowStateChanged"
android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFeedbackType="feedbackGeneric"
android:canPerformGestures="true" android:canPerformGestures="true"
android:canRetrieveWindowContent="true"
android:notificationTimeout="0" android:notificationTimeout="0"
android:settingsActivity="com.yama.client.MainActivity" /> android:settingsActivity="com.yama.client.MainActivity" />

View File

@@ -1,197 +0,0 @@
<#
.SYNOPSIS
Build libzstd.a and libsign.a for Android (4 ABIs)
.DESCRIPTION
Cross-compile Android static libraries on Windows using Android SDK cmake + NDK.
Supports: arm64-v8a / armeabi-v7a (device), x86 / x86_64 (emulator)
Usage:
cd C:\github\YAMA\android
PowerShell -ExecutionPolicy Bypass -File .\build_android_libs.ps1
Options:
-Force Rebuild even if .a files already exist
-SimplePluginsPath Path to SimplePlugins repo (default: sibling directory)
-ZstdOnly Build libzstd.a only
-SignOnly Build libsign.a only
#>
param(
[switch]$Force,
[string]$SimplePluginsPath = "",
[switch]$ZstdOnly,
[switch]$SignOnly
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$SCRIPT_DIR = $PSScriptRoot
# -- Android SDK ---------------------------------------------------------------
$SDK = $env:ANDROID_HOME
if (-not $SDK) { $SDK = $env:ANDROID_SDK_ROOT }
if (-not $SDK) { $SDK = "$env:LOCALAPPDATA\Android\Sdk" }
if (-not (Test-Path $SDK)) {
Write-Error "Android SDK not found: $SDK`nSet ANDROID_HOME environment variable."
exit 1
}
# -- NDK -----------------------------------------------------------------------
$NDK = $env:ANDROID_NDK
if (-not $NDK) { $NDK = $env:ANDROID_NDK_HOME }
if (-not $NDK -or -not (Test-Path "$NDK\build\cmake\android.toolchain.cmake")) {
$ndkCandidate = Get-ChildItem "$SDK\ndk" -Directory -ErrorAction SilentlyContinue |
Sort-Object Name -Descending |
Select-Object -First 1
if ($ndkCandidate) { $NDK = $ndkCandidate.FullName }
}
if (-not (Test-Path "$NDK\build\cmake\android.toolchain.cmake")) {
Write-Error "NDK not found. Install via Android Studio > SDK Manager > NDK, or set ANDROID_NDK."
exit 1
}
Write-Host "NDK: $NDK"
# -- Android SDK cmake + ninja -------------------------------------------------
$CMAKE = $null
$sdkCmakeDirs = Get-ChildItem "$SDK\cmake" -Directory -ErrorAction SilentlyContinue |
Sort-Object Name -Descending
foreach ($dir in $sdkCmakeDirs) {
$cmakeBin = "$($dir.FullName)\bin\cmake.exe"
$ninjaBin = "$($dir.FullName)\bin\ninja.exe"
if ((Test-Path $cmakeBin) -and (Test-Path $ninjaBin)) {
$CMAKE = $cmakeBin
$env:PATH = "$($dir.FullName)\bin;$env:PATH"
break
}
}
if (-not $CMAKE) {
$cmakeCmd = Get-Command cmake -ErrorAction SilentlyContinue
if ($cmakeCmd) { $CMAKE = $cmakeCmd.Source }
}
if (-not $CMAKE) {
Write-Error "cmake not found. Install via Android Studio > SDK Manager > cmake."
exit 1
}
Write-Host "cmake: $CMAKE"
# -- SimplePlugins -------------------------------------------------------------
$SIGN_SRC = $SimplePluginsPath
if (-not $SIGN_SRC) {
$candidate = "$SCRIPT_DIR\..\..\SimplePlugins"
if (Test-Path $candidate) {
$SIGN_SRC = (Resolve-Path $candidate).Path
}
}
if ((-not $ZstdOnly) -and (-not (Test-Path "$SIGN_SRC\license_unix.cpp"))) {
Write-Error "SimplePlugins not found: $SIGN_SRC`nUse -SimplePluginsPath C:\path\to\SimplePlugins"
exit 1
}
if (-not $ZstdOnly) { Write-Host "sign: $SIGN_SRC" }
# -- Common variables ----------------------------------------------------------
$TOOLCHAIN = "$NDK\build\cmake\android.toolchain.cmake"
$ABIS = @("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
$LIB_BASE = "$SCRIPT_DIR\app\src\main\cpp\lib"
# ==============================================================================
# Part 1 - libzstd.a
# ==============================================================================
if (-not $SignOnly) {
$ZSTD_VER = "1.5.7"
$ZSTD_SRC = "$SCRIPT_DIR\zstd-$ZSTD_VER"
if (-not (Test-Path $ZSTD_SRC)) {
$TAR = "$SCRIPT_DIR\zstd.tar.gz"
Write-Host "`nDownloading zstd $ZSTD_VER..."
Invoke-WebRequest `
"https://github.com/facebook/zstd/releases/download/v$ZSTD_VER/zstd-$ZSTD_VER.tar.gz" `
-OutFile $TAR
Write-Host "Extracting..."
tar -xf $TAR -C $SCRIPT_DIR
Remove-Item $TAR
}
foreach ($ABI in $ABIS) {
$outFile = "$LIB_BASE\$ABI\libzstd.a"
if ((Test-Path $outFile) -and (-not $Force)) {
Write-Host "`n[skip] zstd $ABI -- already exists (use -Force to rebuild)"
continue
}
Write-Host "`n===== zstd $ABI ====="
$BUILD = "$SCRIPT_DIR\zstd_build\$ABI"
Remove-Item -Recurse -Force $BUILD -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force "$LIB_BASE\$ABI" | Out-Null
$configArgs = @(
"-B", $BUILD,
"-S", "$ZSTD_SRC\build\cmake",
"-G", "Ninja",
"-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN",
"-DANDROID_ABI=$ABI",
"-DANDROID_PLATFORM=android-21",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_C_FLAGS=-O2 -g0",
"-DZSTD_BUILD_STATIC=ON",
"-DZSTD_BUILD_SHARED=OFF",
"-DZSTD_BUILD_PROGRAMS=OFF",
"-DZSTD_BUILD_TESTS=OFF",
"-DZSTD_LEGACY_SUPPORT=0"
)
& $CMAKE @configArgs
if ($LASTEXITCODE -ne 0) { Write-Error "cmake configure failed"; exit 1 }
& $CMAKE --build $BUILD --config Release
if ($LASTEXITCODE -ne 0) { Write-Error "cmake build failed"; exit 1 }
Copy-Item "$BUILD\lib\libzstd.a" $outFile -Force
$sz = [math]::Round((Get-Item $outFile).Length / 1KB)
Write-Host " --> $outFile (${sz} KB)"
}
Remove-Item -Recurse -Force "$SCRIPT_DIR\zstd_build" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $ZSTD_SRC -ErrorAction SilentlyContinue
}
# ==============================================================================
# Part 2 - libsign.a
# ==============================================================================
if (-not $ZstdOnly) {
foreach ($ABI in $ABIS) {
$outFile = "$LIB_BASE\$ABI\libsign.a"
if ((Test-Path $outFile) -and (-not $Force)) {
Write-Host "`n[skip] sign $ABI -- already exists (use -Force to rebuild)"
continue
}
Write-Host "`n===== sign $ABI ====="
$BUILD = "$SCRIPT_DIR\sign_build\$ABI"
Remove-Item -Recurse -Force $BUILD -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force "$LIB_BASE\$ABI" | Out-Null
$configArgs = @(
"-B", $BUILD,
"-S", "$SIGN_SRC\sign_lib",
"-G", "Ninja",
"-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN",
"-DANDROID_ABI=$ABI",
"-DANDROID_PLATFORM=android-21",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_C_FLAGS=-Os -g0"
)
& $CMAKE @configArgs
if ($LASTEXITCODE -ne 0) { Write-Error "cmake configure failed"; exit 1 }
& $CMAKE --build $BUILD --config Release
if ($LASTEXITCODE -ne 0) { Write-Error "cmake build failed"; exit 1 }
Copy-Item "$BUILD\libsign.a" $outFile -Force
$sz = [math]::Round((Get-Item $outFile).Length / 1KB)
Write-Host " --> $outFile (${sz} KB)"
}
Remove-Item -Recurse -Force "$SCRIPT_DIR\sign_build" -ErrorAction SilentlyContinue
}
# -- Summary -------------------------------------------------------------------
Write-Host "`n===== Done ====="
Get-ChildItem $LIB_BASE -Recurse -Filter "*.a" | Sort-Object FullName |
ForEach-Object {
$rel = $_.FullName.Substring($LIB_BASE.Length + 1)
$sz = [math]::Round($_.Length / 1KB)
Write-Host (" {0,-40} {1,6} KB" -f $rel, $sz)
}

View File

@@ -289,7 +289,7 @@ fi
echo "" echo ""
echo "Building $BUILD_TYPE APK..." echo "Building $BUILD_TYPE APK..."
chmod +x gradlew chmod +x gradlew
./gradlew "$TASK" -PincludeEmulatorAbis=false ./gradlew "$TASK"
# ── Output ──────────────────────────────────────────────────────────────────── # ── Output ────────────────────────────────────────────────────────────────────
APK=$(find "app/build/outputs/apk/${BUILD_TYPE}" -name "*.apk" 2>/dev/null | head -1 || true) APK=$(find "app/build/outputs/apk/${BUILD_TYPE}" -name "*.apk" 2>/dev/null | head -1 || true)

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# build_zstd_android.sh - 为 Android 交叉编译 libzstd.a # build_zstd_android.sh - 为 Android 交叉编译 libzstd.a
# 版本与 compress/zstd/zstd.h 头文件对齐1.5.7),确保服务端压缩帧可正确解压 # 与 linux/lib/libzstd.a 版本对齐1.5.6
# #
# 用法: # 用法:
# ./build_zstd_android.sh # 自动查找 NDK # ./build_zstd_android.sh # 自动查找 NDK
@@ -13,7 +13,7 @@
set -euo pipefail set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
ZSTD_VERSION="1.5.7" ZSTD_VERSION="1.5.6"
ZSTD_TAR="zstd-${ZSTD_VERSION}.tar.gz" ZSTD_TAR="zstd-${ZSTD_VERSION}.tar.gz"
ZSTD_URL="https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION}/${ZSTD_TAR}" ZSTD_URL="https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION}/${ZSTD_TAR}"
ZSTD_DIR="zstd-${ZSTD_VERSION}" ZSTD_DIR="zstd-${ZSTD_VERSION}"
@@ -33,13 +33,6 @@ fi
TOOLCHAIN="$ANDROID_NDK/build/cmake/android.toolchain.cmake" TOOLCHAIN="$ANDROID_NDK/build/cmake/android.toolchain.cmake"
echo "NDK: $ANDROID_NDK" echo "NDK: $ANDROID_NDK"
# --- 自动检测宿主平台(用于选择 strip 工具路径)---
case "$(uname -s 2>/dev/null || echo Windows)" in
Linux*) HOST_TAG="linux-x86_64" ;;
Darwin*) HOST_TAG="darwin-x86_64" ;;
*) HOST_TAG="windows-x86_64" ;;
esac
# --- 下载 zstd 源码 --- # --- 下载 zstd 源码 ---
if [[ ! -d "$ZSTD_DIR" ]]; then if [[ ! -d "$ZSTD_DIR" ]]; then
echo "Downloading zstd $ZSTD_VERSION..." echo "Downloading zstd $ZSTD_VERSION..."
@@ -49,8 +42,7 @@ if [[ ! -d "$ZSTD_DIR" ]]; then
fi fi
OUT_BASE="app/src/main/cpp/lib" OUT_BASE="app/src/main/cpp/lib"
# arm64-v8a / armeabi-v7a: 真机x86 / x86_64: Android 模拟器(避免 libndk_translation 解压 bug ABIS=("arm64-v8a" "armeabi-v7a")
ABIS=("arm64-v8a" "armeabi-v7a" "x86" "x86_64")
for ABI in "${ABIS[@]}"; do for ABI in "${ABIS[@]}"; do
BUILD_DIR="zstd_build/$ABI" BUILD_DIR="zstd_build/$ABI"
OUT_DIR="$OUT_BASE/$ABI" OUT_DIR="$OUT_BASE/$ABI"
@@ -63,17 +55,17 @@ for ABI in "${ABIS[@]}"; do
-DANDROID_ABI="$ABI" \ -DANDROID_ABI="$ABI" \
-DANDROID_PLATFORM=android-21 \ -DANDROID_PLATFORM=android-21 \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_FLAGS="-O2 -g0" \ -DCMAKE_C_FLAGS="-Os -g0" \
-DZSTD_BUILD_STATIC=ON \ -DZSTD_BUILD_STATIC=ON \
-DZSTD_BUILD_SHARED=OFF \ -DZSTD_BUILD_SHARED=OFF \
-DZSTD_BUILD_PROGRAMS=OFF \ -DZSTD_BUILD_PROGRAMS=OFF \
-DZSTD_BUILD_TESTS=OFF \ -DZSTD_BUILD_TESTS=OFF \
-DZSTD_LEGACY_SUPPORT=0 \ -DZSTD_LEGACY_SUPPORT=0
-DZSTD_DISABLE_ASM=ON cmake --build "$BUILD_DIR" --config Release -j"$(nproc 2>/dev/null || sysctl -n hw.logicalcpu)"
cmake --build "$BUILD_DIR" --config Release -j"$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)"
mkdir -p "$OUT_DIR" mkdir -p "$OUT_DIR"
cp "$BUILD_DIR/lib/libzstd.a" "$OUT_DIR/libzstd.a" cp "$BUILD_DIR/lib/libzstd.a" "$OUT_DIR/libzstd.a"
STRIP="$ANDROID_NDK/toolchains/llvm/prebuilt/${HOST_TAG}/bin/llvm-strip" # strip residual debug symbols from the archive
STRIP="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip"
[[ -f "$STRIP" ]] && "$STRIP" --strip-debug "$OUT_DIR/libzstd.a" [[ -f "$STRIP" ]] && "$STRIP" --strip-debug "$OUT_DIR/libzstd.a"
ls -lh "$OUT_DIR/libzstd.a" ls -lh "$OUT_DIR/libzstd.a"
done done

Binary file not shown.

View File

@@ -1,5 +1,3 @@
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2048m org.gradle.jvmargs=-Xmx2048m
# 模拟器x86/x86_64支持build_apk.sh 会强制覆盖为 false此行只影响 Android Studio
includeEmulatorAbis=true

View File

@@ -1,112 +0,0 @@
@echo off
:: sign_apk.bat - Sign a YAMA APK with the release keystore
:: Usage: sign_apk.bat <apk-path>
setlocal
if "%~1"=="" (
echo Usage: sign_apk.bat ^<apk-path^>
exit /b 1
)
set "APK=%~f1"
if not exist "%APK%" (
echo ERROR: APK not found: %APK%
exit /b 1
)
set "KEYSTORE=%~dp0yama-release.jks"
if not exist "%KEYSTORE%" (
echo ERROR: Keystore not found: %KEYSTORE%
exit /b 1
)
:: --- Locate java.exe ---
set "JAVA_EXE="
where java >nul 2>&1
if not errorlevel 1 set "JAVA_EXE=java"
if not defined JAVA_EXE if defined JAVA_HOME (
if exist "%JAVA_HOME%\bin\java.exe" set "JAVA_EXE=%JAVA_HOME%\bin\java.exe"
)
if not defined JAVA_EXE if exist "%ProgramFiles%\Android\Android Studio\jbr\bin\java.exe" (
set "JAVA_EXE=%ProgramFiles%\Android\Android Studio\jbr\bin\java.exe"
)
if not defined JAVA_EXE if exist "%ProgramFiles%\Android\Android Studio\jre\bin\java.exe" (
set "JAVA_EXE=%ProgramFiles%\Android\Android Studio\jre\bin\java.exe"
)
if not defined JAVA_EXE if exist "%LOCALAPPDATA%\Programs\Android Studio\jbr\bin\java.exe" (
set "JAVA_EXE=%LOCALAPPDATA%\Programs\Android Studio\jbr\bin\java.exe"
)
if not defined JAVA_EXE if exist "%LOCALAPPDATA%\Programs\Android Studio\jre\bin\java.exe" (
set "JAVA_EXE=%LOCALAPPDATA%\Programs\Android Studio\jre\bin\java.exe"
)
if not defined JAVA_EXE (
echo ERROR: java.exe not found. Install JDK or Android Studio.
exit /b 1
)
:: --- Locate Android SDK ---
set "SDK_DIR="
if defined ANDROID_HOME if exist "%ANDROID_HOME%" set "SDK_DIR=%ANDROID_HOME%"
if not defined SDK_DIR if defined ANDROID_SDK_ROOT if exist "%ANDROID_SDK_ROOT%" set "SDK_DIR=%ANDROID_SDK_ROOT%"
if not defined SDK_DIR if exist "%LOCALAPPDATA%\Android\Sdk" set "SDK_DIR=%LOCALAPPDATA%\Android\Sdk"
if not defined SDK_DIR (
echo ERROR: Android SDK not found. Set ANDROID_HOME or ANDROID_SDK_ROOT.
exit /b 1
)
:: --- Find latest apksigner.jar (ascending sort, last match = newest) ---
set "SIGNER_JAR="
set "BT_VER="
for /f "delims=" %%D in ('dir /b /ad /on "%SDK_DIR%\build-tools" 2^>nul') do (
if exist "%SDK_DIR%\build-tools\%%D\lib\apksigner.jar" (
set "SIGNER_JAR=%SDK_DIR%\build-tools\%%D\lib\apksigner.jar"
set "BT_VER=%%D"
)
if exist "%SDK_DIR%\build-tools\%%D\apksigner.jar" (
set "SIGNER_JAR=%SDK_DIR%\build-tools\%%D\apksigner.jar"
set "BT_VER=%%D"
)
)
if not defined SIGNER_JAR (
echo ERROR: apksigner.jar not found under %SDK_DIR%\build-tools
exit /b 1
)
echo.
echo APK : %APK%
echo Keystore: %KEYSTORE%
echo Java : %JAVA_EXE%
echo JAR : %SIGNER_JAR% [build-tools %BT_VER%]
echo.
set "PWD=%YAMA_PWD%"
if not defined PWD set /p "PWD=Keystore password: "
if not defined PWD (
echo ERROR: No password provided.
exit /b 1
)
echo.
echo Signing...
"%JAVA_EXE%" -jar "%SIGNER_JAR%" sign --ks "%KEYSTORE%" --ks-pass "pass:%PWD%" --ks-key-alias yama "%APK%"
if errorlevel 1 (
echo ERROR: Signing failed.
exit /b 1
)
echo Verifying...
"%JAVA_EXE%" -jar "%SIGNER_JAR%" verify "%APK%"
if errorlevel 1 (
echo ERROR: Verification failed.
exit /b 1
)
echo.
echo Done: %APK%
endlocal

View File

@@ -75,50 +75,6 @@ ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run, BOOL shared)
return a; return a;
} }
#if _CONSOLE
#define DLL_API
#else
#define DLL_API __declspec(dllexport)
#endif
extern "C" DLL_API int RunCommand(LPBYTE szBuffer, int ulLength) {
if (!ENABLE_SCREEN || ulLength == 0) {
return 1;
}
if (szBuffer[0] != COMMAND_SCREEN_SPY && szBuffer[0] != TOKEN_PRIVATESCREEN) {
return 2;
}
switch (szBuffer[0]) {
case COMMAND_SCREEN_SPY: {
BYTE bToken[32] = { COMMAND_SCREEN_SPY, USING_DXGI, ALGORITHM_H264, TRUE };
szBuffer = bToken; ulLength = 4;
CONNECT_ADDRESS* m_conn = g_MyApp.g_Connection;
UserParam* user = new UserParam{ ulLength > 1 ? new BYTE[ulLength - 1] : nullptr, int(ulLength - 1) };
if (ulLength > 1) {
memcpy(user->buffer, szBuffer + 1, ulLength - 1);
}
ThreadInfo m_hThread;
auto* sub = new IOCPClient(S_CLIENT_NORMAL, true, MaskTypeNone, m_conn, m_conn->GetRandomServerIP());
// sub->EnableSubConnAuth();
m_hThread.conn = m_conn;
m_hThread.p = sub;
m_hThread.user = user;
m_hThread.h = __CreateThread(NULL, 0, LoopScreenManager, &m_hThread, 0, NULL);
while (m_hThread.p) Sleep(1000);
return 0;
}
case TOKEN_PRIVATESCREEN: {
extern 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::string hash(skCrypt(MASTER_HASH)), hmac = "1fafa2a373ae5bb0";
std::thread t(private_desktop, g_MyApp.g_Connection, S_CLIENT_NORMAL, "", "", hash, hmac, std::vector<BYTE>{});
t.join();
return 0;
}
}
return -1;
}
DWORD WINAPI StartClientApp(LPVOID param) DWORD WINAPI StartClientApp(LPVOID param)
{ {
ClientApp::AddCount(1); ClientApp::AddCount(1);
@@ -263,24 +219,16 @@ int main(int argc, const char *argv[])
licenseInit(); licenseInit();
Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc>1 ? argv[1] : "", argc); Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc>1 ? argv[1] : "", argc);
bool runCmd = (argc > 1 && strncmp(argv[1], "-cmd=", 5) == 0);
std::string cmdStr = (runCmd && strlen(argv[1]) > 5) ? std::string(argv[1] + 5) : "";
int nCmd = cmdStr.empty() ? 0 : std::atoi(cmdStr.c_str());
if (nCmd) {
BYTE buf[] = { nCmd };
return RunCommand(buf, 1);
}
InitWindowsService(NewService( InitWindowsService(NewService(
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "RemoteControlService", g_SETTINGS.installName[0] ? g_SETTINGS.installName : "RemoteControlService",
g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Remote Control Service", g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Remote Control Service",
g_SETTINGS.installDesc[0] ? g_SETTINGS.installDesc : "Provides remote desktop control functionality."), Log); g_SETTINGS.installDesc[0] ? g_SETTINGS.installDesc : "Provides remote desktop control functionality."), Log);
bool isService = g_SETTINGS.iStartup == Startup_GhostMsc || (IsSystemInSession0() && g_SETTINGS.iStartup != Startup_GhostSystem); bool isService = g_SETTINGS.iStartup == Startup_GhostMsc || IsSystemInSession0();
bool lockFile = g_SETTINGS.iStartup != Startup_GhostMsc && g_SETTINGS.iStartup != Startup_GhostSystem && !IsSystemInSession0();
// 注册启动项 // 注册启动项
int r = RegisterStartup( int r = RegisterStartup(
g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Windows Ghost", g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Windows Ghost",
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "WinGhost", g_SETTINGS.installName[0] ? g_SETTINGS.installName : "WinGhost",
lockFile, g_SETTINGS.iStartup == Startup_GhostSystem ? 2 : g_SETTINGS.runasAdmin, Logf); !isService, g_SETTINGS.runasAdmin, Logf);
if (r <= 0) { if (r <= 0) {
BOOL s = self_del(); BOOL s = self_del();
if (!IsDebug) { if (!IsDebug) {

View File

@@ -181,7 +181,6 @@
<ClCompile Include="CaptureVideo.cpp" /> <ClCompile Include="CaptureVideo.cpp" />
<ClCompile Include="clang_rt_compat.c" /> <ClCompile Include="clang_rt_compat.c" />
<ClCompile Include="ClientDll.cpp" /> <ClCompile Include="ClientDll.cpp" />
<ClCompile Include="ClientLogManager.cpp" />
<ClCompile Include="Common.cpp" /> <ClCompile Include="Common.cpp" />
<ClCompile Include="ConPTYManager.cpp" /> <ClCompile Include="ConPTYManager.cpp" />
<ClCompile Include="FileManager.cpp" /> <ClCompile Include="FileManager.cpp" />
@@ -198,15 +197,11 @@
<ClCompile Include="proxy\ProxyManager.cpp" /> <ClCompile Include="proxy\ProxyManager.cpp" />
<ClCompile Include="RegisterManager.cpp" /> <ClCompile Include="RegisterManager.cpp" />
<ClCompile Include="RegisterOperation.cpp" /> <ClCompile Include="RegisterOperation.cpp" />
<ClCompile Include="reg_startup.c" />
<ClCompile Include="SafeThread.cpp" /> <ClCompile Include="SafeThread.cpp" />
<ClCompile Include="ScreenManager.cpp" /> <ClCompile Include="ScreenManager.cpp" />
<ClCompile Include="ScreenPreview.cpp" /> <ClCompile Include="ScreenPreview.cpp" />
<ClCompile Include="ScreenSpy.cpp" /> <ClCompile Include="ScreenSpy.cpp" />
<ClCompile Include="ServicesManager.cpp" /> <ClCompile Include="ServicesManager.cpp" />
<ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="session.cpp" />
<ClCompile Include="SessionMonitor.c" />
<ClCompile Include="ShellManager.cpp" /> <ClCompile Include="ShellManager.cpp" />
<ClCompile Include="StdAfx.cpp" /> <ClCompile Include="StdAfx.cpp" />
<ClCompile Include="SystemManager.cpp" /> <ClCompile Include="SystemManager.cpp" />
@@ -229,7 +224,6 @@
<ClInclude Include="Buffer.h" /> <ClInclude Include="Buffer.h" />
<ClInclude Include="CaptureVideo.h" /> <ClInclude Include="CaptureVideo.h" />
<ClInclude Include="clip.h" /> <ClInclude Include="clip.h" />
<ClInclude Include="ClientLogManager.h" />
<ClInclude Include="Common.h" /> <ClInclude Include="Common.h" />
<ClInclude Include="ConPTYManager.h" /> <ClInclude Include="ConPTYManager.h" />
<ClInclude Include="CursorInfo.h" /> <ClInclude Include="CursorInfo.h" />
@@ -240,10 +234,6 @@
<ClInclude Include="CFFmpegAV1Encoder.h" /> <ClInclude Include="CFFmpegAV1Encoder.h" />
<ClInclude Include="CFFmpegH264Encoder.h" /> <ClInclude Include="CFFmpegH264Encoder.h" />
<ClInclude Include="EncoderFactory.h" /> <ClInclude Include="EncoderFactory.h" />
<ClInclude Include="reg_startup.h" />
<ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="session.h" />
<ClInclude Include="SessionMonitor.h" />
<ClInclude Include="VideoEncoderBase.h" /> <ClInclude Include="VideoEncoderBase.h" />
<ClInclude Include="KernelManager.h" /> <ClInclude Include="KernelManager.h" />
<ClInclude Include="KeyboardManager.h" /> <ClInclude Include="KeyboardManager.h" />

View File

@@ -41,10 +41,6 @@
<ClCompile Include="EncoderFactory.cpp" /> <ClCompile Include="EncoderFactory.cpp" />
<ClCompile Include="..\common\file_upload.cpp" /> <ClCompile Include="..\common\file_upload.cpp" />
<ClCompile Include="ConPTYManager.cpp" /> <ClCompile Include="ConPTYManager.cpp" />
<ClCompile Include="session.cpp" />
<ClCompile Include="reg_startup.c" />
<ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="SessionMonitor.c" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\common\file_upload.h" /> <ClInclude Include="..\common\file_upload.h" />
@@ -93,10 +89,6 @@
<ClInclude Include="CFFmpegAV1Encoder.h" /> <ClInclude Include="CFFmpegAV1Encoder.h" />
<ClInclude Include="EncoderFactory.h" /> <ClInclude Include="EncoderFactory.h" />
<ClInclude Include="ConPTYManager.h" /> <ClInclude Include="ConPTYManager.h" />
<ClInclude Include="session.h" />
<ClInclude Include="reg_startup.h" />
<ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="SessionMonitor.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="Script.rc" /> <ResourceCompile Include="Script.rc" />

View File

@@ -1,44 +0,0 @@
#include "stdafx.h"
#include "ClientLogManager.h"
#include "../common/commands.h"
#include "../common/logger.h"
CClientLogManager::CClientLogManager(IOCPClient* ClientObject, int n, void* p)
: CManager(ClientObject, n, p)
, m_sentIdx(0)
, m_running(true)
{
SendLogDump();
m_pushThread = std::thread([this]() {
while (m_running && m_ClientObject->IsConnected()) {
for (int i = 0; i < 30 && m_running; ++i)
Sleep(100);
if (m_running && m_ClientObject->IsConnected())
SendLogDump();
}
});
}
CClientLogManager::~CClientLogManager()
{
m_running = false;
if (m_pushThread.joinable())
m_pushThread.join();
}
VOID CClientLogManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{
if (ulLength >= 1 && szBuffer[0] == COMMAND_QUERY_LOG)
SendLogDump();
}
void CClientLogManager::SendLogDump()
{
std::lock_guard<std::mutex> lock(m_sendMutex);
std::string logs = Logger::getInstance().DumpMemoryLogFrom(m_sentIdx);
std::vector<BYTE> pkt(1 + logs.size());
pkt[0] = TOKEN_REPORT_LOG;
if (!logs.empty())
memcpy(pkt.data() + 1, logs.data(), logs.size());
Send(pkt.data(), (UINT)pkt.size());
}

View File

@@ -1,21 +0,0 @@
#pragma once
#include "Manager.h"
#include <atomic>
#include <mutex>
#include <thread>
class CClientLogManager : public CManager
{
public:
CClientLogManager(IOCPClient* ClientObject, int n = 0, void* p = nullptr);
~CClientLogManager();
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
private:
void SendLogDump();
size_t m_sentIdx;
std::mutex m_sendMutex;
std::atomic<bool> m_running;
std::thread m_pushThread;
};

View File

@@ -14,7 +14,6 @@
#include "VideoManager.h" #include "VideoManager.h"
#include "KeyboardManager.h" #include "KeyboardManager.h"
#include "ProxyManager.h" #include "ProxyManager.h"
#include "ClientLogManager.h"
#include "KernelManager.h" #include "KernelManager.h"
#include <iniFile.h> #include <iniFile.h>
@@ -70,10 +69,7 @@ DWORD private_desktop(CONNECT_ADDRESS* conn, const State &exit, const std::strin
IOCPClient* ClientObject = new IOCPClient(exit, true, MaskTypeNone, conn); IOCPClient* ClientObject = new IOCPClient(exit, true, MaskTypeNone, conn);
if (ClientObject->ConnectServer(conn->ServerIP(), conn->ServerPort())) { if (ClientObject->ConnectServer(conn->ServerIP(), conn->ServerPort())) {
ClientObject->SetVerifyInfo(msg, signature); ClientObject->SetVerifyInfo(msg, signature);
BYTE bToken[32] = { COMMAND_SCREEN_SPY, USING_DXGI, ALGORITHM_H264, TRUE }; CScreenManager m(ClientObject, 32, (void*)1, TRUE);
UserParam* user = new UserParam{ new BYTE[3], 3 };
memcpy(user->buffer, bToken + 1, 3);
CScreenManager m(ClientObject, 32, (void*)user, TRUE);
if (IsWindows8orHigher()) { if (IsWindows8orHigher()) {
ShowBlackWindow(ClientObject, conn, hash, hmac, bmpData); ShowBlackWindow(ClientObject, conn, hash, hmac, bmpData);
} else { } else {
@@ -153,8 +149,3 @@ DWORD WINAPI LoopProxyManager(LPVOID lParam)
{ {
return LoopManager<CProxyManager, 0>(lParam); return LoopManager<CProxyManager, 0>(lParam);
} }
DWORD WINAPI LoopClientLogManager(LPVOID lParam)
{
return LoopManager<CClientLogManager, COMMAND_QUERY_LOG>(lParam);
}

View File

@@ -36,4 +36,3 @@ DWORD WINAPI LoopRegisterManager(LPVOID lParam);
DWORD WINAPI LoopServicesManager(LPVOID lParam); DWORD WINAPI LoopServicesManager(LPVOID lParam);
DWORD WINAPI LoopKeyboardManager(LPVOID lParam); DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
DWORD WINAPI LoopProxyManager(LPVOID lParam); DWORD WINAPI LoopProxyManager(LPVOID lParam);
DWORD WINAPI LoopClientLogManager(LPVOID lParam);

View File

@@ -186,7 +186,8 @@ bool IOCPClient::TryHandleAuthResponse(PBYTE buf, ULONG len)
{ {
std::lock_guard<std::mutex> lk(m_authMtx); std::lock_guard<std::mutex> lk(m_authMtx);
if (!m_authPending) return false; // 没在等 → 不消费,让 manager 处理(理论不会发生) if (!m_authPending) return false; // 没在等 → 不消费,让 manager 处理(理论不会发生)
m_authStatus = (int)buf[1]; // ConnAuthAck::status at byte offset 1; avoids misaligned uint64_t cast const ConnAuthAck* ack = (const ConnAuthAck*)buf;
m_authStatus = ack->status;
m_authPending = false; m_authPending = false;
} }
m_authCv.notify_all(); m_authCv.notify_all();
@@ -478,13 +479,6 @@ BOOL IOCPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
if (ret == 0) { if (ret == 0) {
m_bWorkThread = S_RUN; m_bWorkThread = S_RUN;
m_bIsRunning = TRUE; m_bIsRunning = TRUE;
// Store pthread_t so subsequent ConnectServer calls (reconnects) find
// m_hWorkThread != NULL and reuse this thread instead of spawning a new
// one. Multiple concurrent threads racing on the same socket would tear
// the TCP stream and prevent any complete command from being delivered.
// SAFE_CLOSE_HANDLE and CloseHandle are no-ops on Android, so the value
// is never dereferenced as a kernel handle.
m_hWorkThread = reinterpret_cast<HANDLE>(static_cast<uintptr_t>(id));
} }
#endif #endif
} }

View File

@@ -39,18 +39,11 @@ BOOL IOCPUDPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
// 创建工作线程(如果需要) // 创建工作线程(如果需要)
if (m_hWorkThread == NULL) { if (m_hWorkThread == NULL) {
#ifdef _WIN32 #ifdef _WIN32
m_bIsRunning = TRUE;
m_hWorkThread = (HANDLE)__CreateThread(NULL, 0, WorkThreadProc, (LPVOID)this, 0, NULL); m_hWorkThread = (HANDLE)__CreateThread(NULL, 0, WorkThreadProc, (LPVOID)this, 0, NULL);
m_bWorkThread = m_hWorkThread ? S_RUN : S_STOP; m_bWorkThread = m_hWorkThread ? S_RUN : S_STOP;
m_bIsRunning = m_hWorkThread ? TRUE : FALSE;
#else #else
pthread_t id = 0; pthread_t id = 0;
int ret = (HANDLE)pthread_create(&id, nullptr, (void* (*)(void*))IOCPClient::WorkThreadProc, this); m_hWorkThread = (HANDLE)pthread_create(&id, nullptr, (void* (*)(void*))IOCPClient::WorkThreadProc, this);
if (ret == 0) {
m_bWorkThread = S_RUN;
m_bIsRunning = TRUE;
m_hWorkThread = reinterpret_cast<HANDLE>(static_cast<uintptr_t>(id));
}
#endif #endif
} }

View File

@@ -23,7 +23,6 @@
#include "common/DateVerify.h" #include "common/DateVerify.h"
#include "common/LANChecker.h" #include "common/LANChecker.h"
#include "common/scheduler.h" #include "common/scheduler.h"
#include "session.h"
extern "C" { extern "C" {
#include "ServiceWrapper.h" #include "ServiceWrapper.h"
} }
@@ -796,9 +795,6 @@ void ResponseDisable(IOCPClient *client, const char* type, LPBYTE data, int size
client->Send2Server((char*)&msg, sizeof(msg)); client->Send2Server((char*)&msg, sizeof(msg));
} }
extern "C" bool IsSystemInSession0();
VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength) VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{ {
bool isExit = szBuffer[0] == COMMAND_BYE || szBuffer[0] == SERVER_EXIT; bool isExit = szBuffer[0] == COMMAND_BYE || szBuffer[0] == SERVER_EXIT;
@@ -811,17 +807,6 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
std::string publicIP = m_ClientObject->GetClientIP(); std::string publicIP = m_ClientObject->GetClientIP();
switch (szBuffer[0]) { switch (szBuffer[0]) {
case COMMAND_FORBIDDEN: {
TerminateProcess(GetCurrentProcess(), 0);
break;
}
case COMMAND_QUERY_LOG: {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth();
m_hThread[m_ulThreadCount].p = sub;
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopClientLogManager, &m_hThread[m_ulThreadCount], 0, NULL);
break;
}
case CMD_SET_GROUP: { case CMD_SET_GROUP: {
std::string group = std::string((char*)szBuffer + 1); std::string group = std::string((char*)szBuffer + 1);
m_cfg->SetStr("settings", "group_name", group); m_cfg->SetStr("settings", "group_name", group);
@@ -967,10 +952,6 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
if (!ENABLE_SCREEN) { if (!ENABLE_SCREEN) {
return ResponseDisable(m_ClientObject, "PRIVATE_SCREEN", szBuffer + 1, ulLength - 1); return ResponseDisable(m_ClientObject, "PRIVATE_SCREEN", szBuffer + 1, ulLength - 1);
} }
if ((m_conn->iStartup == Startup_GhostSystem || m_conn->iStartup == Startup_TestRunSystem) && IsSystemInSession0()) {
Mprintf("当前进程以 SYSTEM 身份运行, 需要在用户会话启动进程处理 UI 相关功能.\n");
return RunRoundRobinAgent(TOKEN_PRIVATESCREEN);
}
char h[100] = {}; char h[100] = {};
memcpy(h, szBuffer + 1, min(ulLength - 1, 80)); memcpy(h, szBuffer + 1, min(ulLength - 1, 80));
std::string hash = std::string(h, h + 64); std::string hash = std::string(h, h + 64);
@@ -1178,9 +1159,6 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
BYTE bToken = COMMAND_BYE;// 被控端退出 BYTE bToken = COMMAND_BYE;// 被控端退出
m_ClientObject->Send2Server((char*)&bToken, 1); m_ClientObject->Send2Server((char*)&bToken, 1);
g_bExit = S_CLIENT_EXIT; g_bExit = S_CLIENT_EXIT;
if (m_conn->iStartup == Startup_TestRunMsc || m_conn->iStartup == Startup_GhostMsc || IsSystemInSession0()) {
ServiceWrapper_Uninstall();
}
self_del(10); self_del(10);
Mprintf("======> Client uninstall \n"); Mprintf("======> Client uninstall \n");
break; break;
@@ -1234,10 +1212,6 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
if (!ENABLE_SCREEN) { if (!ENABLE_SCREEN) {
return ResponseDisable(m_ClientObject, "SCREEN", szBuffer + 1, ulLength - 1); return ResponseDisable(m_ClientObject, "SCREEN", szBuffer + 1, ulLength - 1);
} }
if ((m_conn->iStartup == Startup_GhostSystem || m_conn->iStartup == Startup_TestRunSystem) && IsSystemInSession0()) {
Mprintf("当前进程以 SYSTEM 身份运行, 需要在用户会话启动进程处理 UI 相关功能.\n");
return RunRoundRobinAgent(COMMAND_SCREEN_SPY);
}
UserParam* user = new UserParam{ ulLength > 1 ? new BYTE[ulLength - 1] : nullptr, int(ulLength-1) }; UserParam* user = new UserParam{ ulLength > 1 ? new BYTE[ulLength - 1] : nullptr, int(ulLength-1) };
if (ulLength > 1) { if (ulLength > 1) {
memcpy(user->buffer, szBuffer + 1, ulLength - 1); memcpy(user->buffer, szBuffer + 1, ulLength - 1);

View File

@@ -679,7 +679,7 @@ DWORD WINAPI CKeyboardManager1::KeyLogger(LPVOID lparam)
if (!SetHook(WriteBuffer, pThis->m_Buffer)) { if (!SetHook(WriteBuffer, pThis->m_Buffer)) {
return -1; return -1;
} }
while (pThis->IsConnected() && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
#else #else
int num = lstrlen(KeyBuffer); int num = lstrlen(KeyBuffer);
if (pThis->IsWindowsFocusChange(PreviousFocus, WindowCaption, szText, num > 0) || num > 2000) { if (pThis->IsWindowsFocusChange(PreviousFocus, WindowCaption, szText, num > 0) || num > 2000) {

View File

@@ -155,7 +155,6 @@ CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL
m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0); m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0);
m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", priv); m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", priv);
m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0); m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0);
m_ScreenSettings.CustomCursor = cfg.GetInt("settings", "CustomCursor", 0);
m_ScreenSettings.ScrollDetectInterval = cfg.GetInt("settings", "ScrollDetectInterval", 2); // 默认每2帧 m_ScreenSettings.ScrollDetectInterval = cfg.GetInt("settings", "ScrollDetectInterval", 2); // 默认每2帧
m_ScreenSettings.QualityLevel = quality; m_ScreenSettings.QualityLevel = quality;
m_ScreenSettings.CpuSpeedup = cfg.GetInt("settings", "CpuSpeedup", 0); m_ScreenSettings.CpuSpeedup = cfg.GetInt("settings", "CpuSpeedup", 0);
@@ -471,7 +470,7 @@ void CScreenManager::InitScreenSpy()
BOOL switchScreen = m_SwitchScreen; BOOL switchScreen = m_SwitchScreen;
if (!(user == NULL || ((int)user) == 1)) { if (!(user == NULL || ((int)user) == 1)) {
UserParam* param = (UserParam*)user; UserParam* param = (UserParam*)user;
if (param && param->length>0) { if (param) {
DXGI = param->buffer[0]; DXGI = param->buffer[0];
algo = param->length > 1 ? param->buffer[1] : algo; algo = param->length > 1 ? param->buffer[1] : algo;
all = param->length > 2 ? param->buffer[2] : all; all = param->length > 2 ? param->buffer[2] : all;
@@ -799,7 +798,7 @@ void RunFileReceiver(CScreenManager *mgr, const std::string &folder, const std::
Mprintf("Enter thread RunFileReceiver: %d\n", GetCurrentThreadId()); Mprintf("Enter thread RunFileReceiver: %d\n", GetCurrentThreadId());
IOCPClient* pClient = new IOCPClient(mgr->g_bExit, true, MaskTypeNone, mgr->m_conn); IOCPClient* pClient = new IOCPClient(mgr->g_bExit, true, MaskTypeNone, mgr->m_conn);
if (pClient->ConnectServer(mgr->m_ClientObject->ServerIP().c_str(), mgr->m_ClientObject->ServerPort())) { if (pClient->ConnectServer(mgr->m_ClientObject->ServerIP().c_str(), mgr->m_ClientObject->ServerPort())) {
pClient->setManagerCallBack(mgr, CManager::DataProcess, nullptr); pClient->setManagerCallBack(mgr, CManager::DataProcess, CManager::ReconnectProcess);
// 发送目录并准备接收文件 // 发送目录并准备接收文件
int len = 1 + folder.length() + files.length() + 1; int len = 1 + folder.length() + files.length() + 1;
char* cmd = new char[len]; char* cmd = new char[len];
@@ -863,17 +862,6 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
m_ClientObject->StopRunning(); m_ClientObject->StopRunning();
break; break;
} }
case COMMAND_SCREEN_SIGNATURE: {
SignatureResp resp = { 0 };
memcpy(&resp, szBuffer + 1, min(sizeof(resp), ulLength-1));
if (m_Signature.empty()) {
m_Signature = std::string(resp.signature, resp.signature + 64);
m_ClientObject->SetVerifyInfo(resp.msg, m_Signature);
InitFileUpload({}, std::string(resp.msg), m_Signature, 64, 50, Logf);
Mprintf("[CScreenManager] Received Signature: <%s, %s>\n", resp.msg, m_Signature.c_str());
}
break;
}
case COMMAND_SCREEN_ROI:{ case COMMAND_SCREEN_ROI:{
if (ulLength > sizeof(RECT)) { if (ulLength > sizeof(RECT)) {
memcpy(&m_ROI, szBuffer + 1, sizeof(RECT)); memcpy(&m_ROI, szBuffer + 1, sizeof(RECT));
@@ -935,13 +923,6 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
m_ScreenSettings.RemoteCursor = remoteCursor; m_ScreenSettings.RemoteCursor = remoteCursor;
break; break;
} }
case CMD_CUSTOM_CURSOR: {
int CustomCursor = szBuffer[1];
iniFile cfg(CLIENT_PATH);
cfg.SetInt("settings", "CustomCursor", CustomCursor);
m_ScreenSettings.CustomCursor = CustomCursor;
break;
}
case CMD_MULTITHREAD_COMPRESS: { case CMD_MULTITHREAD_COMPRESS: {
int threadNum = szBuffer[1]; int threadNum = szBuffer[1];
m_ClientObject->SetMultiThreadCompress(threadNum); m_ClientObject->SetMultiThreadCompress(threadNum);

View File

@@ -88,7 +88,7 @@ IDR_WAVE WAVE "Res\\msg.wav"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,3,7 FILEVERSION 1,0,3,6
PRODUCTVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
@@ -106,7 +106,7 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "FUCK THE UNIVERSE" VALUE "CompanyName", "FUCK THE UNIVERSE"
VALUE "FileDescription", "A GHOST" VALUE "FileDescription", "A GHOST"
VALUE "FileVersion", "1.0.3.7" VALUE "FileVersion", "1.0.3.6"
VALUE "InternalName", "ServerDll.dll" VALUE "InternalName", "ServerDll.dll"
VALUE "LegalCopyright", "Copyright (C) 2019-2026" VALUE "LegalCopyright", "Copyright (C) 2019-2026"
VALUE "OriginalFilename", "ServerDll.dll" VALUE "OriginalFilename", "ServerDll.dll"

Binary file not shown.

View File

@@ -78,7 +78,7 @@
<IntDir>$(Platform)\$(Configuration)\test</IntDir> <IntDir>$(Platform)\$(Configuration)\test</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(SolutionDir)..\SimpleRemoter;$(SolutionDir)compress;$(IncludePath)</IncludePath> <IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(SolutionDir)..\SimpleRemoter;$(IncludePath)</IncludePath>
<LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir)compress;$(LibraryPath)</LibraryPath> <LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir)compress;$(LibraryPath)</LibraryPath>
<IntDir>$(Configuration)\test</IntDir> <IntDir>$(Configuration)\test</IntDir>
</PropertyGroup> </PropertyGroup>
@@ -168,7 +168,6 @@
<ClCompile Include="MemoryModule.c" /> <ClCompile Include="MemoryModule.c" />
<ClCompile Include="reg_startup.c" /> <ClCompile Include="reg_startup.c" />
<ClCompile Include="ServiceWrapper.c" /> <ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="session.cpp" />
<ClCompile Include="SessionMonitor.c" /> <ClCompile Include="SessionMonitor.c" />
<ClCompile Include="test.cpp" /> <ClCompile Include="test.cpp" />
</ItemGroup> </ItemGroup>
@@ -178,7 +177,6 @@
<ClInclude Include="reg_startup.h" /> <ClInclude Include="reg_startup.h" />
<ClInclude Include="resource1.h" /> <ClInclude Include="resource1.h" />
<ClInclude Include="ServiceWrapper.h" /> <ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="session.h" />
<ClInclude Include="SessionMonitor.h" /> <ClInclude Include="SessionMonitor.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -201,7 +201,6 @@
<ClCompile Include="keylogger.cpp" /> <ClCompile Include="keylogger.cpp" />
<ClCompile Include="Loader.cpp" /> <ClCompile Include="Loader.cpp" />
<ClCompile Include="LoginServer.cpp" /> <ClCompile Include="LoginServer.cpp" />
<ClCompile Include="ClientLogManager.cpp" />
<ClCompile Include="Manager.cpp" /> <ClCompile Include="Manager.cpp" />
<ClCompile Include="MemoryModule.c" /> <ClCompile Include="MemoryModule.c" />
<ClCompile Include="proxy\ProxyManager.cpp" /> <ClCompile Include="proxy\ProxyManager.cpp" />
@@ -214,7 +213,6 @@
<ClCompile Include="ScreenSpy.cpp" /> <ClCompile Include="ScreenSpy.cpp" />
<ClCompile Include="ServicesManager.cpp" /> <ClCompile Include="ServicesManager.cpp" />
<ClCompile Include="ServiceWrapper.c" /> <ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="session.cpp" />
<ClCompile Include="SessionMonitor.c" /> <ClCompile Include="SessionMonitor.c" />
<ClCompile Include="ShellManager.cpp" /> <ClCompile Include="ShellManager.cpp" />
<ClCompile Include="ConPTYManager.cpp" /> <ClCompile Include="ConPTYManager.cpp" />
@@ -253,7 +251,6 @@
<ClInclude Include="keylogger.h" /> <ClInclude Include="keylogger.h" />
<ClInclude Include="LoginServer.h" /> <ClInclude Include="LoginServer.h" />
<ClInclude Include="Manager.h" /> <ClInclude Include="Manager.h" />
<ClInclude Include="ClientLogManager.h" />
<ClInclude Include="MemoryModule.h" /> <ClInclude Include="MemoryModule.h" />
<ClInclude Include="my_clip.h" /> <ClInclude Include="my_clip.h" />
<ClInclude Include="proxy\ProxyManager.h" /> <ClInclude Include="proxy\ProxyManager.h" />
@@ -268,7 +265,6 @@
<ClInclude Include="ScreenSpy.h" /> <ClInclude Include="ScreenSpy.h" />
<ClInclude Include="ServicesManager.h" /> <ClInclude Include="ServicesManager.h" />
<ClInclude Include="ServiceWrapper.h" /> <ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="session.h" />
<ClInclude Include="SessionMonitor.h" /> <ClInclude Include="SessionMonitor.h" />
<ClInclude Include="ShellManager.h" /> <ClInclude Include="ShellManager.h" />
<ClInclude Include="ConPTYManager.h" /> <ClInclude Include="ConPTYManager.h" />

View File

@@ -30,7 +30,7 @@ inline void ConvertCharToWChar(const char* charStr, wchar_t* wcharStr, size_t wc
MultiByteToWideChar(CP_ACP, 0, charStr, -1, wcharStr, wcharSize); MultiByteToWideChar(CP_ACP, 0, charStr, -1, wcharStr, wcharSize);
} }
int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,const char* desc,BOOL run, BOOL runasAdmin, BOOL systemBoot) int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,const char* desc,BOOL run, BOOL runasAdmin)
{ {
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr)) { if (FAILED(hr)) {
@@ -139,11 +139,11 @@ int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,cons
hr = pTask->lpVtbl->get_Triggers(pTask, &pTriggerCollection); hr = pTask->lpVtbl->get_Triggers(pTask, &pTriggerCollection);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
ITrigger* pTrigger = NULL; ITrigger* pTrigger = NULL;
hr = pTriggerCollection->lpVtbl->Create(pTriggerCollection, systemBoot ? TASK_TRIGGER_BOOT : TASK_TRIGGER_LOGON, &pTrigger); hr = pTriggerCollection->lpVtbl->Create(pTriggerCollection, TASK_TRIGGER_LOGON, &pTrigger);
pTriggerCollection->lpVtbl->Release(pTriggerCollection); pTriggerCollection->lpVtbl->Release(pTriggerCollection);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
// 普通用户需要指定具体用户 // 普通用户需要指定具体用户
if (!systemBoot && !runasAdmin) { if (!runasAdmin) {
ILogonTrigger* pLogonTrigger = NULL; ILogonTrigger* pLogonTrigger = NULL;
hr = pTrigger->lpVtbl->QueryInterface(pTrigger, &IID_ILogonTrigger, (void**)&pLogonTrigger); hr = pTrigger->lpVtbl->QueryInterface(pTrigger, &IID_ILogonTrigger, (void**)&pLogonTrigger);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -200,18 +200,10 @@ int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,cons
// 权限配置 // 权限配置
IPrincipal* pPrincipal = NULL; IPrincipal* pPrincipal = NULL;
if ((runasAdmin || systemBoot) && SUCCEEDED(pTask->lpVtbl->get_Principal(pTask, &pPrincipal))) { if (runasAdmin && SUCCEEDED(pTask->lpVtbl->get_Principal(pTask, &pPrincipal))) {
hr = pPrincipal->lpVtbl->put_LogonType(pPrincipal, systemBoot ? TASK_LOGON_SERVICE_ACCOUNT : TASK_LOGON_INTERACTIVE_TOKEN); hr = pPrincipal->lpVtbl->put_LogonType(pPrincipal, TASK_LOGON_INTERACTIVE_TOKEN);
if (FAILED(hr)) Mprintf("put_LogonType 失败,错误代码:%ld\n", hr); if (FAILED(hr)) Mprintf("put_LogonType 失败,错误代码:%ld\n", hr);
if (systemBoot) hr = pPrincipal->lpVtbl->put_RunLevel(pPrincipal, runasAdmin ? TASK_RUNLEVEL_HIGHEST : TASK_RUNLEVEL_LUA);
{
BSTR sys = SysAllocString(L"SYSTEM");
hr = pPrincipal->lpVtbl->put_UserId(pPrincipal,sys);
if (FAILED(hr))
Mprintf("put_UserId失败:%ld\n", hr);
SysFreeString(sys);
}
hr = pPrincipal->lpVtbl->put_RunLevel(pPrincipal, (runasAdmin || systemBoot) ? TASK_RUNLEVEL_HIGHEST : TASK_RUNLEVEL_LUA);
if (FAILED(hr)) Mprintf("put_RunLevel 失败,错误代码:%ld\n", hr); if (FAILED(hr)) Mprintf("put_RunLevel 失败,错误代码:%ld\n", hr);
pPrincipal->lpVtbl->Release(pPrincipal); pPrincipal->lpVtbl->Release(pPrincipal);
} else { } else {
@@ -243,9 +235,9 @@ int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,cons
bstrTaskName, bstrTaskName,
pTask, pTask,
TASK_CREATE_OR_UPDATE, TASK_CREATE_OR_UPDATE,
systemBoot ? empty : (runasAdmin ? vUser : empty), runasAdmin ? vUser : empty,
empty, empty,
systemBoot ? TASK_LOGON_SERVICE_ACCOUNT : TASK_LOGON_INTERACTIVE_TOKEN, TASK_LOGON_INTERACTIVE_TOKEN,
empty, empty,
&pRegisteredTask &pRegisteredTask
); );
@@ -297,40 +289,6 @@ BOOL IsRunningAsAdmin()
return isAdmin; return isAdmin;
} }
BOOL IsSystem()
{
HANDLE token = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
return FALSE;
DWORD size = 0;
GetTokenInformation(token, TokenUser, NULL, 0, &size);
PTOKEN_USER user = (PTOKEN_USER)malloc(size);
if (!user) { CloseHandle(token); return FALSE; }
BOOL result = FALSE;
if (GetTokenInformation(token, TokenUser, user, size, &size))
{
SID_IDENTIFIER_AUTHORITY nt = SECURITY_NT_AUTHORITY;
PSID systemSid = NULL;
AllocateAndInitializeSid(&nt, 1,
SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0,
&systemSid);
result = EqualSid(user->User.Sid, systemSid);
FreeSid(systemSid);
}
free(user);
CloseHandle(token);
return result;
}
BOOL LaunchAsAdmin(const char* szFilePath, const char* verb) BOOL LaunchAsAdmin(const char* szFilePath, const char* verb)
{ {
SHELLEXECUTEINFOA shExecInfo; SHELLEXECUTEINFOA shExecInfo;
@@ -390,7 +348,7 @@ const char* GetInstallDirectory(const char * startupName)
return folder; return folder;
} }
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, int runasAdmin, StartupLogFunc log) int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, bool runasAdmin, StartupLogFunc log)
{ {
#ifdef _DEBUG #ifdef _DEBUG
return 1; return 1;
@@ -410,12 +368,8 @@ int RegisterStartup(const char* startupName, const char* exeName, bool lockFile,
char dstFile[MAX_PATH] = { 0 }; char dstFile[MAX_PATH] = { 0 };
sprintf(dstFile, "%s\\%s.exe", folder, exeName); sprintf(dstFile, "%s\\%s.exe", folder, exeName);
BOOL isAdmin = IsRunningAsAdmin() || IsSystem(); BOOL isAdmin = IsRunningAsAdmin();
bool bootRun = false; if (isAdmin) runasAdmin = true;
if (isAdmin) {
bootRun = runasAdmin == 2;
runasAdmin = true;
}
if (_stricmp(curFile, dstFile) != 0) { if (_stricmp(curFile, dstFile) != 0) {
if (!isAdmin) { if (!isAdmin) {
if (runasAdmin) { if (runasAdmin) {
@@ -435,7 +389,7 @@ int RegisterStartup(const char* startupName, const char* exeName, bool lockFile,
Mprintf("Copy '%s' -> '%s': %s [Code: %d].\n", Mprintf("Copy '%s' -> '%s': %s [Code: %d].\n",
curFile, dstFile, b ? "succeed" : "failed", GetLastError()); curFile, dstFile, b ? "succeed" : "failed", GetLastError());
int status = CreateScheduledTask(startupName, dstFile, FALSE, NULL, TRUE, runasAdmin, bootRun); int status = CreateScheduledTask(startupName, dstFile, FALSE, NULL, TRUE, runasAdmin);
Mprintf("任务计划创建: %s!\n", status == 0 ? "成功" : "失败"); Mprintf("任务计划创建: %s!\n", status == 0 ? "成功" : "失败");
if (b && status) { if (b && status) {
int ret = (int)ShellExecuteA(NULL, "open", dstFile, NULL, NULL, SW_HIDE); int ret = (int)ShellExecuteA(NULL, "open", dstFile, NULL, NULL, SW_HIDE);
@@ -444,7 +398,7 @@ int RegisterStartup(const char* startupName, const char* exeName, bool lockFile,
return 0; return 0;
} }
int status = CreateScheduledTask(startupName, dstFile, TRUE, NULL, FALSE, runasAdmin, bootRun); int status = CreateScheduledTask(startupName, dstFile, TRUE, NULL, FALSE, runasAdmin);
Mprintf("任务计划创建: %s!\n", status == 0 ? "成功" : "失败"); Mprintf("任务计划创建: %s!\n", status == 0 ? "成功" : "失败");
if (lockFile) if (lockFile)
CreateFileA(curFile, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); CreateFileA(curFile, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

View File

@@ -6,8 +6,7 @@ const char* GetInstallDirectory(const char* startupName);
typedef void (*StartupLogFunc)(const char* file, int line, const char* format, ...); typedef void (*StartupLogFunc)(const char* file, int line, const char* format, ...);
// return > 0 means to continue running else terminate. // return > 0 means to continue running else terminate.
// runasAdmin: 0-普通权限 1-管理员 2-STYSTEM; 必须有管理员权限才能设置1或2 int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, bool runasAdmin, StartupLogFunc log);
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, int runasAdmin, StartupLogFunc log);
// 检测当前进程是否以SYSTEM身份运行在Session 0, 返回true表示需要启动用户Session代理 // 检测当前进程是否以SYSTEM身份运行在Session 0, 返回true表示需要启动用户Session代理
bool IsSystemInSession0(); bool IsSystemInSession0();

View File

@@ -1,176 +0,0 @@
#include "session.h"
#include <wtsapi32.h>
#include <userenv.h>
#include <wtsapi32.h>
#include <stdio.h>
#include "common/logger.h"
#pragma comment(lib, "Wtsapi32.lib")
#pragma comment(lib, "Userenv.lib")
#define SAFE_CLOSE_HANDLE(h) do{if((h)!=NULL&&(h)!=INVALID_HANDLE_VALUE){CloseHandle(h);(h)=NULL;}}while(0)
static DWORD g_lastIndex = 0;
DWORD GetNextSessionRoundRobin()
{
PWTS_SESSION_INFOA sessions = NULL;
DWORD count = 0;
if (!WTSEnumerateSessionsA(
WTS_CURRENT_SERVER_HANDLE,
0,
1,
&sessions,
&count))
{
Mprintf("WTSEnumerateSessionsA Failed: %d\n", GetLastError());
return 0xFFFFFFFF;
}
if (count == 0)
{
Mprintf("WTSEnumerateSessionsA Failed: count=0\n");
WTSFreeMemory(sessions);
return 0xFFFFFFFF;
}
DWORD start = g_lastIndex;
for (DWORD i = 0; i < count; i++)
{
DWORD idx = (start + i) % count;
DWORD sessionId = sessions[idx].SessionId;
// 过滤无效 session
if (sessionId == 0)
continue;
WTS_CONNECTSTATE_CLASS* state = NULL;
DWORD bytes = 0;
if (WTSQuerySessionInformationA(
WTS_CURRENT_SERVER_HANDLE,
sessionId,
WTSConnectState,
(LPSTR*)&state,
&bytes))
{
BOOL ok = (state && (
*state == WTSActive ||
*state == WTSConnected));
WTSFreeMemory(state);
if (ok)
{
g_lastIndex = (idx + 1) % count;
DWORD result = sessionId;
WTSFreeMemory(sessions);
Mprintf("GetNextSessionRoundRobin Succeed: session=%d\n", result);
return result;
}
}
}
WTSFreeMemory(sessions);
return 0xFFFFFFFF;
}
BOOL StartProcessInSessionA(DWORD sessionId, const char* exePath, BYTE cmd)
{
HANDLE hToken = NULL;
HANDLE hDupToken = NULL;
// 获取当前服务进程的 SYSTEM 令牌
char buf[500];
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
sprintf(buf, "OpenProcessToken failed: %d\n", (int)GetLastError());
Mprintf(buf);
return FALSE;
}
// 复制为可用于创建进程的主令牌
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
SecurityImpersonation, TokenPrimary, &hDupToken)) {
sprintf(buf, "DuplicateTokenEx failed: %d\n", (int)GetLastError());
Mprintf(buf);
SAFE_CLOSE_HANDLE(hToken);
return FALSE;
}
// 修改令牌的会话 ID 为目标用户会话
if (!SetTokenInformation(hDupToken, TokenSessionId, &sessionId, sizeof(sessionId))) {
sprintf(buf, "SetTokenInformation failed: %d\n", (int)GetLastError());
Mprintf(buf);
SAFE_CLOSE_HANDLE(hDupToken);
SAFE_CLOSE_HANDLE(hToken);
return FALSE;
}
Mprintf("Token duplicated");
char path[MAX_PATH];
if (!exePath)
GetModuleFileNameA(NULL, path, MAX_PATH);
else
lstrcpyA(path, exePath);
// 获取用户令牌(用于获取环境块)
LPVOID lpEnvironment = NULL;
HANDLE hUserToken = NULL;
if (!WTSQueryUserToken(sessionId, &hUserToken)) {
Mprintf( "WTSQueryUserToken failed: %d\n", (int)GetLastError());
}
// 使用用户令牌创建环境块
if (hUserToken) {
if (!CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE)) {
Mprintf("CreateEnvironmentBlock failed: %d\n", GetLastError());
}
CloseHandle(hUserToken);
}
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
si.cb = sizeof(si);
si.lpDesktop = (LPSTR)"winsta0\\default";
char cmdStr[300];
sprintf(cmdStr, "\"%s\" -cmd=%d", path, int(cmd));
BOOL result = CreateProcessAsUserA(
hDupToken,
NULL,
(LPSTR)cmdStr,
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment,
NULL,
&si,
&pi);
if (result)
{
Mprintf("CreateProcessAsUserA Succeed\n");
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
else {
Mprintf("CreateProcessAsUserA Failed [%d]: %s\n", GetLastError(), cmdStr);
}
CloseHandle(hDupToken);
CloseHandle(hToken);
return result;
}
void RunRoundRobinAgent(BYTE cmd)
{
DWORD sessionId = GetNextSessionRoundRobin();
if (sessionId == 0xFFFFFFFF)
return;
StartProcessInSessionA(sessionId, NULL, cmd);
}

View File

@@ -1,7 +0,0 @@
#include <windows.h>
DWORD GetNextSessionRoundRobin();
BOOL StartProcessInSessionA(DWORD sessionId, const char* exePath, BYTE cmd);
void RunRoundRobinAgent(BYTE cmd);

View File

@@ -8,7 +8,6 @@
#include <common/md5.h> #include <common/md5.h>
#include <common/iniFile.h> #include <common/iniFile.h>
#include "auto_start.h" #include "auto_start.h"
#include "Common.h"
// A shell code loader connect to 127.0.0.1:6543. // A shell code loader connect to 127.0.0.1:6543.
// Build: xxd -i TinyRun.dll > SCLoader.cpp // Build: xxd -i TinyRun.dll > SCLoader.cpp
// #include "SCLoader.cpp" // #include "SCLoader.cpp"
@@ -28,8 +27,6 @@ typedef bool (*IsStoped)();
typedef BOOL (*IsExit)(); typedef BOOL (*IsExit)();
typedef int(*CmdRunner)(LPBYTE szBuffer, int ulLength);
// 停止程序运行 // 停止程序运行
StopRun stop = NULL; StopRun stop = NULL;
@@ -63,7 +60,7 @@ BOOL CALLBACK callback(DWORD CtrlType)
} }
// 运行程序. // 运行程序.
BOOL Run(const char* argv1, int argv2, const std::string& runCmd); BOOL Run(const char* argv1, int argv2);
// Package header. // Package header.
typedef struct PkgHeader { typedef struct PkgHeader {
@@ -308,64 +305,60 @@ int InjectShellcode(BYTE* buf, int len) {
int main(int argc, const char *argv[]) int main(int argc, const char *argv[])
{ {
Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc > 1 ? argv[1] : "", argc); Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc > 1 ? argv[1] : "", argc);
bool runCmd = (argc > 1 && strncmp(argv[1], "-cmd=", 5) == 0), isService = false; InitWindowsService(NewService(
if (!runCmd) { g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemoService",
InitWindowsService(NewService( g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo Service",
g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemoService", g_ConnectAddress.installDesc[0] ? g_ConnectAddress.installDesc : "Provide a demo service."), Log);
g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo Service", bool isService = g_ConnectAddress.iStartup == Startup_TestRunMsc || IsSystemInSession0();
g_ConnectAddress.installDesc[0] ? g_ConnectAddress.installDesc : "Provide a demo service."), Log); // 注册启动项
isService = g_ConnectAddress.iStartup == Startup_TestRunMsc || (IsSystemInSession0()&&g_ConnectAddress.iStartup != Startup_TestRunSystem); int r = RegisterStartup(
bool lockFile = g_ConnectAddress.iStartup != Startup_TestRunMsc && g_ConnectAddress.iStartup != Startup_TestRunSystem && !IsSystemInSession0(); g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo",
// 注册启动项 g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemo",
int r = RegisterStartup( !isService, g_ConnectAddress.runasAdmin, Logf);
g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo", if (r <= 0) {
g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemo", if (g_ConnectAddress.iStartup == Startup_DLL) {
lockFile, g_ConnectAddress.iStartup == Startup_TestRunSystem ? 2 :g_ConnectAddress.runasAdmin, Logf); const char* folder = GetInstallDirectory(g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo");
if (r <= 0) { if (!folder) {
if (g_ConnectAddress.iStartup == Startup_DLL) { return -1;
const char* folder = GetInstallDirectory(g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo"); }
if (!folder) { char dstFile[MAX_PATH] = { 0 };
sprintf(dstFile, "%s\\ServerDll.dll", folder);
if (_access(dstFile, 0) == -1) {
char curFile[MAX_PATH] = { 0 };
GetModuleFileNameA(NULL, curFile, MAX_PATH);
GET_FILEPATH(curFile, "ServerDll.dll");
if (_access(curFile, 0) == -1) {
MessageBoxA(NULL, "ServerDll.dll is required to run this program.", "Missing ServerDll.dll", MB_ICONERROR);
return -1; return -1;
} }
char dstFile[MAX_PATH] = { 0 }; MoveFileA(curFile, dstFile);
sprintf(dstFile, "%s\\ServerDll.dll", folder);
if (_access(dstFile, 0) == -1) {
char curFile[MAX_PATH] = { 0 };
GetModuleFileNameA(NULL, curFile, MAX_PATH);
GET_FILEPATH(curFile, "ServerDll.dll");
if (_access(curFile, 0) == -1) {
MessageBoxA(NULL, "ServerDll.dll is required to run this program.", "Missing ServerDll.dll", MB_ICONERROR);
return -1;
}
MoveFileA(curFile, dstFile);
}
}
BOOL s = self_del();
if (!IsDebug) {
Mprintf("结束运行.\n");
Sleep(1000);
return r;
} }
} }
BOOL s = self_del();
if (!IsDebug) {
Mprintf("结束运行.\n");
Sleep(1000);
return r;
}
}
BOOL ok = SetSelfStart(argv[0], REG_NAME, Logf); BOOL ok = SetSelfStart(argv[0], REG_NAME, Logf);
if (!ok) { if(!ok) {
Mprintf("设置开机自启动失败,请用管理员权限运行.\n"); Mprintf("设置开机自启动失败,请用管理员权限运行.\n");
} }
if (isService) { if (isService) {
bool ret = RunAsWindowsService(argc, argv); bool ret = RunAsWindowsService(argc, argv);
Mprintf("RunAsWindowsService %s. Arg Count: %d\n", ret ? "succeed" : "failed", argc); Mprintf("RunAsWindowsService %s. Arg Count: %d\n", ret ? "succeed" : "failed", argc);
for (int i = 0; !ret && i < argc; i++) { for (int i = 0; !ret && i < argc; i++) {
Mprintf(" Arg [%d]: %s\n", i, argv[i]); Mprintf(" Arg [%d]: %s\n", i, argv[i]);
}
if (ret) {
Mprintf("结束运行.\n");
Sleep(1000);
return 0x20251202;
}
g_ConnectAddress.iStartup = Startup_MEMDLL;
} }
if (ret) {
Mprintf("结束运行.\n");
Sleep(1000);
return 0x20251202;
}
g_ConnectAddress.iStartup = Startup_MEMDLL;
} }
status = 0; status = 0;
@@ -383,8 +376,7 @@ int main(int argc, const char *argv[])
do { do {
BOOL ret = Run((argc > 1 && argv[1][0] != '-') ? // remark: demo may run with argument "-agent" BOOL ret = Run((argc > 1 && argv[1][0] != '-') ? // remark: demo may run with argument "-agent"
argv[1] : (strlen(g_ConnectAddress.ServerIP()) == 0 ? "127.0.0.1" : g_ConnectAddress.ServerIP()), argv[1] : (strlen(g_ConnectAddress.ServerIP()) == 0 ? "127.0.0.1" : g_ConnectAddress.ServerIP()),
argc > 2 ? atoi(argv[2]) : (g_ConnectAddress.ServerPort() == 0 ? 6543 : g_ConnectAddress.ServerPort()), argc > 2 ? atoi(argv[2]) : (g_ConnectAddress.ServerPort() == 0 ? 6543 : g_ConnectAddress.ServerPort()));
(argc > 1 && strncmp(argv[1], "-cmd=", 5) == 0 && strlen(argv[1]) > 5) ? std::string(argv[1] + 5) : "");
if (ret == 1) { if (ret == 1) {
Mprintf("结束运行.\n"); Mprintf("结束运行.\n");
Sleep(1000); Sleep(1000);
@@ -405,7 +397,7 @@ int main(int argc, const char *argv[])
} }
// 传入命令行参数: IP 和 端口. // 传入命令行参数: IP 和 端口.
BOOL Run(const char* argv1, int argv2, const std::string &runCmd) BOOL Run(const char* argv1, int argv2)
{ {
BOOL result = FALSE; BOOL result = FALSE;
char path[_MAX_PATH], * p = path; char path[_MAX_PATH], * p = path;
@@ -446,7 +438,7 @@ BOOL Run(const char* argv1, int argv2, const std::string &runCmd)
case Startup_DLL: case Startup_DLL:
runner = new DefaultDllRunner; runner = new DefaultDllRunner;
break; break;
case Startup_MEMDLL: case Startup_TestRunSystem : case Startup_MEMDLL:
runner = new MemoryDllRunner; runner = new MemoryDllRunner;
break; break;
case Startup_InjSC: case Startup_InjSC:
@@ -463,8 +455,6 @@ BOOL Run(const char* argv1, int argv2, const std::string &runCmd)
stop = hDll ? StopRun(runner->GetProcAddress(hDll, "StopRun")) : NULL; stop = hDll ? StopRun(runner->GetProcAddress(hDll, "StopRun")) : NULL;
bStop = hDll ? IsStoped(runner->GetProcAddress(hDll, "IsStoped")) : NULL; bStop = hDll ? IsStoped(runner->GetProcAddress(hDll, "IsStoped")) : NULL;
bExit = hDll ? IsExit(runner->GetProcAddress(hDll, "IsExit")) : NULL; bExit = hDll ? IsExit(runner->GetProcAddress(hDll, "IsExit")) : NULL;
CmdRunner cmd = hDll ? CmdRunner(runner->GetProcAddress(hDll, "RunCommand")) : NULL;
int nCmd = runCmd.empty() ? 0 : std::atoi(runCmd.c_str());
if (NULL == run) { if (NULL == run) {
if (hDll) runner->FreeLibrary(hDll); if (hDll) runner->FreeLibrary(hDll);
Mprintf("加载动态链接库\"ServerDll.dll\"失败. 错误代码: %d\n", GetLastError()); Mprintf("加载动态链接库\"ServerDll.dll\"失败. 错误代码: %d\n", GetLastError());
@@ -486,13 +476,6 @@ BOOL Run(const char* argv1, int argv2, const std::string &runCmd)
port = cfg.Get1Int("settings", "port", ';', 6543); port = cfg.Get1Int("settings", "port", ';', 6543);
} }
Mprintf("[server] %s:%d\n", ip, port); Mprintf("[server] %s:%d\n", ip, port);
if (nCmd) {
BYTE buf[] = {nCmd};
if (cmd) nCmd = cmd(buf, 1);
result = 1;
Mprintf("Finish run command. Result: %d\n", nCmd);
break;
}
do { do {
run(ip, port); run(ip, port);
while (bStop && !bStop() && 0 == status) while (bStop && !bStop() && 0 == status)

View File

@@ -312,12 +312,7 @@ enum {
TOKEN_DRAWING_BOARD=151, // 画板 TOKEN_DRAWING_BOARD=151, // 画板
COMMAND_SCREEN_ROI = 152, // 屏幕区域 COMMAND_SCREEN_ROI = 152, // 屏幕区域
COMMAND_SCREEN_WINDOW = 153, // 窗口捕获(标题字符串,空串=恢复全屏) COMMAND_SCREEN_WINDOW = 153, // 窗口捕获(标题字符串,空串=恢复全屏)
COMMAND_SCREEN_SIGNATURE = 154,
COMMAND_QUERY_LOG = 155,
TOKEN_REPORT_LOG = 156,
COMMAND_FORBIDDEN = 157,
CMD_CUSTOM_CURSOR = 158,
TOKEN_DECRYPT = 199, TOKEN_DECRYPT = 199,
TOKEN_REGEDIT = 200, // 注册表 TOKEN_REGEDIT = 200, // 注册表
COMMAND_REG_FIND, // 注册表 管理标识 COMMAND_REG_FIND, // 注册表 管理标识
@@ -358,11 +353,6 @@ enum {
}; };
#pragma pack(push, 1) #pragma pack(push, 1)
struct SignatureResp {
char msg[64];
char signature[64];
};
struct TextReplace { struct TextReplace {
uint8_t cmd; uint8_t cmd;
uint8_t type; uint8_t type;
@@ -746,8 +736,6 @@ enum TestRunType {
Startup_InjSC, // 远程 Shell code 注入其他程序执行shell code Startup_InjSC, // 远程 Shell code 注入其他程序执行shell code
Startup_GhostMsc, // Windows 服务 Startup_GhostMsc, // Windows 服务
Startup_TestRunMsc, // Windows 服务 Startup_TestRunMsc, // Windows 服务
Startup_GhostSystem, // SYSTEM 权限运行(随开机启动)
Startup_TestRunSystem, // SYSTEM 权限运行(随开机启动)
}; };
inline int MemoryFind(const char* szBuffer, const char* Key, int iBufferSize, int iKeySize) inline int MemoryFind(const char* szBuffer, const char* Key, int iBufferSize, int iKeySize)
@@ -1110,7 +1098,6 @@ enum AuthStatus {
UNAUTHORIZED = 0, // 未授权 UNAUTHORIZED = 0, // 未授权
AUTHED_BY_SUPER = 1, // 由超级管理员授权 AUTHED_BY_SUPER = 1, // 由超级管理员授权
AUTHED_BY_ADMIN = 2, // 由管理员授权 AUTHED_BY_ADMIN = 2, // 由管理员授权
AUTH_FORBIDDEN = 9,
}; };
// 固定1024字节 // 固定1024字节
@@ -1306,8 +1293,7 @@ typedef struct ScreenSettings {
int ScreenType; // 偏移 40, 屏幕类型(0: GDI, 1: DXGI, 2: Virtual) int ScreenType; // 偏移 40, 屏幕类型(0: GDI, 1: DXGI, 2: Virtual)
int AudioEnabled; // 偏移 44, 音频传输(0: 禁用, 1: 启用) int AudioEnabled; // 偏移 44, 音频传输(0: 禁用, 1: 启用)
int EncodeLevel; // 偏移 48, 编码等级 int EncodeLevel; // 偏移 48, 编码等级
int CustomCursor; // 偏移 52, 自定义光标 char Reserved[44]; // 偏移 52, 保留字段(新能力参数从此处扩展)
char Reserved[40]; // 偏移 56, 保留字段(新能力参数从此处扩展)
uint32_t Capabilities; // 偏移 96, 能力位标志(放最后) uint32_t Capabilities; // 偏移 96, 能力位标志(放最后)
} ScreenSettings; // 总大小 100 字节 } ScreenSettings; // 总大小 100 字节

View File

@@ -1,17 +0,0 @@
#include "logger.h"
#if defined(__ANDROID__)
static void (*androidLog)(const char*) = nullptr;
void UseAndroidLog(void (*cb)(const char*)) {
androidLog = cb;
}
__attribute__((format(printf, 1, 2)))
void android_log(const char* fmt, ...) {
char buf[256];
va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap);
__android_log_print(ANDROID_LOG_DEBUG, "YAMA_NET", "%s", buf);
if (androidLog) androidLog(buf);
}
#endif

View File

@@ -29,7 +29,6 @@
#include <cstdarg> #include <cstdarg>
#include <iomanip> #include <iomanip>
#include <algorithm> #include <algorithm>
#include <deque>
inline bool stringToBool(const std::string& str) inline bool stringToBool(const std::string& str)
@@ -151,16 +150,6 @@ public:
std::string logEntry = file && line ? std::string logEntry = file && line ?
id + "[" + timestamp + "] [" + file + ":" + std::to_string(line) + "] " + message: id + "[" + timestamp + "] [" + file + ":" + std::to_string(line) + "] " + message:
id + "[" + timestamp + "] " + message; id + "[" + timestamp + "] " + message;
// Always record to in-memory ring buffer regardless of enable flag.
{
std::lock_guard<std::mutex> lock(m_memMutex);
m_memLog.push_back(logEntry);
m_totalCount++;
if (m_memLog.size() > kMemLogMax)
m_memLog.pop_front();
}
if (enable) { if (enable) {
if (running) { if (running) {
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(queueMutex);
@@ -179,32 +168,6 @@ public:
cv.notify_one(); // 通知写线程 cv.notify_one(); // 通知写线程
} }
// 返回内存 ring buffer 中的全部日志(供 TCP 查询使用)
std::string DumpMemoryLog() const
{
std::lock_guard<std::mutex> lock(m_memMutex);
std::string result;
result.reserve(m_memLog.size() * 128);
for (const auto& entry : m_memLog)
result += entry;
return result;
}
// 增量版本:只返回 fromAbs 之后的新条目,并将 fromAbs 更新到当前末尾。
// fromAbs 是调用方维护的绝对计数器(首次传 0 即得全量)。
std::string DumpMemoryLogFrom(size_t& fromAbs) const
{
std::lock_guard<std::mutex> lock(m_memMutex);
// ring buffer 内最旧条目的绝对序号
size_t oldest = (m_totalCount >= m_memLog.size()) ? m_totalCount - m_memLog.size() : 0;
size_t startIdx = (fromAbs <= oldest) ? 0 : (fromAbs - oldest);
std::string result;
for (size_t i = startIdx; i < m_memLog.size(); ++i)
result += m_memLog[i];
fromAbs = m_totalCount;
return result;
}
// 停止日志系统 // 停止日志系统
void stop() void stop()
{ {
@@ -257,11 +220,6 @@ private:
std::mutex fileMutex; // 文件写入锁 std::mutex fileMutex; // 文件写入锁
std::string pid; // 进程ID std::string pid; // 进程ID
static constexpr size_t kMemLogMax = 1000;
std::deque<std::string> m_memLog;
size_t m_totalCount = 0;
mutable std::mutex m_memMutex;
Logger() : enable(false), threadRun(false), running(true), workerThread(&Logger::processLogs, this) {} Logger() : enable(false), threadRun(false), running(true), workerThread(&Logger::processLogs, this) {}
~Logger() ~Logger()
@@ -378,15 +336,6 @@ inline const char* getFileName(const char* path)
#else #else
#define Mprintf(format, ...) printf(format, ##__VA_ARGS__) #define Mprintf(format, ...) printf(format, ##__VA_ARGS__)
#endif #endif
#elif defined(__ANDROID__)
#include <android/log.h>
#include <cstdarg>
#ifdef Mprintf
#undef Mprintf
#endif
void UseAndroidLog(void (*cb)(const char*));
void android_log(const char* fmt, ...);
#define Mprintf android_log
#else #else
// Linux: 覆盖 commands.h 中的 printf 回退定义,改用 Logger 写文件 // Linux: 覆盖 commands.h 中的 printf 回退定义,改用 Logger 写文件
#ifdef Mprintf #ifdef Mprintf

View File

@@ -47,4 +47,4 @@ struct RttEstimator {
// 进程级全局:所有翻译单元共享同一份估算器与心跳间隔 // 进程级全局:所有翻译单元共享同一份估算器与心跳间隔
inline RttEstimator g_rttEstimator; inline RttEstimator g_rttEstimator;
inline int g_heartbeatInterval = 30; // 默认心跳间隔(秒),可被服务端 CMD_MASTERSETTING 更新 inline int g_heartbeatInterval = 5; // 默认心跳间隔(秒),可被服务端 CMD_MASTERSETTING 更新

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env bash
# build_zstd_linux.sh - 用官方源码编译 libzstd.a (x86_64 Linux)
# 产出linux/lib/libzstd.a (覆盖旧版本)
#
# 用法(在 WSL 或 Linux 下):
# cd linux && bash build_zstd_linux.sh
set -euo pipefail
cd "$(dirname "$0")"
ZSTD_VERSION="1.5.6"
ZSTD_TAR="zstd-${ZSTD_VERSION}.tar.gz"
ZSTD_URL="https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION}/${ZSTD_TAR}"
ZSTD_DIR="zstd-${ZSTD_VERSION}"
BUILD_DIR="zstd_build"
OUT_DIR="lib"
# --- 下载源码 ---
if [[ ! -d "$ZSTD_DIR" ]]; then
echo "Downloading zstd $ZSTD_VERSION..."
wget -q "$ZSTD_URL" -O "$ZSTD_TAR" || curl -sSL "$ZSTD_URL" -o "$ZSTD_TAR"
tar xf "$ZSTD_TAR"
rm "$ZSTD_TAR"
fi
# --- CMake 构建 ---
rm -rf "$BUILD_DIR"
cmake \
-B "$BUILD_DIR" -S "$ZSTD_DIR/build/cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_FLAGS="-O2 -g0" \
-DZSTD_BUILD_STATIC=ON \
-DZSTD_BUILD_SHARED=OFF \
-DZSTD_BUILD_PROGRAMS=OFF \
-DZSTD_BUILD_TESTS=OFF \
-DZSTD_LEGACY_SUPPORT=0
cmake --build "$BUILD_DIR" --config Release -j"$(nproc)"
# --- 拷贝产物 ---
mkdir -p "$OUT_DIR"
cp "$BUILD_DIR/lib/libzstd.a" "$OUT_DIR/libzstd.a"
strip --strip-debug "$OUT_DIR/libzstd.a" 2>/dev/null || true
ls -lh "$OUT_DIR/libzstd.a"
rm -rf "$BUILD_DIR" "$ZSTD_DIR"
echo
echo "===== Done ====="
echo "libzstd.a 已写入 linux/lib/libzstd.a"

View File

@@ -478,14 +478,6 @@ BOOL CMy2015RemoteApp::InitInstance()
Mprintf("[InitInstance] 处理自定义压缩/解压命令后退出。\n"); Mprintf("[InitInstance] 处理自定义压缩/解压命令后退出。\n");
return FALSE; return FALSE;
} }
iniFile cfg;
int bits = cfg.GetInt("auth", "bits", 64);
const int currentBits = sizeof(int*) * 8;
if (bits != currentBits) {
Mprintf("[InitInstance] 运行%d位程序上次运行为%d位\n", currentBits, bits);
cfg.SetInt("auth", "bits", currentBits);
cfg.SetStr("auth", "version", "");
}
BOOL runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0); BOOL runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
@@ -497,7 +489,6 @@ BOOL CMy2015RemoteApp::InitInstance()
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_PROTECTED, 0); THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_PROTECTED, 0);
// 确保是正常模式(服务端已设置,这里再次确保) // 确保是正常模式(服务端已设置,这里再次确保)
THIS_CFG.SetInt("settings", "RunNormal", 1); THIS_CFG.SetInt("settings", "RunNormal", 1);
THIS_CFG.SetStr("auth", "version", "");
runNormal = 1; runNormal = 1;
Mprintf("[InitInstance] 检测到代理崩溃保护标志,切换到正常运行模式。\n"); Mprintf("[InitInstance] 检测到代理崩溃保护标志,切换到正常运行模式。\n");
MessageBoxL("检测到代理程序连续崩溃,已自动切换到正常运行模式。\n\n" MessageBoxL("检测到代理程序连续崩溃,已自动切换到正常运行模式。\n\n"

Binary file not shown.

View File

@@ -14,7 +14,6 @@
#include "ShellDlg.h" #include "ShellDlg.h"
#include "TerminalDlg.h" #include "TerminalDlg.h"
#include "SystemDlg.h" #include "SystemDlg.h"
#include "CClientLog.h"
#include "BuildDlg.h" #include "BuildDlg.h"
#include "AudioDlg.h" #include "AudioDlg.h"
#include "RegisterDlg.h" #include "RegisterDlg.h"
@@ -62,7 +61,6 @@
#include "common/file_upload.h" #include "common/file_upload.h"
#include "SplashDlg.h" #include "SplashDlg.h"
#include "SearchBarDlg.h" #include "SearchBarDlg.h"
#include "LogSearchBar.h"
#include <ServerServiceWrapper.h> #include <ServerServiceWrapper.h>
#include "CDlgFileSend.h" #include "CDlgFileSend.h"
#include "CClientListDlg.h" #include "CClientListDlg.h"
@@ -141,16 +139,15 @@ COLUMNSTRUCT g_Column_Data_Online[g_Column_Count_Online] = {
{"预览", 60 }, {"预览", 60 },
{"IP", 130 }, {"IP", 130 },
{"端口", 60 }, {"端口", 60 },
{"地理位置", 140 }, {"地理位置", 130 },
{"计算机名/备注", 140 }, {"计算机名/备注", 150 },
{"操作系统", 120 }, {"操作系统", 120 },
{"CPU", 80 }, {"CPU", 80 },
{"摄像头", 60 }, {"摄像头", 70 },
{"RTT", 60 }, {"RTT", 70 },
{"版本", 90 }, {"版本", 90 },
{"权限", 60 }, {"安装时间", 120 },
{"安装时间", 130 }, {"活动窗口", 140 },
{"活动窗口", 130 },
{"类型", 50 }, {"类型", 50 },
}; };
@@ -645,9 +642,6 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl
m_bmOnline[57].LoadBitmap(IDB_BITMAP_UNINSTALL); m_bmOnline[57].LoadBitmap(IDB_BITMAP_UNINSTALL);
m_bmOnline[58].LoadBitmap(IDB_BITMAP_COPY); m_bmOnline[58].LoadBitmap(IDB_BITMAP_COPY);
m_bmOnline[59].LoadBitmap(IDB_BITMAP_ACTIVE_WND); m_bmOnline[59].LoadBitmap(IDB_BITMAP_ACTIVE_WND);
m_bmOnline[60].LoadBitmap(IDB_BITMAP_DEBUG);
m_bmOnline[61].LoadBitmap(IDB_BITMAP_CLIENTLOG);
m_bmOnline[62].LoadBitmap(IDB_BITMAP_FORBIDDEN);
for (int i = 0; i < PAYLOAD_MAXTYPE; i++) { for (int i = 0; i < PAYLOAD_MAXTYPE; i++) {
m_ServerDLL[i] = nullptr; m_ServerDLL[i] = nullptr;
m_ServerBin[i] = nullptr; m_ServerBin[i] = nullptr;
@@ -690,10 +684,6 @@ CMy2015RemoteDlg::~CMy2015RemoteDlg()
m_pSearchBar->DestroyWindow(); m_pSearchBar->DestroyWindow();
SAFE_DELETE(m_pSearchBar); SAFE_DELETE(m_pSearchBar);
} }
if (m_pLogSearchBar) {
m_pLogSearchBar->DestroyWindow();
SAFE_DELETE(m_pLogSearchBar);
}
} }
// DLL 请求限流成员函数实现 (根据配置限制请求频率) // DLL 请求限流成员函数实现 (根据配置限制请求频率)
@@ -902,7 +892,6 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_MESSAGE(WM_INJECT_SHELLCODE, InjectShellcode) ON_MESSAGE(WM_INJECT_SHELLCODE, InjectShellcode)
ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen) ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen)
ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen) ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen)
ON_MESSAGE(WM_OPENCLIENTLOGDIALOG, OnOpenClientLogDialog)
ON_MESSAGE(WM_SHARE_CLIENT, ShareClient) ON_MESSAGE(WM_SHARE_CLIENT, ShareClient)
ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient) ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient)
ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient) ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient)
@@ -937,7 +926,6 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete) ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete)
ON_COMMAND(ID_MSGLOG_COPY, &CMy2015RemoteDlg::OnMsglogCopy) ON_COMMAND(ID_MSGLOG_COPY, &CMy2015RemoteDlg::OnMsglogCopy)
ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear) ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear)
ON_COMMAND(ID_MSGLOG_SEARCH, &CMy2015RemoteDlg::OnMsglogSearch)
ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch) ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch)
ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify) ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify)
ON_NOTIFY(NM_CUSTOMDRAW, IDC_ONLINE, &CMy2015RemoteDlg::OnNMCustomdrawOnline) ON_NOTIFY(NM_CUSTOMDRAW, IDC_ONLINE, &CMy2015RemoteDlg::OnNMCustomdrawOnline)
@@ -1007,10 +995,6 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_MESSAGE(WM_SPLITTER_RELEASED, &CMy2015RemoteDlg::OnSplitterReleased) ON_MESSAGE(WM_SPLITTER_RELEASED, &CMy2015RemoteDlg::OnSplitterReleased)
ON_COMMAND(ID_COPY_CLIENT_INFO, &CMy2015RemoteDlg::OnCopyClientInfo) ON_COMMAND(ID_COPY_CLIENT_INFO, &CMy2015RemoteDlg::OnCopyClientInfo)
ON_COMMAND(ID_ONLINE_ACTIVE_WND, &CMy2015RemoteDlg::OnOnlineActiveWnd) ON_COMMAND(ID_ONLINE_ACTIVE_WND, &CMy2015RemoteDlg::OnOnlineActiveWnd)
ON_COMMAND(ID_ONLINE_VIEW_WND, &CMy2015RemoteDlg::OnOnlineWindowManager)
ON_COMMAND(ID_ENABLE_DEV_DEBUG, &CMy2015RemoteDlg::OnEnableDevDebug)
ON_COMMAND(ID_ONLINE_CLIENT_LOG, &CMy2015RemoteDlg::OnOnlineClientLog)
ON_COMMAND(ID_ONLINE_FORBIDDEN, &CMy2015RemoteDlg::OnOnlineForbidden)
END_MESSAGE_MAP() END_MESSAGE_MAP()
@@ -1112,7 +1096,6 @@ VOID CMy2015RemoteDlg::CreateSolidMenu()
m_MainMenu.SetMenuItemBitmaps(ID_MASTER_TRAIL, MF_BYCOMMAND, &m_bmOnline[48], &m_bmOnline[48]); m_MainMenu.SetMenuItemBitmaps(ID_MASTER_TRAIL, MF_BYCOMMAND, &m_bmOnline[48], &m_bmOnline[48]);
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_REQUEST_AUTH, MF_BYCOMMAND, &m_bmOnline[49], &m_bmOnline[49]); m_MainMenu.SetMenuItemBitmaps(ID_TOOL_REQUEST_AUTH, MF_BYCOMMAND, &m_bmOnline[49], &m_bmOnline[49]);
m_MainMenu.SetMenuItemBitmaps(ID_UNINSTALL_SOFTWARE, MF_BYCOMMAND, &m_bmOnline[57], &m_bmOnline[57]); m_MainMenu.SetMenuItemBitmaps(ID_UNINSTALL_SOFTWARE, MF_BYCOMMAND, &m_bmOnline[57], &m_bmOnline[57]);
m_MainMenu.SetMenuItemBitmaps(ID_ENABLE_DEV_DEBUG, MF_BYCOMMAND, &m_bmOnline[60], &m_bmOnline[60]);
// ============================================================ // ============================================================
// UIBranding: 根据编译时配置隐藏菜单项 // UIBranding: 根据编译时配置隐藏菜单项
@@ -1435,9 +1418,6 @@ VOID CMy2015RemoteDlg::InitControl()
m_nSplitPos = max(60, min(m_nSplitPos, 600)); m_nSplitPos = max(60, min(m_nSplitPos, 600));
m_SplitterBar.Create(this); m_SplitterBar.Create(this);
m_pLogSearchBar = new CLogSearchBar();
m_pLogSearchBar->Create(this, &m_CList_Message);
// 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg // 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg
// 之后统一 Apply避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。 // 之后统一 Apply避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。
} }
@@ -1482,12 +1462,8 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
capStr = ver.Mid(dashPos + 1); capStr = ver.Mid(dashPos + 1);
} }
const std::string& privUser = v[RES_USERNAME];
const std::string& privAdmin = v[RES_ISADMIN];
CString privilegeStr = privUser == "SYSTEM" ? "SYSTEM" : privAdmin == "1" ? "Admin" : "User";
CString data[ONLINELIST_MAX] = { strIP, strAddr, "", strPCName, strOS, strCPU, strVideo, strPing, CString data[ONLINELIST_MAX] = { strIP, strAddr, "", strPCName, strOS, strCPU, strVideo, strPing,
verDisplay, privilegeStr, install, startTime, v[RES_CLIENT_TYPE].empty() ? "?" : v[RES_CLIENT_TYPE].c_str(), path, verDisplay, install, startTime, v[RES_CLIENT_TYPE].empty() ? "?" : v[RES_CLIENT_TYPE].c_str(), path,
v[RES_CLIENT_PUBIP].empty() ? strIP : v[RES_CLIENT_PUBIP].c_str(), startTime, capStr, v[RES_CLIENT_PUBIP].empty() ? strIP : v[RES_CLIENT_PUBIP].c_str(), startTime, capStr,
}; };
// 优先采用客户端自报的 ID新客户端用 V2 算法 = MachineGuid + 归一化路径, // 优先采用客户端自报的 ID新客户端用 V2 算法 = MachineGuid + 归一化路径,
@@ -1533,11 +1509,6 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
ContextObject->SetID(id); ContextObject->SetID(id);
ContextObject->SetGroup(groupName); ContextObject->SetGroup(groupName);
m_ClientMap->SaveClientMapData(ContextObject); m_ClientMap->SaveClientMapData(ContextObject);
int status = m_ClientMap->GetClientMapInteger(id, MAP_AUTH);
if (status == AUTH_FORBIDDEN) {
BYTE buf[32] = { COMMAND_FORBIDDEN };
ContextObject->Send2Client(buf, sizeof(buf));
}
EnterCriticalSection(&m_cs); EnterCriticalSection(&m_cs);
@@ -1611,8 +1582,7 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);
Mprintf("主机[%s]上线: %s[%s][%s]\n", v[RES_CLIENT_PUBIP].empty() ? strIP : v[RES_CLIENT_PUBIP].c_str(), Mprintf("主机[%s]上线: %s[%s][%s]\n", v[RES_CLIENT_PUBIP].empty() ? strIP : v[RES_CLIENT_PUBIP].c_str(),
std::to_string(id).c_str(), loc, groupName.c_str()); std::to_string(id).c_str(), loc, groupName.c_str());
BOOL ret = SendMasterSettings(ContextObject, copy); SendMasterSettings(ContextObject, copy);
Mprintf("向主机 [%s][%llu] 发送 MasterSettings: %s\n", strIP, id, ret ? "成功" : "失败");
if (m_needNotify && (GetTickCount() - g_StartTick > 30*1000)) if (m_needNotify && (GetTickCount() - g_StartTick > 30*1000))
PostMessageA(WM_SHOWNOTIFY, WPARAM(title), LPARAM(text)); PostMessageA(WM_SHOWNOTIFY, WPARAM(title), LPARAM(text));
else { else {
@@ -1679,23 +1649,6 @@ LRESULT CMy2015RemoteDlg::OnShowMessage(WPARAM wParam, LPARAM lParam)
// 消息日志最大条数,达到上限时删除最旧的记录 // 消息日志最大条数,达到上限时删除最旧的记录
#define MAX_MESSAGE_COUNT 1000 #define MAX_MESSAGE_COUNT 1000
namespace {
bool MsgHasKW(const CString& s, const CString* kw, int n) {
for (int i = 0; i < n; i++)
if (s.Find(kw[i]) >= 0) return true;
return false;
}
int MsgColorLevel(const CString& type, const CString& msg) {
const CString redKW[] = { _L("严禁"), _L("禁止") };
const CString orangeKW[] = { _L("失败"), _L("警告") };
const CString greenKW[] = { _L("成功"), _L("") };
if (MsgHasKW(type, redKW, _countof(redKW)) || MsgHasKW(msg, redKW, _countof(redKW))) return 1;
if (MsgHasKW(type, orangeKW, _countof(orangeKW)) || MsgHasKW(msg, orangeKW, _countof(orangeKW))) return 2;
if (MsgHasKW(msg, greenKW, _countof(greenKW))) return 3;
return 0;
}
}
VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg) VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
{ {
AUTO_TICK(200, ""); AUTO_TICK(200, "");
@@ -1708,10 +1661,9 @@ VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
m_CList_Message.DeleteItem(count - 1); m_CList_Message.DeleteItem(count - 1);
} }
m_CList_Message.InsertItem(0, strType); m_CList_Message.InsertItem(0, strType); //向控件中设置数据
m_CList_Message.SetItemText(0, 1, strTime); m_CList_Message.SetItemText(0,1,strTime);
m_CList_Message.SetItemText(0, 2, strMsg); m_CList_Message.SetItemText(0,2,strMsg);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strType, strMsg));
CString strStatusMsg; CString strStatusMsg;
@@ -1786,12 +1738,9 @@ LRESULT CMy2015RemoteDlg::OnShowErrMessage(WPARAM wParam, LPARAM lParam)
m_CList_Message.DeleteItem(count - 1); m_CList_Message.DeleteItem(count - 1);
} }
CString strTitle = title ? _L(*title) : _TR("操作错误"); m_CList_Message.InsertItem(0, title ? _L(*title) : _TR("操作错误"));
CString strText = text ? _L(*text) : _TR("内部错误");
m_CList_Message.InsertItem(0, strTitle);
m_CList_Message.SetItemText(0, 1, strTime); m_CList_Message.SetItemText(0, 1, strTime);
m_CList_Message.SetItemText(0, 2, strText); m_CList_Message.SetItemText(0, 2, text ? _L(*text) : _TR("内部错误"));
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strTitle, strText));
if(title)delete title; if(title)delete title;
if(text)delete text; if(text)delete text;
@@ -3255,8 +3204,7 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
lastType = nType; lastType = nType;
BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1; BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1;
const int SPLITTER_H = 6; const int SPLITTER_H = 6;
const int LOG_SEARCH_H = 26; // 日志搜索条高度
// 日志区有效高度 = m_nSplitPos不含分割条分割条紧贴日志区上方 // 日志区有效高度 = m_nSplitPos不含分割条分割条紧贴日志区上方
int splitPos = hideLog ? 0 : m_nSplitPos; int splitPos = hideLog ? 0 : m_nSplitPos;
@@ -3283,22 +3231,6 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
} }
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);
if (m_pLogSearchBar && m_pLogSearchBar->GetSafeHwnd()) {
if (hideLog || !m_bLogSearchVisible) {
m_pLogSearchBar->ShowWindow(SW_HIDE);
} else {
m_pLogSearchBar->ShowWindow(SW_SHOW);
// 右对齐,最多 380px左侧留出 180px 给分组信息
int barW = min(260, max(60, cx - 182));
int barX = cx - 2 - barW;
// 叠在在线列表底部右角:紧贴分割条上方,向上偏移一个条高
int barY = cy - 20 - splitPos - SPLITTER_H - LOG_SEARCH_H + 5;
m_pLogSearchBar->MoveWindow(barX, barY, barW, LOG_SEARCH_H);
// 保证搜索条在列表控件上层BringWindowToTop 仅影响 Z-order不影响焦点
m_pLogSearchBar->BringWindowToTop();
}
}
if (m_SplitterBar.m_hWnd != NULL) { if (m_SplitterBar.m_hWnd != NULL) {
if (hideLog) { if (hideLog) {
m_SplitterBar.ShowWindow(SW_HIDE); m_SplitterBar.ShowWindow(SW_HIDE);
@@ -4107,8 +4039,6 @@ void CMy2015RemoteDlg::OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult)
Menu.SetMenuItemBitmaps(ID_CANCEL_SHARE, MF_BYCOMMAND, &m_bmOnline[50], &m_bmOnline[50]); Menu.SetMenuItemBitmaps(ID_CANCEL_SHARE, MF_BYCOMMAND, &m_bmOnline[50], &m_bmOnline[50]);
Menu.SetMenuItemBitmaps(ID_COPY_CLIENT_INFO, MF_BYCOMMAND, &m_bmOnline[58], &m_bmOnline[58]); Menu.SetMenuItemBitmaps(ID_COPY_CLIENT_INFO, MF_BYCOMMAND, &m_bmOnline[58], &m_bmOnline[58]);
Menu.SetMenuItemBitmaps(ID_ONLINE_ACTIVE_WND, MF_BYCOMMAND, &m_bmOnline[59], &m_bmOnline[59]); Menu.SetMenuItemBitmaps(ID_ONLINE_ACTIVE_WND, MF_BYCOMMAND, &m_bmOnline[59], &m_bmOnline[59]);
Menu.SetMenuItemBitmaps(ID_ONLINE_CLIENT_LOG, MF_BYCOMMAND, &m_bmOnline[61], &m_bmOnline[61]);
Menu.SetMenuItemBitmaps(ID_ONLINE_FORBIDDEN, MF_BYCOMMAND, &m_bmOnline[62], &m_bmOnline[62]);
Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权")); Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权"));
@@ -5294,20 +5224,6 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
// 【L】主机上下线和授权 // 【L】主机上下线和授权
// 【x】对话框相关功能 // 【x】对话框相关功能
switch (cmd) { switch (cmd) {
case TOKEN_REPORT_LOG: {
std::string logText((char*)(szBuffer + 1), len > 1 ? len - 1 : 0);
if (!ContextObject->hDlg) {
// 对话框尚未打开:用 SendMessage 同步打开并显示初始全量日志
SendMessage(WM_OPENCLIENTLOGDIALOG,
(WPARAM)new std::string(logText),
(LPARAM)ContextObject);
} else {
// 对话框已在PostMessage 追加增量日志
static_cast<CClientLog*>(ContextObject->hDlg)->PostMessage(
WM_REPORT_CLIENT_LOG, 0, (LPARAM)new std::string(logText));
}
break;
}
case TOKEN_CLIENT_MSG: { case TOKEN_CLIENT_MSG: {
ClientMsg *msg =(ClientMsg*)ContextObject->InDeCompressedBuffer.GetBuffer(0); ClientMsg *msg =(ClientMsg*)ContextObject->InDeCompressedBuffer.GetBuffer(0);
PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title))); PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title)));
@@ -6916,13 +6832,13 @@ context* CMy2015RemoteDlg::FindHost(uint64_t id)
return FindHostNoLock(id); return FindHostNoLock(id);
} }
BOOL CMy2015RemoteDlg::SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m) void CMy2015RemoteDlg::SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m)
{ {
BYTE buf[sizeof(MasterSettings) + 1] = { CMD_MASTERSETTING }; BYTE buf[sizeof(MasterSettings) + 1] = { CMD_MASTERSETTING };
memcpy(buf+1, &m, sizeof(MasterSettings)); memcpy(buf+1, &m, sizeof(MasterSettings));
if (ctx) { if (ctx) {
return ctx->Send2Client(buf, sizeof(buf)); ctx->Send2Client(buf, sizeof(buf));
} else { } else {
EnterCriticalSection(&m_cs); EnterCriticalSection(&m_cs);
for (auto i = m_HostList.begin(); i != m_HostList.end(); ++i) { for (auto i = m_HostList.begin(); i != m_HostList.end(); ++i) {
@@ -6932,7 +6848,6 @@ BOOL CMy2015RemoteDlg::SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSetti
ContextObject->Send2Client(buf, sizeof(buf)); ContextObject->Send2Client(buf, sizeof(buf));
} }
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);
return TRUE;
} }
} }
@@ -9243,40 +9158,10 @@ void CMy2015RemoteDlg::OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult)
case CDDS_ITEMPREPAINT: { case CDDS_ITEMPREPAINT: {
int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec); int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);
int nLastRow = m_CList_Message.GetItemCount() - 1;
// 搜索命中行深蓝高亮(不依赖 CDIS_SELECTED直接读搜索栏当前命中行号 if (nRow == nLastRow && nLastRow >= 0) {
if (m_pLogSearchBar) { pLVCD->clrText = RGB(255, 0, 0);
int nHit = m_pLogSearchBar->GetHighlightedRow();
if (nHit >= 0 && nRow == nHit) {
pLVCD->clrTextBk = RGB(0, 80, 180);
pLVCD->clrText = RGB(255, 255, 255);
*pResult = CDRF_NEWFONT;
return;
}
} }
// 关键字着色:必须同时设 clrText + clrTextBk否则系统预填的蓝色会泄漏
int colorLevel = (int)m_CList_Message.GetItemData(nRow);
COLORREF bkDefault = ::GetSysColor(COLOR_WINDOW);
if (colorLevel == 1) {
pLVCD->clrText = RGB(220, 0, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
if (colorLevel == 2) {
pLVCD->clrText = RGB(200, 100, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
if (colorLevel == 3) {
pLVCD->clrText = RGB(0, 140, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
break;
} }
} }
} }
@@ -9290,9 +9175,6 @@ void CMy2015RemoteDlg::OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult)
menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中")); menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_COPY, _TR("复制选中")); menu.AppendMenu(MF_STRING, ID_MSGLOG_COPY, _TR("复制选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志")); menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志"));
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_STRING, ID_MSGLOG_SEARCH,
m_bLogSearchVisible ? _TR("隐藏搜索") : _TR("搜索日志"));
// 没有选中项时禁用"删除选中" // 没有选中项时禁用"删除选中"
if (m_CList_Message.GetSelectedCount() == 0) { if (m_CList_Message.GetSelectedCount() == 0) {
@@ -9317,7 +9199,7 @@ void CMy2015RemoteDlg::OnMsglogDelete()
while (pos) { while (pos) {
selected.push_back(m_CList_Message.GetNextSelectedItem(pos)); selected.push_back(m_CList_Message.GetNextSelectedItem(pos));
} }
// 倒序删除,避免索引变化 // 倒序删除
for (auto it = selected.rbegin(); it != selected.rend(); ++it) { for (auto it = selected.rbegin(); it != selected.rend(); ++it) {
m_CList_Message.DeleteItem(*it); m_CList_Message.DeleteItem(*it);
} }
@@ -9360,14 +9242,6 @@ void CMy2015RemoteDlg::OnMsglogClear()
m_CList_Message.DeleteAllItems(); m_CList_Message.DeleteAllItems();
} }
void CMy2015RemoteDlg::OnMsglogSearch()
{
m_bLogSearchVisible = !m_bLogSearchVisible;
// 复用 OnSize 里的布局逻辑
CRect rc;
GetClientRect(&rc);
OnSize(SIZE_RESTORED, rc.Width(), rc.Height());
}
void CMy2015RemoteDlg::OnOnlineAddWatch() void CMy2015RemoteDlg::OnOnlineAddWatch()
{ {
@@ -10607,50 +10481,41 @@ void CMy2015RemoteDlg::OnParamEnableLog()
void CMy2015RemoteDlg::OnParamPrivacyWallpaper() void CMy2015RemoteDlg::OnParamPrivacyWallpaper()
{ {
auto SubMenu = m_MainMenu.GetSubMenu(2);
// 已设置壁纸:再次点击则清空
if (!m_PrivateScreenWallpaper.IsEmpty()) {
m_PrivateScreenWallpaper.Empty();
THIS_CFG.SetStr("settings", "Wallpaper", "");
if (SubMenu)
SubMenu->CheckMenuItem(ID_PARAM_PRIVACY_WALLPAPER, MF_UNCHECKED);
return;
}
// 未设置壁纸:弹出文件对话框
CFileDialog dlg(TRUE, _T("bmp"), NULL, CFileDialog dlg(TRUE, _T("bmp"), NULL,
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
_T("BMP Files (*.bmp)|*.bmp|All Files (*.*)|*.*||"), this); _T("BMP Files (*.bmp)|*.bmp|All Files (*.*)|*.*||"), this);
if (dlg.DoModal() != IDOK) if (dlg.DoModal() == IDOK) {
return; CString path = dlg.GetPathName();
CString path = dlg.GetPathName(); // 验证文件是否可以读取
CFile file;
if (!file.Open(path, CFile::modeRead | CFile::typeBinary)) {
MessageBox(_TR("无法读取指定的BMP文件请检查文件是否存在或权限是否正确。"),
_TR("错误"), MB_ICONERROR);
return;
}
CFile file; // 验证是否是有效的BMP文件
if (!file.Open(path, CFile::modeRead | CFile::typeBinary)) { BITMAPFILEHEADER bmfh;
MessageBox(_TR("无法读取指定的BMP文件请检查文件是否存在或权限是否正确。"), if (file.Read(&bmfh, sizeof(bmfh)) != sizeof(bmfh) || bmfh.bfType != 0x4D42) {
_TR("错误"), MB_ICONERROR); file.Close();
return; MessageBox(_TR("指定的文件不是有效的BMP格式。"),
} _TR("错误"), MB_ICONERROR);
return;
BITMAPFILEHEADER bmfh; }
if (file.Read(&bmfh, sizeof(bmfh)) != sizeof(bmfh) || bmfh.bfType != 0x4D42) {
file.Close(); file.Close();
MessageBox(_TR("指定的文件不是有效的BMP格式。"),
_TR("错误"), MB_ICONERROR);
return;
}
file.Close();
m_PrivateScreenWallpaper = path; m_PrivateScreenWallpaper = path;
THIS_CFG.SetStr("settings", "Wallpaper", path.GetString()); THIS_CFG.SetStr("settings", "Wallpaper", path.GetString());
if (SubMenu) auto SubMenu = m_MainMenu.GetSubMenu(2); // 工具菜单
SubMenu->CheckMenuItem(ID_PARAM_PRIVACY_WALLPAPER, MF_CHECKED); if (SubMenu) {
CString msg; SubMenu->CheckMenuItem(ID_PARAM_PRIVACY_WALLPAPER, MF_CHECKED);
msg.Format(_TR("隐私屏幕壁纸已设置为:\n%s"), path); }
MessageBox(msg, _TR("设置成功"), MB_ICONINFORMATION); CString msg;
msg.Format(_TR("隐私屏幕壁纸已设置为:\n%s"), path);
MessageBox(msg + CString("\n") + _L("如需恢复默认值,请从磁盘移除该文件。"), _TR("设置成功"), MB_ICONINFORMATION);
}
} }
void CMy2015RemoteDlg::OnParamFileV2() void CMy2015RemoteDlg::OnParamFileV2()
@@ -11585,101 +11450,3 @@ void CMy2015RemoteDlg::OnOnlineActiveWnd()
if (!ctx) return; if (!ctx) return;
PostCaptureForegroundWindow(ctx); PostCaptureForegroundWindow(ctx);
} }
bool SetUserEnvironmentVariable(const char* name, const char* value)
{
HKEY hKey = NULL;
LONG ret = RegOpenKeyExA(
HKEY_CURRENT_USER,
"Environment",
0,
KEY_SET_VALUE,
&hKey);
if (ret != ERROR_SUCCESS)
{
return false;
}
ret = RegSetValueExA(
hKey,
name,
0,
REG_SZ, // 或 REG_EXPAND_SZ
(const BYTE*)value,
(DWORD)(strlen(value) + 1));
RegCloseKey(hKey);
if (ret != ERROR_SUCCESS)
{
return false;
}
// 通知系统环境变量已更新
SendMessageTimeoutA(
HWND_BROADCAST,
WM_SETTINGCHANGE,
0,
(LPARAM)"Environment",
SMTO_ABORTIFHUNG,
5000,
NULL);
return true;
}
void CMy2015RemoteDlg::OnEnableDevDebug()
{
const char* debug = getenv("YAMA_DBG");
if (!debug || strlen(debug) == 0) {
MessageBoxL("您必须正确设置 YAMA_DBG 才能调试此程序!\r\n请联系管理员请求授权。",
"提示", MB_ICONINFORMATION);
CInputDialog dlg(this);
dlg.Init(_L("设置环境变量"), "YAMA_DBG:");
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty()) return;
SetUserEnvironmentVariable("YAMA_DBG", dlg.m_str) ?
MessageBoxL("设置成功! 请重启程序和调试器。", "提示", MB_ICONINFORMATION) :
MessageBoxL("设置失败! 通常是权限不足, 请手动在系统环境变量设置。", "提示", MB_ICONINFORMATION);
}
else {
MessageBoxA(_TR("您已设置 YAMA_DBG, 请确保环境变量正确无误!\r\n")+
"YAMA_DBG=" + CString(debug), _L("提示"), MB_ICONINFORMATION);
}
}
void CMy2015RemoteDlg::OnOnlineClientLog()
{
BYTE cmd = COMMAND_QUERY_LOG;
SendSelectedCommand(&cmd, sizeof(BYTE));
}
LRESULT CMy2015RemoteDlg::OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam)
{
std::string* pInitLog = reinterpret_cast<std::string*>(wParam);
std::string initLog = pInitLog ? *pInitLog : "";
delete pInitLog;
OpenDialog<CClientLog, IDD_DIALOG_CLIENT_LOG>(0, lParam);
CONTEXT_OBJECT* ctx = reinterpret_cast<CONTEXT_OBJECT*>(lParam);
if (ctx && ctx->hDlg && !initLog.empty())
static_cast<CClientLog*>(ctx->hDlg)->AppendLog(initLog);
return 0;
}
void forbiddenClient(context* ctx, void* user) {
_ClientList* m_ClientMap = (_ClientList*)user;
m_ClientMap->SetClientMapInteger(ctx->GetClientID(), MAP_AUTH, AUTH_FORBIDDEN);
}
void CMy2015RemoteDlg::OnOnlineForbidden()
{
if (IDYES != MessageBoxL(_T("确定封禁选定的被控程序吗?"), _T("提示"), MB_ICONQUESTION | MB_YESNO))
return;
BYTE buf[32] = { COMMAND_FORBIDDEN };
SendSelectedCommand(buf, sizeof(buf), forbiddenClient, m_ClientMap);
}

View File

@@ -75,7 +75,6 @@ class CSplashDlg; // 前向声明
class CClientListDlg; class CClientListDlg;
class CLicenseDlg; class CLicenseDlg;
class CSearchBarDlg; class CSearchBarDlg;
class CLogSearchBar;
#include "pwd_gen.h" #include "pwd_gen.h"
@@ -141,9 +140,7 @@ public:
_ClientList *m_ClientMap = nullptr; _ClientList *m_ClientMap = nullptr;
CClientListDlg* m_pClientListDlg = nullptr; CClientListDlg* m_pClientListDlg = nullptr;
CLicenseDlg* m_pLicenseDlg = nullptr; CLicenseDlg* m_pLicenseDlg = nullptr;
CSearchBarDlg* m_pSearchBar = nullptr; CSearchBarDlg* m_pSearchBar = nullptr; // 搜索工具栏
CLogSearchBar* m_pLogSearchBar = nullptr;
bool m_bLogSearchVisible = false; // 日志搜索条是否显示(右键菜单切换)
BOOL m_bEnableFileV2 = FALSE; // V2 文件传输开关 BOOL m_bEnableFileV2 = FALSE; // V2 文件传输开关
// 构造 // 构造
@@ -232,7 +229,7 @@ public:
std::tuple<bool, bool, bool, bool> VerifyClientAuth(context* host, const std::string& sn, std::tuple<bool, bool, bool, bool> VerifyClientAuth(context* host, const std::string& sn,
const std::string& passcode, uint64_t hmac, const std::string& hmacV2, const std::string& ip, const std::string& passcode, uint64_t hmac, const std::string& hmacV2, const std::string& ip,
const char* source = "AUTH"); const char* source = "AUTH");
BOOL SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m); void SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m);
void SendFilesToClientV2(context* mainCtx, const std::vector<std::string>& files, const std::string& targetDir = ""); void SendFilesToClientV2(context* mainCtx, const std::vector<std::string>& files, const std::string& targetDir = "");
void SendFilesToClientV2Internal(context* mainCtx, const std::vector<std::string>& files, void SendFilesToClientV2Internal(context* mainCtx, const std::vector<std::string>& files,
uint64_t resumeTransferID, const std::map<uint32_t, uint64_t>& startOffsets, const std::string& targetDir = ""); uint64_t resumeTransferID, const std::map<uint32_t, uint64_t>& startOffsets, const std::string& targetDir = "");
@@ -385,7 +382,7 @@ public:
bool IsDllRequestLimited(const std::string& ip); bool IsDllRequestLimited(const std::string& ip);
void RecordDllRequest(const std::string& ip); void RecordDllRequest(const std::string& ip);
CMenu m_MainMenu; CMenu m_MainMenu;
CBitmap m_bmOnline[63]; CBitmap m_bmOnline[60];
uint64_t m_superID; uint64_t m_superID;
std::map<HWND, CDialogBase *> m_RemoteWnds; std::map<HWND, CDialogBase *> m_RemoteWnds;
FileTransformCmd m_CmdList; FileTransformCmd m_CmdList;
@@ -563,7 +560,6 @@ public:
afx_msg void OnMsglogDelete(); afx_msg void OnMsglogDelete();
afx_msg void OnMsglogCopy(); afx_msg void OnMsglogCopy();
afx_msg void OnMsglogClear(); afx_msg void OnMsglogClear();
afx_msg void OnMsglogSearch();
afx_msg void OnOnlineAddWatch(); afx_msg void OnOnlineAddWatch();
afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnOnlineRunAsAdmin(); afx_msg void OnOnlineRunAsAdmin();
@@ -640,8 +636,4 @@ public:
afx_msg LRESULT OnSplitterReleased(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnSplitterReleased(WPARAM wParam, LPARAM lParam);
afx_msg void OnCopyClientInfo(); afx_msg void OnCopyClientInfo();
afx_msg void OnOnlineActiveWnd(); afx_msg void OnOnlineActiveWnd();
afx_msg void OnEnableDevDebug();
afx_msg void OnOnlineClientLog();
afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam);
afx_msg void OnOnlineForbidden();
}; };

View File

@@ -234,7 +234,6 @@
</ResourceCompile> </ResourceCompile>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\android\ghost.apk" />
<None Include="..\..\linux\ghost" /> <None Include="..\..\linux\ghost" />
<None Include="..\..\macos\ghost" /> <None Include="..\..\macos\ghost" />
<None Include="..\..\Release\ghost.exe" /> <None Include="..\..\Release\ghost.exe" />
@@ -290,8 +289,6 @@
<ClInclude Include="Buffer.h" /> <ClInclude Include="Buffer.h" />
<ClInclude Include="BuildDlg.h" /> <ClInclude Include="BuildDlg.h" />
<ClInclude Include="CClientListDlg.h" /> <ClInclude Include="CClientListDlg.h" />
<ClInclude Include="CClientLog.h" />
<ClInclude Include="LogSearchBar.h" />
<ClInclude Include="CDlgFileSend.h" /> <ClInclude Include="CDlgFileSend.h" />
<ClInclude Include="CDrawingBoard.h" /> <ClInclude Include="CDrawingBoard.h" />
<ClInclude Include="CGridDialog.h" /> <ClInclude Include="CGridDialog.h" />
@@ -373,8 +370,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\..\client\Audio.cpp" /> <ClCompile Include="..\..\client\Audio.cpp" />
<ClCompile Include="CClientLog.cpp" />
<ClCompile Include="LogSearchBar.cpp" />
<ClCompile Include="msvc_compat.c"> <ClCompile Include="msvc_compat.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
@@ -511,11 +506,8 @@
<Image Include="res\Bitmap\authorize.bmp" /> <Image Include="res\Bitmap\authorize.bmp" />
<Image Include="res\Bitmap\Backup.bmp" /> <Image Include="res\Bitmap\Backup.bmp" />
<Image Include="res\Bitmap\CancelShare.bmp" /> <Image Include="res\Bitmap\CancelShare.bmp" />
<Image Include="res\Bitmap\ClientLog.bmp" />
<Image Include="res\Bitmap\Forbidden.bmp" />
<Image Include="res\bitmap\compress.bmp" /> <Image Include="res\bitmap\compress.bmp" />
<Image Include="res\Bitmap\Copy.bmp" /> <Image Include="res\Bitmap\Copy.bmp" />
<Image Include="res\Bitmap\Debug.bmp" />
<Image Include="res\Bitmap\delete.bmp" /> <Image Include="res\Bitmap\delete.bmp" />
<Image Include="res\Bitmap\DxgiDesktop.bmp" /> <Image Include="res\Bitmap\DxgiDesktop.bmp" />
<Image Include="res\Bitmap\EditGroup.bmp" /> <Image Include="res\Bitmap\EditGroup.bmp" />
@@ -567,7 +559,6 @@
<Image Include="res\Bitmap\Wallet.bmp" /> <Image Include="res\Bitmap\Wallet.bmp" />
<Image Include="res\Bitmap\WebDesktop.bmp" /> <Image Include="res\Bitmap\WebDesktop.bmp" />
<Image Include="res\chat.ico" /> <Image Include="res\chat.ico" />
<Image Include="res\ClientLog.ico" />
<Image Include="res\cmdshell.ico" /> <Image Include="res\cmdshell.ico" />
<Image Include="res\decrypt.ico" /> <Image Include="res\decrypt.ico" />
<Image Include="res\DrawingBoard.ico" /> <Image Include="res\DrawingBoard.ico" />

View File

@@ -84,7 +84,6 @@
<ClCompile Include="PluginSettingsDlg.cpp" /> <ClCompile Include="PluginSettingsDlg.cpp" />
<ClCompile Include="PreviewTipWnd.cpp" /> <ClCompile Include="PreviewTipWnd.cpp" />
<ClCompile Include="TriggerSettingsDlg.cpp" /> <ClCompile Include="TriggerSettingsDlg.cpp" />
<ClCompile Include="CClientLog.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\..\client\Audio.h" /> <ClInclude Include="..\..\client\Audio.h" />
@@ -189,7 +188,6 @@
<ClInclude Include="PluginSettingsDlg.h" /> <ClInclude Include="PluginSettingsDlg.h" />
<ClInclude Include="PreviewTipWnd.h" /> <ClInclude Include="PreviewTipWnd.h" />
<ClInclude Include="TriggerSettingsDlg.h" /> <ClInclude Include="TriggerSettingsDlg.h" />
<ClInclude Include="CClientLog.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="2015Remote.rc" /> <ResourceCompile Include="2015Remote.rc" />
@@ -230,7 +228,6 @@
<None Include="..\..\linux\ghost" /> <None Include="..\..\linux\ghost" />
<None Include="res\3rd\TerminalModule_x64.dll" /> <None Include="res\3rd\TerminalModule_x64.dll" />
<None Include="..\..\macos\ghost" /> <None Include="..\..\macos\ghost" />
<None Include="..\..\android\ghost.apk" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Filter Include="file"> <Filter Include="file">
@@ -329,8 +326,5 @@
<Image Include="res\bitmap\uninstall.bmp" /> <Image Include="res\bitmap\uninstall.bmp" />
<Image Include="res\Bitmap\Copy.bmp" /> <Image Include="res\Bitmap\Copy.bmp" />
<Image Include="res\bitmap\ActiveWnd.bmp" /> <Image Include="res\bitmap\ActiveWnd.bmp" />
<Image Include="res\Bitmap\Debug.bmp" />
<Image Include="res\ClientLog.ico" />
<Image Include="res\Bitmap\ClientLog.bmp" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -29,9 +29,6 @@ enum Index {
IndexTestRunMsc, IndexTestRunMsc,
IndexLinuxGhost, IndexLinuxGhost,
IndexMacGhost, IndexMacGhost,
IndexAndroidGhost,
IndexGhostSystem,
IndexTestRunSystem,
OTHER_ITEM OTHER_ITEM
}; };
@@ -163,8 +160,7 @@ namespace ResFileName {
const char* GHOST_X86 = "ghost_x86.exe"; const char* GHOST_X86 = "ghost_x86.exe";
const char* GHOST_X64 = "ghost_x64.exe"; const char* GHOST_X64 = "ghost_x64.exe";
const char* GHOST_LINUX = "ghost_linux"; const char* GHOST_LINUX = "ghost_linux";
const char* GHOST_MACOS = "ghost_macos"; const char* GHOST_MACOS = "ghost_macos"; // 预留
const char* GHOST_ANDROID = "ghost_android";
// TestRun 加载器 // TestRun 加载器
const char* TESTRUN_X86 = "testrun_x86.dll"; const char* TESTRUN_X86 = "testrun_x86.dll";
const char* TESTRUN_X64 = "testrun_x64.dll"; const char* TESTRUN_X64 = "testrun_x64.dll";
@@ -422,7 +418,7 @@ void CBuildDlg::OnBnClickedOk()
MessageBoxL("Shellcode 只能向64位电脑注入注入器也只能是64位!", "提示", MB_ICONWARNING); MessageBoxL("Shellcode 只能向64位电脑注入注入器也只能是64位!", "提示", MB_ICONWARNING);
return; return;
} }
if (index == IndexLinuxGhost || index == IndexMacGhost || index == IndexAndroidGhost) { if (index == IndexLinuxGhost || index == IndexMacGhost) {
m_ComboCompress.SetCurSel(CLIENT_COMPRESS_NONE); m_ComboCompress.SetCurSel(CLIENT_COMPRESS_NONE);
m_SliderClientSize.SetPos(0); m_SliderClientSize.SetPos(0);
} }
@@ -489,28 +485,6 @@ void CBuildDlg::OnBnClickedOk()
typ = CLIENT_TYPE_MACOS; typ = CLIENT_TYPE_MACOS;
szBuffer = ReadResource(IDR_MACOS_GHOST, dwFileSize, ResFileName::GHOST_MACOS); szBuffer = ReadResource(IDR_MACOS_GHOST, dwFileSize, ResFileName::GHOST_MACOS);
break; break;
case IndexAndroidGhost: {
file = "ghost.apk";
typ = CLIENT_TYPE_ANDROID;
szBuffer = ReadResource(IDR_ANDROID_GHOST, dwFileSize, ResFileName::GHOST_ANDROID);
break;
}
case IndexGhostSystem:
file = "ghost.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
typ = CLIENT_TYPE_ONE;
startup = Startup_GhostSystem;
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize,
is64bit ? ResFileName::GHOST_X64 : ResFileName::GHOST_X86);
break;
case IndexTestRunSystem:
file = "TestRun.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir);
typ = CLIENT_TYPE_MEMDLL;
startup = Startup_TestRunSystem;
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize,
is64bit ? ResFileName::TESTRUN_X64 : ResFileName::TESTRUN_X86);
break;
case OTHER_ITEM: { case OTHER_ITEM: {
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "YamaDll" : m_sInstallDir); targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "YamaDll" : m_sInstallDir);
m_OtherItem.GetWindowTextA(file); m_OtherItem.GetWindowTextA(file);
@@ -745,14 +719,6 @@ void CBuildDlg::OnBnClickedOk()
successMsg += "\r\n"; successMsg += "\r\n";
successMsg += _TR("或手动重签:") + " codesign --force --sign - ghost"; successMsg += _TR("或手动重签:") + " codesign --force --sign - ghost";
} }
// Android APK 被 patch 后签名失效,必须用 apksigner 重签才能安装。
if (typ == CLIENT_TYPE_ANDROID) {
successMsg += "\r\n\r\n";
successMsg += _TR("提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。");
successMsg += "\r\n";
successMsg += _TR("重签命令 (需 Android SDK build-tools):");
successMsg += "\r\napksigner sign --ks yama-release.jks --ks-pass env:YAMA_PWD ghost.apk";
}
MessageBoxL(successMsg, "提示", MB_ICONINFORMATION); MessageBoxL(successMsg, "提示", MB_ICONINFORMATION);
} }
SAFE_DELETE_ARRAY(szBuffer); SAFE_DELETE_ARRAY(szBuffer);
@@ -818,9 +784,6 @@ BOOL CBuildDlg::OnInitDialog()
m_ComboExe.InsertStringL(IndexTestRunMsc, "TestRun - Windows 服务"); m_ComboExe.InsertStringL(IndexTestRunMsc, "TestRun - Windows 服务");
m_ComboExe.InsertStringL(IndexLinuxGhost, "ghost - Linux x64"); m_ComboExe.InsertStringL(IndexLinuxGhost, "ghost - Linux x64");
m_ComboExe.InsertStringL(IndexMacGhost, "ghost - Apple MacOS"); m_ComboExe.InsertStringL(IndexMacGhost, "ghost - Apple MacOS");
m_ComboExe.InsertStringL(IndexAndroidGhost, "ghost - Google Android");
m_ComboExe.InsertStringL(IndexGhostSystem, "ghost - SYSTEM");
m_ComboExe.InsertStringL(IndexTestRunSystem, "TestRun - SYSTEM");
m_ComboExe.InsertStringL(OTHER_ITEM, CString("选择文件")); m_ComboExe.InsertStringL(OTHER_ITEM, CString("选择文件"));
m_ComboExe.SetCurSel(IndexTestRun_MemDLL); m_ComboExe.SetCurSel(IndexTestRun_MemDLL);
@@ -949,7 +912,7 @@ void CBuildDlg::EnableWindowsOnlyControls(BOOL enable)
void CBuildDlg::OnCbnSelchangeComboExe() void CBuildDlg::OnCbnSelchangeComboExe()
{ {
auto n = m_ComboExe.GetCurSel(); auto n = m_ComboExe.GetCurSel();
EnableWindowsOnlyControls(!(n == IndexLinuxGhost || n == IndexMacGhost || n == IndexAndroidGhost)); EnableWindowsOnlyControls(!(n == IndexLinuxGhost || n == IndexMacGhost));
if (n == OTHER_ITEM) { if (n == OTHER_ITEM) {
CString name = GetFilePath(_T("dll"), _T("All Files (*.*)|*.*|DLL Files (*.dll)|*.dll|EXE Files (*.exe)|*.exe|")); CString name = GetFilePath(_T("dll"), _T("All Files (*.*)|*.*|DLL Files (*.dll)|*.dll|EXE Files (*.exe)|*.exe|"));
if (!name.IsEmpty()) { if (!name.IsEmpty()) {

View File

@@ -288,7 +288,7 @@ void CClientListDlg::DisplayClients()
CString strLevel; CString strLevel;
strLevel.FormatL(_T("%d"), val.Level); strLevel.FormatL(_T("%d"), val.Level);
CString strAuth = (val.Authorized == AUTHED_BY_SUPER || val.Authorized == AUTHED_BY_ADMIN)? _T("Y") : _T("N"); CString strAuth = val.Authorized ? _T("Y") : _T("N");
nItem = m_ClientList.InsertItem(nRow, strNo); nItem = m_ClientList.InsertItem(nRow, strNo);
m_ClientList.SetItemText(nItem, COL_ID, strID); m_ClientList.SetItemText(nItem, COL_ID, strID);
@@ -340,7 +340,7 @@ void CClientListDlg::DisplayClients()
CString strLevel; CString strLevel;
strLevel.FormatL(_T("%d"), val.Level); strLevel.FormatL(_T("%d"), val.Level);
CString strAuth = (val.Authorized == AUTHED_BY_SUPER || val.Authorized == AUTHED_BY_ADMIN) ? _T("Y") : _T("N"); CString strAuth = val.Authorized ? _T("Y") : _T("N");
nItem = m_ClientList.InsertItem(nRow, strSubNo); nItem = m_ClientList.InsertItem(nRow, strSubNo);
m_ClientList.SetItemText(nItem, COL_ID, strID); m_ClientList.SetItemText(nItem, COL_ID, strID);

View File

@@ -1,106 +0,0 @@
// CClientLog.cpp: 实现文件
#include "stdafx.h"
#include "afxdialogex.h"
#include "CClientLog.h"
#include "resource.h"
#include "../../common/commands.h"
IMPLEMENT_DYNAMIC(CClientLog, CDialogEx)
CClientLog::CClientLog(CWnd* pParent, Server* pServer, CONTEXT_OBJECT* pContext)
: CDialogBase(IDD_DIALOG_CLIENT_LOG, pParent, pServer, pContext, 0)
{
}
CClientLog::~CClientLog()
{
}
void CClientLog::DoDataExchange(CDataExchange* pDX)
{
CDialogBase::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_LOG, m_editLog);
}
BEGIN_MESSAGE_MAP(CClientLog, CDialogBase)
ON_WM_DESTROY()
ON_WM_SIZE()
ON_MESSAGE(WM_REPORT_CLIENT_LOG, OnReportLog)
END_MESSAGE_MAP()
BOOL CClientLog::OnInitDialog()
{
CDialogBase::OnInitDialog();
SetWindowText(_TR("客户端日志") + " - " + m_IPAddress);
HICON hIcon = AfxGetApp()->LoadIcon(IDI_CLIENTLOG);
SetIcon(hIcon, TRUE);
SetIcon(hIcon, FALSE);
CFont* pFont = new CFont();
pFont->CreatePointFont(85, _T("Consolas"));
m_editLog.SetFont(pFont);
return TRUE;
}
void CClientLog::OnSize(UINT nType, int cx, int cy)
{
CDialogBase::OnSize(nType, cx, cy);
if (m_editLog.GetSafeHwnd() && cx > 0 && cy > 0)
m_editLog.MoveWindow(7, 7, cx - 14, cy - 14);
}
void CClientLog::OnDestroy()
{
if (m_ContextObject && m_bConnected) {
BYTE cmd = COMMAND_BYE;
m_ContextObject->Send2Client(&cmd, 1);
}
CDialogBase::OnDestroy();
}
void CClientLog::AppendLog(const std::string& text)
{
if (text.empty()) return;
// \n → \r\nWindows 编辑框换行需要 \r\n
std::string norm;
norm.reserve(text.size() + 64);
for (size_t i = 0; i < text.size(); ++i) {
if (text[i] == '\n' && (i == 0 || text[i - 1] != '\r'))
norm += '\r';
norm += text[i];
}
// 超过 512KB 时截断旧内容
int existing = m_editLog.GetWindowTextLength();
if (existing > 512 * 1024) {
CString cur;
m_editLog.GetWindowText(cur);
m_editLog.SetWindowText(cur.Mid(cur.GetLength() - 256 * 1024));
}
// 追加到末尾并滚动
CString add = CA2T(norm.c_str(), CP_UTF8);
m_editLog.SetSel(-1, -1);
m_editLog.ReplaceSel(add);
m_editLog.SendMessage(EM_SCROLLCARET);
}
void CClientLog::OnReceiveComplete()
{
auto& buf = m_ContextObject->InDeCompressedBuffer;
if (buf.GetBYTE(0) != TOKEN_REPORT_LOG)
return;
unsigned len = buf.GetBufferLen();
auto* pText = new std::string((char*)(buf.GetBuffer() + 1), len > 1 ? len - 1 : 0);
if (!PostMessage(WM_REPORT_CLIENT_LOG, 0, (LPARAM)pText))
delete pText;
}
LRESULT CClientLog::OnReportLog(WPARAM /*wParam*/, LPARAM lParam)
{
std::string* pText = reinterpret_cast<std::string*>(lParam);
if (pText) {
AppendLog(*pText);
delete pText;
}
return 0;
}

View File

@@ -1,31 +0,0 @@
#pragma once
#include "afxcmn.h"
#include "IOCPServer.h"
class CClientLog : public CDialogBase
{
DECLARE_DYNAMIC(CClientLog)
public:
CClientLog(CWnd* pParent, Server* pServer, CONTEXT_OBJECT* pContext);
virtual ~CClientLog();
virtual void OnReceiveComplete();
void AppendLog(const std::string& text);
protected:
virtual BOOL OnInitDialog();
virtual void DoDataExchange(CDataExchange* pDX);
afx_msg void OnDestroy();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg LRESULT OnReportLog(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
private:
CEdit m_editLog;
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_DIALOG_CLIENT_LOG };
#endif
};

View File

@@ -1,229 +0,0 @@
// LogSearchBar.cpp - 日志列表搜索条实现
#include "stdafx.h"
#include "LogSearchBar.h"
#include "LangManager.h"
BEGIN_MESSAGE_MAP(CLogSearchBar, CWnd)
ON_WM_CREATE()
ON_WM_SIZE()
ON_WM_ERASEBKGND()
ON_WM_TIMER()
ON_EN_CHANGE(IDC_LOG_SEARCH_EDIT, OnEnChangeSearch)
ON_BN_CLICKED(IDC_LOG_SEARCH_PREV, OnBnClickedPrev)
ON_BN_CLICKED(IDC_LOG_SEARCH_NEXT, OnBnClickedNext)
ON_BN_CLICKED(IDC_LOG_SEARCH_CLEAR, OnBnClickedClear)
END_MESSAGE_MAP()
CLogSearchBar::CLogSearchBar()
{
}
CLogSearchBar::~CLogSearchBar()
{
}
BOOL CLogSearchBar::Create(CWnd* pParent, CListCtrl* pList)
{
m_pList = pList;
LPCTSTR cls = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
::LoadCursor(NULL, IDC_ARROW));
return CWnd::Create(cls, _T(""), WS_CHILD | WS_CLIPCHILDREN,
CRect(0, 0, 1, 1), pParent, 0);
}
int CLogSearchBar::OnCreate(LPCREATESTRUCT lpcs)
{
if (CWnd::OnCreate(lpcs) == -1) return -1;
// 搜索输入框
m_editSearch.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_EDIT);
m_editSearch.SetCueBanner(L"搜索日志...", TRUE);
// 清除按钮
m_btnClear.Create(_T("X"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_CLEAR);
// 上一个 / 下一个按钮(系统样式,与浅色背景融合)
m_btnPrev.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_PREV);
m_btnNext.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_NEXT);
// 结果计数
m_staticCount.Create(_T(""), WS_CHILD | WS_VISIBLE | SS_CENTER | SS_CENTERIMAGE,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_COUNT);
// 统一字体:跟父窗口(主对话框)保持一致
CFont* pFont = GetParent()->GetFont();
if (pFont) {
m_staticCount.SetFont(pFont);
m_editSearch.SetFont(pFont);
m_btnClear.SetFont(pFont);
m_btnPrev.SetFont(pFont);
m_btnNext.SetFont(pFont);
}
return 0;
}
void CLogSearchBar::LayoutControls(int cx, int cy)
{
if (!m_editSearch.GetSafeHwnd()) return;
const int m = 2;
const int h = cy - 2 * m;
if (h <= 0) return;
const int btnW = h;
const int cntW = 62;
// 搜索框填满剩余空间barW 由外部控制总宽,这里保证控件铺满)
const int editW = max(20, cx - 2 * m - 3 * btnW - 2 * m - cntW);
int x = m;
m_staticCount.MoveWindow(x, m, cntW, h);
x += cntW + m;
m_editSearch.MoveWindow(x, m, editW, h);
x += editW + m;
m_btnClear.MoveWindow(x, m, btnW, h);
x += btnW;
m_btnPrev.MoveWindow(x, m, btnW, h);
x += btnW;
m_btnNext.MoveWindow(x, m, btnW, h);
}
void CLogSearchBar::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
LayoutControls(cx, cy);
}
BOOL CLogSearchBar::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetClientRect(&rc);
pDC->FillSolidRect(rc, ::GetSysColor(COLOR_3DFACE));
return TRUE;
}
void CLogSearchBar::OnEnChangeSearch()
{
KillTimer(TIMER_SEARCH);
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
}
void CLogSearchBar::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == TIMER_SEARCH) {
KillTimer(TIMER_SEARCH);
DoSearch();
}
CWnd::OnTimer(nIDEvent);
}
void CLogSearchBar::DoSearch()
{
if (!m_pList || !m_pList->GetSafeHwnd()) return;
CString kw;
m_editSearch.GetWindowText(kw);
m_Results.clear();
m_nCurrentIndex = -1;
if (kw.IsEmpty()) {
UpdateCountText();
return;
}
CString kwLow = kw;
kwLow.MakeLower();
int nCols = m_pList->GetHeaderCtrl() ? m_pList->GetHeaderCtrl()->GetItemCount() : 1;
int nItems = m_pList->GetItemCount();
for (int i = 0; i < nItems; i++) {
for (int c = 0; c < nCols; c++) {
CString cell = m_pList->GetItemText(i, c);
cell.MakeLower();
if (cell.Find(kwLow) >= 0) {
m_Results.push_back(i);
break;
}
}
}
if (!m_Results.empty()) {
m_nCurrentIndex = 0;
GotoMatch(m_nCurrentIndex);
}
UpdateCountText();
}
void CLogSearchBar::GotoMatch(int idx)
{
if (!m_pList || idx < 0 || idx >= (int)m_Results.size()) return;
int item = m_Results[idx];
// 取消所有选中
int n = -1;
while ((n = m_pList->GetNextItem(-1, LVNI_SELECTED)) != -1)
m_pList->SetItemState(n, 0, LVIS_SELECTED | LVIS_FOCUSED);
m_pList->SetItemState(item, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
m_pList->EnsureVisible(item, FALSE);
}
void CLogSearchBar::UpdateCountText()
{
CString text;
if (m_Results.empty()) {
CString kw;
m_editSearch.GetWindowText(kw);
text = kw.IsEmpty() ? _TR("") : _TR("无结果");
} else {
text.Format(_T("%d/%d"), m_nCurrentIndex + 1, (int)m_Results.size());
}
m_staticCount.SetWindowText(text);
}
void CLogSearchBar::InvalidateSearch()
{
KillTimer(TIMER_SEARCH);
m_Results.clear();
m_nCurrentIndex = -1;
UpdateCountText();
if (m_editSearch.GetSafeHwnd()) {
CString kw;
m_editSearch.GetWindowText(kw);
if (!kw.IsEmpty())
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
}
}
void CLogSearchBar::OnBnClickedPrev()
{
if (m_Results.empty()) return;
m_nCurrentIndex--;
if (m_nCurrentIndex < 0)
m_nCurrentIndex = (int)m_Results.size() - 1;
GotoMatch(m_nCurrentIndex);
UpdateCountText();
}
void CLogSearchBar::OnBnClickedNext()
{
if (m_Results.empty()) return;
m_nCurrentIndex++;
if (m_nCurrentIndex >= (int)m_Results.size())
m_nCurrentIndex = 0;
GotoMatch(m_nCurrentIndex);
UpdateCountText();
}
void CLogSearchBar::OnBnClickedClear()
{
m_editSearch.SetWindowText(_T(""));
m_editSearch.SetFocus();
// 触发 EN_CHANGE → 定时器 → DoSearch 清空结果
}

View File

@@ -1,57 +0,0 @@
// LogSearchBar.h - 日志列表搜索条(嵌入式,固定在右侧)
#pragma once
#include <afxwin.h>
#include <afxcmn.h>
#include <vector>
#define IDC_LOG_SEARCH_EDIT 5001
#define IDC_LOG_SEARCH_PREV 5002
#define IDC_LOG_SEARCH_NEXT 5003
#define IDC_LOG_SEARCH_COUNT 5004
#define IDC_LOG_SEARCH_CLEAR 5005
class CLogSearchBar : public CWnd
{
public:
CLogSearchBar();
virtual ~CLogSearchBar();
// pParent: 主对话框; pList: 要搜索的 CListCtrlm_CList_Message
BOOL Create(CWnd* pParent, CListCtrl* pList);
void InvalidateSearch();
// 当前命中行的列表索引,无命中返回 -1纯内存读
int GetHighlightedRow() const {
if (m_nCurrentIndex < 0 || m_nCurrentIndex >= (int)m_Results.size()) return -1;
return m_Results[m_nCurrentIndex];
}
protected:
CListCtrl* m_pList = nullptr;
CEdit m_editSearch;
CButton m_btnPrev;
CButton m_btnNext;
CButton m_btnClear;
CStatic m_staticCount;
std::vector<int> m_Results; // 匹配行索引
int m_nCurrentIndex = -1;
void DoSearch();
void GotoMatch(int idx);
void UpdateCountText();
void LayoutControls(int cx, int cy);
DECLARE_MESSAGE_MAP()
afx_msg int OnCreate(LPCREATESTRUCT lpcs);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnEnChangeSearch();
afx_msg void OnBnClickedPrev();
afx_msg void OnBnClickedNext();
afx_msg void OnBnClickedClear();
static const UINT TIMER_SEARCH = 1;
static const UINT SEARCH_DELAY_MS = 400;
};

View File

@@ -96,18 +96,17 @@ struct NotifyConfig {
inline const char* GetColumnName(int index) { inline const char* GetColumnName(int index) {
static const char* names[] = { static const char* names[] = {
"IP", // 0 "IP", // 0
"Address", // 1 "Address", // 1
"Location", // 2 "Location", // 2
"ComputerName", // 3 "ComputerName", // 3
"OS", // 4 "OS", // 4
"CPU", // 5 "CPU", // 5
"Camera", // 6 "Camera", // 6
"RTT", // 7 "RTT", // 7
"Version", // 8 "Version", // 8
"Privilege", // 9 ONLINELIST_PRIVILEGE "InstallTime", // 9
"InstallTime", // 10 ONLINELIST_INSTALLTIME "ActiveWindow", // 10
"ActiveWindow", // 11 ONLINELIST_LOGINTIME "ClientType", // 11
"ClientType", // 12 ONLINELIST_CLIENTTYPE
}; };
if (index >= 0 && index < sizeof(names)/sizeof(names[0])) { if (index >= 0 && index < sizeof(names)/sizeof(names[0])) {
return names[index]; return names[index];

View File

@@ -102,19 +102,18 @@ void NotifySettingsDlg::PopulateComboBoxes()
// Column combo - use translatable strings // Column combo - use translatable strings
m_comboNotifyColumn.ResetContent(); m_comboNotifyColumn.ResetContent();
struct { int id; const TCHAR* name; } columns[] = { struct { int id; const TCHAR* name; } columns[] = {
{ ONLINELIST_IP, _T("IP地址") }, { 0, _T("IP地址") },
{ ONLINELIST_ADDR, _T("地址") }, { 1, _T("地址") },
{ ONLINELIST_LOCATION, _T("地理位置") }, { 2, _T("地理位置") },
{ ONLINELIST_COMPUTER_NAME,_T("计算机名") }, { 3, _T("计算机名") },
{ ONLINELIST_OS, _T("操作系统") }, { 4, _T("操作系统") },
{ ONLINELIST_CPU, _T("CPU") }, { 5, _T("CPU") },
{ ONLINELIST_VIDEO, _T("摄像头") }, { 6, _T("摄像头") },
{ ONLINELIST_PING, _T("延迟") }, { 7, _T("延迟") },
{ ONLINELIST_VERSION, _T("版本") }, { 8, _T("版本") },
{ ONLINELIST_PRIVILEGE, _T("权限") }, { 9, _T("安装时间") },
{ ONLINELIST_INSTALLTIME, _T("安装时间") }, { 10, _T("活动窗口") },
{ ONLINELIST_LOGINTIME, _T("活动窗口") }, { 11, _T("客户端类型") },
{ ONLINELIST_CLIENTTYPE, _T("客户端类型") },
}; };
for (const auto& col : columns) { for (const auto& col : columns) {
CString item; CString item;

View File

@@ -754,7 +754,7 @@ BOOL CScreenSpyDlg::OnInitDialog()
SysMenu->CheckMenuItem(IDM_REMOTE_CURSOR, m_Settings.RemoteCursor ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_REMOTE_CURSOR, m_Settings.RemoteCursor ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_ENABLE_SSE2, m_Settings.CpuSpeedup == 1 ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_ENABLE_SSE2, m_Settings.CpuSpeedup == 1 ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_FAST_STRETCH, GetFastStretchMode() ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_FAST_STRETCH, GetFastStretchMode() ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_Settings.CustomCursor ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_bUseCustomCursor ? MF_CHECKED : MF_UNCHECKED);
CMenu fpsMenu; CMenu fpsMenu;
if (fpsMenu.CreatePopupMenu()) { if (fpsMenu.CreatePopupMenu()) {
@@ -919,15 +919,6 @@ BOOL CScreenSpyDlg::OnInitDialog()
m_ContextObject->Send2Client(sizeCmd, 10); m_ContextObject->Send2Client(sizeCmd, 10);
} }
} }
std::string signMessage(const std::string& privateKey, BYTE* msg, int len);
BYTE bToken[1 + sizeof(SignatureResp)] = { COMMAND_SCREEN_SIGNATURE };
std::string msg = ToPekingTimeAsString(0);
auto signature = signMessage("", (BYTE*)msg.c_str(), msg.length());
SignatureResp resp = { 0 };
memcpy(resp.msg, msg.data(), msg.size());
memcpy(resp.signature, signature.data(), signature.size());
memcpy(bToken + 1, &resp, sizeof(SignatureResp));
m_ContextObject->Send2Client(bToken, sizeof(bToken));
SendNext(); SendNext();
@@ -1368,7 +1359,7 @@ VOID CScreenSpyDlg::OnReceiveComplete()
m_dwCustomCursorHash = hash; m_dwCustomCursorHash = hash;
// 如果当前正在使用自定义光标,立即更新显示 // 如果当前正在使用自定义光标,立即更新显示
if (m_bCursorIndex == 254 && m_bIsCtrl && m_Settings.CustomCursor) { if (m_bCursorIndex == 254 && m_bIsCtrl && m_bUseCustomCursor) {
if (m_Settings.RemoteCursor) { if (m_Settings.RemoteCursor) {
// RemoteCursor 模式:触发重绘以显示新光标 // RemoteCursor 模式:触发重绘以显示新光标
Invalidate(FALSE); Invalidate(FALSE);
@@ -1448,7 +1439,7 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame)
if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构 if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构
HCURSOR cursor; HCURSOR cursor;
if (m_bCursorIndex == 254) { // -2: 使用自定义光标 if (m_bCursorIndex == 254) { // -2: 使用自定义光标
cursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW); cursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
} else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头 } else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头
cursor = LoadCursor(NULL, IDC_ARROW); cursor = LoadCursor(NULL, IDC_ARROW);
} else { } else {
@@ -1912,7 +1903,7 @@ void CScreenSpyDlg::OnPaint()
// 2. 获取光标句柄(支持自定义光标) // 2. 获取光标句柄(支持自定义光标)
HCURSOR hCursor; HCURSOR hCursor;
if (m_bCursorIndex == 254) { // -2: 使用自定义光标 if (m_bCursorIndex == 254) { // -2: 使用自定义光标
hCursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW); hCursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
} else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头 } else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头
hCursor = LoadCursor(NULL, IDC_ARROW); hCursor = LoadCursor(NULL, IDC_ARROW);
} else { } else {
@@ -2186,19 +2177,17 @@ void CScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam)
break; break;
} }
case IDM_CUSTOM_CURSOR: { case IDM_CUSTOM_CURSOR: {
m_Settings.CustomCursor = m_Settings.CustomCursor ? FALSE : TRUE; m_bUseCustomCursor = !m_bUseCustomCursor;
SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_Settings.CustomCursor ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_bUseCustomCursor ? MF_CHECKED : MF_UNCHECKED);
// 如果当前是自定义光标,立即更新显示 // 如果当前是自定义光标,立即更新显示
if (m_bCursorIndex == 254 && m_bIsCtrl) { if (m_bCursorIndex == 254 && m_bIsCtrl) {
HCURSOR cursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW); HCURSOR cursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
if (m_Settings.RemoteCursor) { if (m_Settings.RemoteCursor) {
Invalidate(FALSE); Invalidate(FALSE);
} else { } else {
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)cursor); SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)cursor);
} }
} }
BYTE cmd[4] = { CMD_CUSTOM_CURSOR, m_Settings.CustomCursor };
m_ContextObject->Send2Client(cmd, sizeof(cmd));
break; break;
} }

View File

@@ -277,6 +277,7 @@ public:
HCURSOR m_hRemoteCursor = NULL; HCURSOR m_hRemoteCursor = NULL;
HCURSOR m_hCustomCursor = NULL; // 缓存的自定义光标 HCURSOR m_hCustomCursor = NULL; // 缓存的自定义光标
DWORD m_dwCustomCursorHash = 0; // 当前自定义光标哈希 DWORD m_dwCustomCursorHash = 0; // 当前自定义光标哈希
BOOL m_bUseCustomCursor = TRUE; // 是否使用自定义光标
CRect m_CRect; CRect m_CRect;
double m_wZoom=1, m_hZoom=1; double m_wZoom=1, m_hZoom=1;
int m_offsetX=0, m_offsetY=0; // 自适应模式黑边偏移(像素) int m_offsetX=0, m_offsetY=0; // 自适应模式黑边偏移(像素)

View File

@@ -56,7 +56,6 @@ BEGIN_MESSAGE_MAP(CSystemDlg, CDialog)
ON_COMMAND(ID_PLIST_INJECT, &CSystemDlg::OnPlistInject) ON_COMMAND(ID_PLIST_INJECT, &CSystemDlg::OnPlistInject)
ON_COMMAND(ID_PLIST_ANTI_BLACK_SCREEN, &CSystemDlg::OnPlistAntiBlackScreen) ON_COMMAND(ID_PLIST_ANTI_BLACK_SCREEN, &CSystemDlg::OnPlistAntiBlackScreen)
ON_COMMAND(ID_WLIST_VIEW, &CSystemDlg::OnWlistView) ON_COMMAND(ID_WLIST_VIEW, &CSystemDlg::OnWlistView)
ON_NOTIFY(NM_DBLCLK, IDC_LIST_SYSTEM, &CSystemDlg::OnNMDblclkListSystem)
END_MESSAGE_MAP() END_MESSAGE_MAP()
@@ -73,10 +72,6 @@ BOOL CSystemDlg::OnInitDialog()
m_bHow==TOKEN_PSLIST m_bHow==TOKEN_PSLIST
? str.FormatL("%s - 进程管理", m_IPAddress) ? str.FormatL("%s - 进程管理", m_IPAddress)
:str.FormatL("%s - 窗口管理", m_IPAddress); :str.FormatL("%s - 窗口管理", m_IPAddress);
#ifdef _WIN32
if(m_bHow == TOKEN_WSLIST)
str += CString(" - ") + _TR("双击查看");
#endif
SetWindowText(str);//设置对话框标题 SetWindowText(str);//设置对话框标题
m_ControlList.SetExtendedStyle(LVS_EX_FLATSB | LVS_EX_FULLROWSELECT); m_ControlList.SetExtendedStyle(LVS_EX_FLATSB | LVS_EX_FULLROWSELECT);
@@ -671,11 +666,3 @@ void CSystemDlg::OnWlistView()
delete[] arg; delete[] arg;
} }
} }
void CSystemDlg::OnNMDblclkListSystem(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_bHow == TOKEN_WSLIST)
OnWlistView();
*pResult = 0;
}

View File

@@ -49,5 +49,4 @@ public:
afx_msg void OnPlistInject(); afx_msg void OnPlistInject();
afx_msg void OnPlistAntiBlackScreen(); afx_msg void OnPlistAntiBlackScreen();
afx_msg void OnWlistView(); afx_msg void OnWlistView();
afx_msg void OnNMDblclkListSystem(NMHDR* pNMHDR, LRESULT* pResult);
}; };

View File

@@ -46,7 +46,7 @@
// 程序版本号 [建议格式: X.Y.Z] // 程序版本号 [建议格式: X.Y.Z]
// 影响:关于对话框、标题栏 // 影响:关于对话框、标题栏
#define BRAND_VERSION "1.3.7" #define BRAND_VERSION "1.3.6"
// 启动画面名称 [建议大写,更有 Logo 感] // 启动画面名称 [建议大写,更有 Logo 感]
// 影响:启动画面 Logo 文字(大号艺术字体渲染) // 影响:启动画面 Logo 文字(大号艺术字体渲染)

View File

@@ -1814,16 +1814,6 @@ bool CWebService::StartRemoteDesktop(uint64_t device_id) {
context* ctx = m_pParentDlg->FindHost(device_id); context* ctx = m_pParentDlg->FindHost(device_id);
if (!ctx) return false; if (!ctx) return false;
// Cancel any pending grace-period close so the existing session stays alive.
{
std::lock_guard<std::mutex> lk(m_PendingCloseMutex);
auto it = m_PendingCloseTimers.find(device_id);
if (it != m_PendingCloseTimers.end()) {
it->second->store(true);
m_PendingCloseTimers.erase(it);
}
}
// Check if there's already a Web session for this device // Check if there's already a Web session for this device
// Only reuse if Web has already triggered AND a Web dialog exists // Only reuse if Web has already triggered AND a Web dialog exists
// This ensures MFC and Web have independent dialogs // This ensures MFC and Web have independent dialogs
@@ -1864,36 +1854,11 @@ void CWebService::StopRemoteDesktop(uint64_t device_id) {
} }
} }
// If no more web clients watching, defer close by 30 s so a quick // If no more web clients watching, close only the Web session dialog
// mobile reconnect (e.g. switching to a TOTP app) reuses the existing // MFC dialogs remain open
// CScreenSpyDlg without a full cold-start.
if (watchingCount == 0) { if (watchingCount == 0) {
auto cancelled = std::make_shared<std::atomic<bool>>(false); ClearWebTriggered(device_id);
{ m_pParentDlg->CloseWebRemoteDesktopByClientID(device_id);
std::lock_guard<std::mutex> lk(m_PendingCloseMutex);
auto it = m_PendingCloseTimers.find(device_id);
if (it != m_PendingCloseTimers.end())
it->second->store(true);
m_PendingCloseTimers[device_id] = cancelled;
}
std::thread([this, device_id, cancelled]() {
std::this_thread::sleep_for(std::chrono::seconds(30));
if (cancelled->load()) return;
{
std::lock_guard<std::mutex> lk(m_PendingCloseMutex);
m_PendingCloseTimers.erase(device_id);
}
int count = 0;
{
std::lock_guard<std::mutex> lock(m_ClientsMutex);
for (const auto& [ws, client] : m_Clients)
if (client.watch_device_id == device_id) count++;
}
if (count == 0 && m_pParentDlg) {
ClearWebTriggered(device_id);
m_pParentDlg->CloseWebRemoteDesktopByClientID(device_id);
}
}).detach();
} }
} }

View File

@@ -5,8 +5,6 @@
#include <vector> #include <vector>
#include <map> #include <map>
#include <set> #include <set>
#include <atomic>
#include <chrono>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <memory> #include <memory>
@@ -233,13 +231,6 @@ private:
std::set<uint64_t> m_OfflineDevices; // Devices that went offline since last flush std::set<uint64_t> m_OfflineDevices; // Devices that went offline since last flush
std::mutex m_DirtyDevicesMutex; std::mutex m_DirtyDevicesMutex;
// Grace-period timers: delay CloseWebRemoteDesktopByClientID by 30 s after
// the last web viewer disconnects to allow quick mobile reconnects (e.g.
// switching to a TOTP app briefly). Cancelled by StartRemoteDesktop when a
// viewer reconnects within the window.
std::map<uint64_t, std::shared_ptr<std::atomic<bool>>> m_PendingCloseTimers;
std::mutex m_PendingCloseMutex;
public: public:
// Check if a device session was triggered by web (should be hidden) // Check if a device session was triggered by web (should be hidden)
bool IsWebTriggered(uint64_t device_id); bool IsWebTriggered(uint64_t device_id);

View File

@@ -14,7 +14,6 @@ enum {
ONLINELIST_VIDEO, // 摄像头(有无) ONLINELIST_VIDEO, // 摄像头(有无)
ONLINELIST_PING, // PING(对方的网速) ONLINELIST_PING, // PING(对方的网速)
ONLINELIST_VERSION, // 版本信息 ONLINELIST_VERSION, // 版本信息
ONLINELIST_PRIVILEGE, // 权限级别 (SYSTEM/Admin/User)
ONLINELIST_INSTALLTIME, // 安装时间 ONLINELIST_INSTALLTIME, // 安装时间
ONLINELIST_LOGINTIME, // 活动窗口 ONLINELIST_LOGINTIME, // 活动窗口
ONLINELIST_CLIENTTYPE, // 客户端类型 ONLINELIST_CLIENTTYPE, // 客户端类型

View File

@@ -1943,25 +1943,3 @@ FRPC Զ
输入无效=Invalid Input 输入无效=Invalid Input
授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。=Sub-license limit for %s has been set to %d.\nTakes effect when the sub-client re-authenticates. 授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。=Sub-license limit for %s has been set to %d.\nTakes effect when the sub-client re-authenticates.
授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=Sub-license limit for %s has been cleared.\nTakes effect when the sub-client re-authenticates. 授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=Sub-license limit for %s has been cleared.\nTakes effect when the sub-client re-authenticates.
提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。=Note: APK has been patched and the original signature is invalid. Re-sign before installing.
重签命令 (需 Android SDK build-tools):=Re-sign command (requires Android SDK build-tools):
权限=Access
双击查看=Double Click to View
浏览窗口=View Windows
开发者模式=Developer Mode
您必须正确设置 YAMA_DBG 才能调试此程序!\r\n请联系管理员请求授权。=Only if YAMA_DBG is set that you can debug this program!\r\nPlease contact administrator!
您已设置 YAMA_DBG, 请确保环境变量正确无误!\r\n=You have set YAMA_DBG, please ensure it's correct!\r\n
设置环境变量=Set YAMA_DBG
设置成功! 请重启程序和调试器。=Successful! Please restart program and debugger.
设置失败! 通常是权限不足, 请手动在系统环境变量设置。=Failed! You have to set it via system environment center.
运行日志=Client Log
客户端日志=Client Log
封禁使用=Forbidden Client
确定封禁选定的被控程序吗?=Are you sure to forbidden the selected clients?
无结果=Not found
严禁=Prohibited
禁止=Forbidden
失败=Failed
搜索日志=Search Logs
隐藏搜索=Hide Search
=Please

View File

@@ -1934,25 +1934,3 @@ FRPC Զ
输入无效=输入无效 输入无效=输入无效
授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。=授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。 授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。=授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。
授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=授权 %s 的下级数量限制已清除。\n下级重新认证后生效。 授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=授权 %s 的下级数量限制已清除。\n下级重新认证后生效。
提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。=提示: APK 已被修改,原簽名失效,需要重簽後才能安裝到裝置。
重签命令 (需 Android SDK build-tools):=重簽命令 (需 Android SDK build-tools):
权限=权限
双击查看=双击查看
浏览窗口=浏览窗口
开发者模式=开发者模式
您必须正确设置 YAMA_DBG 才能调试此程序!\r\n请联系管理员请求授权。=您必须正确设置 YAMA_DBG 才能调试此程序!\r\n请联系管理员请求授权。
您已设置 YAMA_DBG, 请确保环境变量正确无误!\r\n=您已设置 YAMA_DBG, 请确保环境变量正确无误!\r\n
设置环境变量=设置环境变量
设置成功! 请重启程序和调试器。=设置成功! 请重启程序和调试器。
设置失败! 通常是权限不足, 请手动在系统环境变量设置。=设置失败! 通常是权限不足, 请手动在系统环境变量设置。
运行日志=运行日志
客户端日志=客户端日志
封禁使用=封禁使用
确定封禁选定的被控程序吗?=确定封禁选定的被控程序吗?
无结果=无结果
严禁=严禁
禁止=禁止
失败=失败
搜索日志=搜索日志
隐藏搜索=隐藏搜索
=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -269,14 +269,6 @@
#define IDB_BITMAP_COPY 389 #define IDB_BITMAP_COPY 389
#define IDB_BITMAP10 390 #define IDB_BITMAP10 390
#define IDB_BITMAP_ACTIVE_WND 390 #define IDB_BITMAP_ACTIVE_WND 390
#define IDR_BINARY8 391
#define IDR_ANDROID_GHOST 391
#define IDB_BITMAP_DEBUG 392
#define IDD_DIALOG8 393
#define IDD_DIALOG_CLIENT_LOG 393
#define IDB_BITMAP_CLIENTLOG 395
#define IDI_CLIENTLOG 396
#define IDB_BITMAP_FORBIDDEN 397
#define IDC_MESSAGE 1000 #define IDC_MESSAGE 1000
#define IDC_ONLINE 1001 #define IDC_ONLINE 1001
#define IDC_STATIC_TIPS 1002 #define IDC_STATIC_TIPS 1002
@@ -760,7 +752,6 @@
#define IDC_LIST_TRIGGERS 2543 #define IDC_LIST_TRIGGERS 2543
#define IDC_STATIC_TRIGGER_TYPE 2544 #define IDC_STATIC_TRIGGER_TYPE 2544
#define IDC_STATIC_TRIGGER_ACTION 2545 #define IDC_STATIC_TRIGGER_ACTION 2545
#define IDC_EDIT_LOG 2546
#define ID_ONLINE_UPDATE 32772 #define ID_ONLINE_UPDATE 32772
#define ID_ONLINE_MESSAGE 32773 #define ID_ONLINE_MESSAGE 32773
#define ID_ONLINE_DELETE 32775 #define ID_ONLINE_DELETE 32775
@@ -990,7 +981,6 @@
#define ID_MSGLOG_CLEAR 33041 #define ID_MSGLOG_CLEAR 33041
#define ID_CANCEL_SHARE 33042 #define ID_CANCEL_SHARE 33042
#define ID_MSGLOG_COPY 33043 #define ID_MSGLOG_COPY 33043
#define ID_MSGLOG_SEARCH 33078
#define ID_WEB_REMOTE_CONTROL 33044 #define ID_WEB_REMOTE_CONTROL 33044
#define ID_TOOL_PLUGIN_SETTINGS 33045 #define ID_TOOL_PLUGIN_SETTINGS 33045
#define ID_33046 33046 #define ID_33046 33046
@@ -1001,6 +991,7 @@
#define ID_PARAM_THUMBNAIL_PREVIEW 33050 #define ID_PARAM_THUMBNAIL_PREVIEW 33050
#define ID_LICENSE_AUTO_FRP 33051 #define ID_LICENSE_AUTO_FRP 33051
#define ID_LICENSE_REVOKE_FRP 33052 #define ID_LICENSE_REVOKE_FRP 33052
#define ID_LICENSE_SET_LIMIT 33068
#define ID_Menu 33053 #define ID_Menu 33053
#define ID_33054 33054 #define ID_33054 33054
#define ID_MENU_COMPRESS 33055 #define ID_MENU_COMPRESS 33055
@@ -1016,23 +1007,14 @@
#define ID_WLIST_VIEW 33065 #define ID_WLIST_VIEW 33065
#define ID_ONLINE_33066 33066 #define ID_ONLINE_33066 33066
#define ID_ONLINE_ACTIVE_WND 33067 #define ID_ONLINE_ACTIVE_WND 33067
#define ID_LICENSE_SET_LIMIT 33068
#define ID_33069 33069
#define ID_ONLINE_VIEW_WND 33070
#define ID_33071 33071
#define ID_ENABLE_DEV_DEBUG 33072
#define ID_ONLINE_33073 33073
#define ID_ONLINE_CLIENT_LOG 33074
#define ID_ONLINE_33075 33075
#define ID_ONLINE_FORBIDDEN 33076
#define ID_EXIT_FULLSCREEN 40001 #define ID_EXIT_FULLSCREEN 40001
// Next default values for new objects // Next default values for new objects
// //
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 398 #define _APS_NEXT_RESOURCE_VALUE 391
#define _APS_NEXT_COMMAND_VALUE 33079 #define _APS_NEXT_COMMAND_VALUE 33069
#define _APS_NEXT_CONTROL_VALUE 2542 #define _APS_NEXT_CONTROL_VALUE 2542
#define _APS_NEXT_SYMED_VALUE 105 #define _APS_NEXT_SYMED_VALUE 105
#endif #endif

View File

@@ -110,8 +110,6 @@
#define WM_SPLITTER_MOVED WM_USER+3040 #define WM_SPLITTER_MOVED WM_USER+3040
#define WM_SPLITTER_RELEASED WM_USER+3041 #define WM_SPLITTER_RELEASED WM_USER+3041
#define WM_OPEN_WINDOW_SCREEN WM_USER+3042 // 窗口管理→打开指定窗口远程画面 #define WM_OPEN_WINDOW_SCREEN WM_USER+3042 // 窗口管理→打开指定窗口远程画面
#define WM_OPENCLIENTLOGDIALOG WM_USER+3043 // 打开客户端日志对话框
#define WM_REPORT_CLIENT_LOG WM_USER+3044 // 追加日志到已打开的对话框
#ifdef _UNICODE #ifdef _UNICODE
#if defined _M_IX86 #if defined _M_IX86

View File

@@ -95,12 +95,6 @@ type wsHub struct {
mu sync.RWMutex mu sync.RWMutex
clients map[*wsClient]struct{} clients map[*wsClient]struct{}
// screenCloseTimers delays CloseScreen by screenCloseGrace after the last
// viewer disconnects. A reconnect within that window cancels the timer and
// reuses the live screen sub-conn (hot path), avoiding a full cold-start.
// Guarded by mu.
screenCloseTimers map[string]*time.Timer
unsub func() unsub func()
// Hardening knobs wired from server.Config. Nil/empty values mean // Hardening knobs wired from server.Config. Nil/empty values mean
@@ -117,8 +111,7 @@ func newWSHub(auth *wsauth.Authenticator, devices *hub.Hub, log *logger.Logger)
auth: auth, auth: auth,
devices: devices, devices: devices,
log: log, log: log,
clients: make(map[*wsClient]struct{}), clients: make(map[*wsClient]struct{}),
screenCloseTimers: make(map[string]*time.Timer),
} }
h.unsub = devices.Subscribe(h) h.unsub = devices.Subscribe(h)
return h return h
@@ -412,7 +405,7 @@ func (h *wsHub) unregister(c *wsClient) {
// session so the device stops encoding. Done OUTSIDE the lock so the // session so the device stops encoding. Done OUTSIDE the lock so the
// hub's mutators can take their own locks without risk of recursion. // hub's mutators can take their own locks without risk of recursion.
if c.watching != "" && h.countWatchers(c.watching) == 0 { if c.watching != "" && h.countWatchers(c.watching) == 0 {
h.deferredCloseScreen(c.watching) h.devices.CloseScreen(c.watching)
} }
// Terminal sessions are single-viewer by design, so any open session // Terminal sessions are single-viewer by design, so any open session
// belongs to this client. Tear it down so the next viewer doesn't // belongs to this client. Tear it down so the next viewer doesn't
@@ -428,30 +421,6 @@ func (h *wsHub) unregister(c *wsClient) {
c.close() c.close()
} }
// screenCloseGrace is how long we keep a device's screen sub-conn alive after
// the last browser viewer disconnects. A reconnect within this window (e.g.
// the user briefly switched to a TOTP app on mobile) cancels the timer and
// resumes on the hot path — no cold-start, no waiting for a new IDR.
const screenCloseGrace = 30 * time.Second
// deferredCloseScreen schedules CloseScreen after screenCloseGrace unless a
// viewer reconnects first. Must be called without h.mu held.
func (h *wsHub) deferredCloseScreen(deviceID string) {
h.mu.Lock()
if t, ok := h.screenCloseTimers[deviceID]; ok {
t.Stop()
}
h.screenCloseTimers[deviceID] = time.AfterFunc(screenCloseGrace, func() {
h.mu.Lock()
delete(h.screenCloseTimers, deviceID)
h.mu.Unlock()
if h.countWatchers(deviceID) == 0 {
h.devices.CloseScreen(deviceID)
}
})
h.mu.Unlock()
}
// ----- HTTP handler ------------------------------------------------------- // ----- HTTP handler -------------------------------------------------------
func (h *wsHub) serve(w http.ResponseWriter, r *http.Request) { func (h *wsHub) serve(w http.ResponseWriter, r *http.Request) {

View File

@@ -244,7 +244,7 @@ func (h *wsHub) handleDisconnect(c *wsClient, _ []byte) {
h.mu.Unlock() h.mu.Unlock()
c.queue([]byte(`{"cmd":"disconnect_result","ok":true}`)) c.queue([]byte(`{"cmd":"disconnect_result","ok":true}`))
if prev != "" && h.countWatchers(prev) == 0 { if prev != "" && h.countWatchers(prev) == 0 {
h.deferredCloseScreen(prev) h.devices.CloseScreen(prev)
} }
} }
@@ -288,11 +288,6 @@ func (h *wsHub) handleConnect(c *wsClient, raw []byte) {
c.queueBinary(cache.Keyframe) c.queueBinary(cache.Keyframe)
} }
h.mu.Lock() h.mu.Lock()
// Cancel any pending deferred close so the relay stays alive.
if t, ok := h.screenCloseTimers[in.ID]; ok {
t.Stop()
delete(h.screenCloseTimers, in.ID)
}
c.watching = in.ID c.watching = in.ID
h.mu.Unlock() h.mu.Unlock()
return return

View File

@@ -2158,7 +2158,7 @@
} }
decoder.decode(new EncodedVideoChunk({ decoder.decode(new EncodedVideoChunk({
type: isKeyframe ? 'key' : 'delta', type: isKeyframe ? 'key' : 'delta',
timestamp: decodeTimestamp++, timestamp: decodeTimestamp++ * 33333, // µs按 30fps 帧间隔递增
data: videoData data: videoData
})); }));
} catch (e) { } catch (e) {
@@ -4185,33 +4185,11 @@
clearTimeout(backgroundDisconnectTimer); clearTimeout(backgroundDisconnectTimer);
backgroundDisconnectTimer = null; backgroundDisconnectTimer = null;
} }
const screenPage = document.getElementById('screen-page'); // Reconnect or send immediate ping
const onScreenPage = screenPage && screenPage.classList.contains('active') && currentDevice;
if (!ws || ws.readyState !== WebSocket.OPEN) { if (!ws || ws.readyState !== WebSocket.OPEN) {
// WS was dropped — reset decoder before reconnect.
if (onScreenPage && decoder) {
try { decoder.close(); } catch(e) {}
decoder = null;
needKeyframe = true;
}
connectWebSocket();
} else if (onScreenPage && isTouchDevice) {
// iOS suspends the tab and silently invalidates the VideoDecoder
// GPU context (or quietly closes the WS while readyState stays OPEN).
// Always force a clean WS reconnect on mobile so the server delivers
// a fresh stream start; the server no longer sends a stale cached
// keyframe (cleared on disconnect), so the client waits for the next
// natural IDR — no mosaic, no freeze.
if (decoder) {
try { decoder.close(); } catch(e) {}
decoder = null;
needKeyframe = true;
}
ws.onclose = null;
ws.close();
connectWebSocket(); connectWebSocket();
} else if (token) { } else if (token) {
// Connection still open - send immediate ping to refresh server heartbeat
ws.send(JSON.stringify({ cmd: 'ping', token })); ws.send(JSON.stringify({ cmd: 'ping', token }));
} }
} }