Compare commits
3 Commits
a649c10d0f
...
c38ccbe7ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c38ccbe7ca | ||
|
|
655b1934a4 | ||
|
|
ac14073921 |
@@ -21,6 +21,7 @@
|
|||||||
#include "common/file_upload.h"
|
#include "common/file_upload.h"
|
||||||
#include "common/DateVerify.h"
|
#include "common/DateVerify.h"
|
||||||
#include "common/LANChecker.h"
|
#include "common/LANChecker.h"
|
||||||
|
#include "common/scheduler.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "ServiceWrapper.h"
|
#include "ServiceWrapper.h"
|
||||||
}
|
}
|
||||||
@@ -75,6 +76,11 @@ CKernelManager::CKernelManager(CONNECT_ADDRESS* conn, IOCPClient* ClientObject,
|
|||||||
m_hKeyboard = kb;
|
m_hKeyboard = kb;
|
||||||
// C2C 初始化
|
// C2C 初始化
|
||||||
if (conn) m_MyClientID = conn->clientID;
|
if (conn) m_MyClientID = conn->clientID;
|
||||||
|
// 恢复并启动 SCH_MODE_STARTUP 模式的 DLL
|
||||||
|
int n = RestoreMemDLL();
|
||||||
|
if (n) {
|
||||||
|
Mprintf("[CKernelManager] RestoreMemDLL count: %d\n", n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL IsThreadsRunning(ThreadInfo* threads, int count)
|
BOOL IsThreadsRunning(ThreadInfo* threads, int count)
|
||||||
@@ -623,10 +629,93 @@ std::string getHardwareIDByCfg(const std::string& pwdHash, const std::string& ma
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CKernelManager::RestoreMemDLL() {
|
||||||
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
binFile bin(CLIENT_PATH);
|
||||||
|
|
||||||
|
// 枚举所有以 .md5 结尾的值名称
|
||||||
|
auto md5Keys = cfg.EnumValues("settings", ".md5");
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (const auto& key : md5Keys) {
|
||||||
|
// 获取 MD5 值
|
||||||
|
std::string md5 = cfg.GetStr("settings", key);
|
||||||
|
if (md5.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 从 "xxx.md5" 提取 "xxx"
|
||||||
|
std::string name = key.substr(0, key.size() - 4);
|
||||||
|
|
||||||
|
// 获取对应的二进制数据
|
||||||
|
std::string binData = bin.GetStr("settings", name + ".bin");
|
||||||
|
if (binData.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 解析 DllExecuteInfo,提取 DLL 数据
|
||||||
|
const int sz = 1 + sizeof(DllExecuteInfo);
|
||||||
|
if (binData.size() < sz)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const DllExecuteInfo* info = reinterpret_cast<const DllExecuteInfo*>(binData.data() + 1);
|
||||||
|
if (binData.size() < sz + info->Size)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 恢复到 m_MemDLL
|
||||||
|
const BYTE* dllData = reinterpret_cast<const BYTE*>(binData.data() + sz);
|
||||||
|
m_MemDLL[md5] = std::vector<BYTE>(dllData, dllData + info->Size);
|
||||||
|
Mprintf("Restore DLL from registry: %s (%s)\n", name.c_str(), md5.c_str());
|
||||||
|
count++;
|
||||||
|
|
||||||
|
// 检查是否为启动执行模式
|
||||||
|
if (info->Schedule.Mode == SCH_MODE_STARTUP) {
|
||||||
|
// 复制一份用于检查和执行
|
||||||
|
DllExecuteInfo infoCopy = *info;
|
||||||
|
ScheduleParams& sch = infoCopy.Schedule;
|
||||||
|
|
||||||
|
// 从注册表读取运行时状态(LastRunTime 和 CurrentCount)
|
||||||
|
std::string lastRunStr = cfg.GetStr("settings", name + ".lastrun");
|
||||||
|
std::string countStr = cfg.GetStr("settings", name + ".count");
|
||||||
|
if (!lastRunStr.empty()) {
|
||||||
|
sch.LastRunTime = std::stoull(lastRunStr);
|
||||||
|
}
|
||||||
|
if (!countStr.empty()) {
|
||||||
|
sch.CurrentCount = (unsigned char)std::stoi(countStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否应该执行
|
||||||
|
if (YamaTaskEngine::ShouldExecute(&sch)) {
|
||||||
|
Mprintf("Auto-start DLL on startup: %s\n", name.c_str());
|
||||||
|
char* buf = info->InfoSize > sizeof(DllExecuteInfo) ? new char[400] : 0;
|
||||||
|
if (buf) memcpy(buf, binData.data() + 1 + sizeof(DllExecuteInfo), 400);
|
||||||
|
PluginParam param(m_conn->ServerIP(), m_conn->ServerPort(), &g_bExit, buf);
|
||||||
|
BYTE* data = m_MemDLL[md5].data();
|
||||||
|
CloseHandle(__CreateThread(NULL, 0, ExecuteDLLProc, new DllExecParam<>(infoCopy, param, data, this), 0, NULL));
|
||||||
|
|
||||||
|
// 更新注册表中的运行时状态
|
||||||
|
// 如果有时间间隔限制,更新 LastRunTime
|
||||||
|
if (sch.Config.Startup.Interval > 0) {
|
||||||
|
YamaTaskEngine::MarkExecuted(&sch);
|
||||||
|
cfg.SetStr("settings", name + ".lastrun", std::to_string(sch.LastRunTime));
|
||||||
|
}
|
||||||
|
// 如果有次数限制,更新 CurrentCount
|
||||||
|
if (sch.MaxCount > 0) {
|
||||||
|
if (sch.Config.Startup.Interval == 0) {
|
||||||
|
// 如果没更新过 LastRunTime,需要单独增加计数
|
||||||
|
sch.CurrentCount++;
|
||||||
|
}
|
||||||
|
cfg.SetStr("settings", name + ".count", std::to_string(sch.CurrentCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T = DllExecuteInfo>
|
template<typename T = DllExecuteInfo>
|
||||||
BOOL ExecDLL(CKernelManager *This, PBYTE szBuffer, ULONG ulLength, void *user)
|
BOOL ExecDLL(CKernelManager *This, PBYTE szBuffer, ULONG ulLength, void *user)
|
||||||
{
|
{
|
||||||
static std::map<std::string, std::vector<BYTE>> m_MemDLL;
|
std::map<std::string, std::vector<BYTE>> &m_MemDLL(This->m_MemDLL);
|
||||||
const int sz = 1 + sizeof(T);
|
const int sz = 1 + sizeof(T);
|
||||||
if (ulLength < sz) return FALSE;
|
if (ulLength < sz) return FALSE;
|
||||||
const T* info = (T*)(szBuffer + 1);
|
const T* info = (T*)(szBuffer + 1);
|
||||||
@@ -649,6 +738,7 @@ BOOL ExecDLL(CKernelManager *This, PBYTE szBuffer, ULONG ulLength, void *user)
|
|||||||
}
|
}
|
||||||
BYTE* data = find != m_MemDLL.end() ? find->second.data() : NULL;
|
BYTE* data = find != m_MemDLL.end() ? find->second.data() : NULL;
|
||||||
if (info->Size == ulLength - sz) {
|
if (info->Size == ulLength - sz) {
|
||||||
|
// 收到完整 DLL 数据,保存到注册表
|
||||||
if (md5[0]) {
|
if (md5[0]) {
|
||||||
m_MemDLL[md5] = std::vector<BYTE>(szBuffer + sz, szBuffer + sz + info->Size);
|
m_MemDLL[md5] = std::vector<BYTE>(szBuffer + sz, szBuffer + sz + info->Size);
|
||||||
iniFile cfg(CLIENT_PATH);
|
iniFile cfg(CLIENT_PATH);
|
||||||
@@ -660,7 +750,18 @@ BOOL ExecDLL(CKernelManager *This, PBYTE szBuffer, ULONG ulLength, void *user)
|
|||||||
}
|
}
|
||||||
data = szBuffer + sz;
|
data = szBuffer + sz;
|
||||||
}
|
}
|
||||||
if (data) {
|
else if (data) {
|
||||||
|
// 只收到参数(无 DLL 数据),更新 .bin 中的参数部分
|
||||||
|
binFile bin(CLIENT_PATH);
|
||||||
|
std::string binData = bin.GetStr("settings", info->Name + std::string(".bin"));
|
||||||
|
if (binData.size() >= sz) {
|
||||||
|
// 替换 .bin 中的参数部分(跳过命令字节)
|
||||||
|
memcpy(&binData[1], szBuffer + 1, sizeof(T));
|
||||||
|
bin.SetStr("settings", info->Name + std::string(".bin"), binData);
|
||||||
|
Mprintf("Update DLL params in registry: %s\n", info->Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data && SCH_MODE_NONE == info->Schedule.Mode) {
|
||||||
PluginParam param(This->m_conn->ServerIP(), This->m_conn->ServerPort(), &This->g_bExit, user);
|
PluginParam param(This->m_conn->ServerIP(), This->m_conn->ServerPort(), &This->g_bExit, user);
|
||||||
CloseHandle(__CreateThread(NULL, 0, ExecuteDLLProc, new DllExecParam<T>(*info, param, data, This), 0, NULL));
|
CloseHandle(__CreateThread(NULL, 0, ExecuteDLLProc, new DllExecParam<T>(*info, param, data, This), 0, NULL));
|
||||||
Mprintf("Execute '%s'%d succeed - Length: %d\n", info->Name, info->CallType, info->Size);
|
Mprintf("Execute '%s'%d succeed - Length: %d\n", info->Name, info->CallType, info->Size);
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ public:
|
|||||||
std::string m_hash;
|
std::string m_hash;
|
||||||
std::string m_hmac;
|
std::string m_hmac;
|
||||||
uint64_t m_MyClientID = 0;
|
uint64_t m_MyClientID = 0;
|
||||||
|
// 执行代码
|
||||||
|
std::map<std::string, std::vector<BYTE>> m_MemDLL;
|
||||||
|
int RestoreMemDLL();
|
||||||
void SetLoginMsg(const std::string& msg)
|
void SetLoginMsg(const std::string& msg)
|
||||||
{
|
{
|
||||||
m_LoginMsg = msg;
|
m_LoginMsg = msg;
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ typedef struct {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "ip_enc.h"
|
#include "ip_enc.h"
|
||||||
|
#include "scheduler.h"
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
@@ -1145,7 +1146,8 @@ typedef struct DllExecuteInfo {
|
|||||||
char Md5[33]; // DLL MD5
|
char Md5[33]; // DLL MD5
|
||||||
int Pid; // 被注入进程ID
|
int Pid; // 被注入进程ID
|
||||||
char Is32Bit; // 是否32位DLL
|
char Is32Bit; // 是否32位DLL
|
||||||
char Reseverd[18];
|
unsigned short InfoSize; // 结构体大小
|
||||||
|
ScheduleParams Schedule; // 执行计划
|
||||||
} DllExecuteInfo;
|
} DllExecuteInfo;
|
||||||
|
|
||||||
typedef struct DllExecuteInfoNew {
|
typedef struct DllExecuteInfoNew {
|
||||||
@@ -1156,7 +1158,8 @@ typedef struct DllExecuteInfoNew {
|
|||||||
char Md5[33]; // DLL MD5
|
char Md5[33]; // DLL MD5
|
||||||
int Pid; // 被注入进程ID
|
int Pid; // 被注入进程ID
|
||||||
char Is32Bit; // 是否32位DLL
|
char Is32Bit; // 是否32位DLL
|
||||||
char Reseverd[18];
|
unsigned short InfoSize; // 结构体大小
|
||||||
|
ScheduleParams Schedule; // 执行计划
|
||||||
char Parameters[400];
|
char Parameters[400];
|
||||||
} DllExecuteInfoNew;
|
} DllExecuteInfoNew;
|
||||||
inline void SetParameters(DllExecuteInfoNew *p, char *param, int size)
|
inline void SetParameters(DllExecuteInfoNew *p, char *param, int size)
|
||||||
|
|||||||
@@ -359,6 +359,88 @@ public:
|
|||||||
}
|
}
|
||||||
m_keyCache.clear();
|
m_keyCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 枚举 m_SubKeyPath 下的所有子键名称
|
||||||
|
// suffix: 只返回以该后缀结尾的键名,默认为空表示返回所有键
|
||||||
|
std::vector<std::string> EnumSubKeys(const std::string& suffix = "") const
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
|
||||||
|
// 使用缓存获取 m_SubKeyPath 的句柄
|
||||||
|
auto it = m_keyCache.find(m_SubKeyPath);
|
||||||
|
HKEY hKey = NULL;
|
||||||
|
if (it != m_keyCache.end()) {
|
||||||
|
hKey = it->second;
|
||||||
|
} else {
|
||||||
|
if (RegOpenKeyExA(m_hRootKey, m_SubKeyPath.c_str(), 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
m_keyCache[m_SubKeyPath] = hKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
char keyName[256];
|
||||||
|
DWORD keyNameSize;
|
||||||
|
DWORD index = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
keyNameSize = sizeof(keyName);
|
||||||
|
LONG ret = RegEnumKeyExA(hKey, index, keyName, &keyNameSize, NULL, NULL, NULL, NULL);
|
||||||
|
if (ret == ERROR_NO_MORE_ITEMS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ret == ERROR_SUCCESS) {
|
||||||
|
if (suffix.empty()) {
|
||||||
|
result.push_back(keyName);
|
||||||
|
} else {
|
||||||
|
std::string name(keyName);
|
||||||
|
if (name.size() >= suffix.size() &&
|
||||||
|
name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0) {
|
||||||
|
result.push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 枚举指定 MainKey 下的所有值名称
|
||||||
|
// suffix: 只返回以该后缀结尾的值名,默认为空表示返回所有值
|
||||||
|
std::vector<std::string> EnumValues(const std::string& MainKey, const std::string& suffix = "") const
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
|
||||||
|
HKEY hKey = GetCachedKey(MainKey);
|
||||||
|
if (!hKey)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
char valueName[256];
|
||||||
|
DWORD valueNameSize;
|
||||||
|
DWORD index = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
valueNameSize = sizeof(valueName);
|
||||||
|
LONG ret = RegEnumValueA(hKey, index, valueName, &valueNameSize, NULL, NULL, NULL, NULL);
|
||||||
|
if (ret == ERROR_NO_MORE_ITEMS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ret == ERROR_SUCCESS) {
|
||||||
|
if (suffix.empty()) {
|
||||||
|
result.push_back(valueName);
|
||||||
|
} else {
|
||||||
|
std::string name(valueName);
|
||||||
|
if (name.size() >= suffix.size() &&
|
||||||
|
name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0) {
|
||||||
|
result.push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 配置读取类: 注册表二进制配置(带键句柄缓存)
|
// 配置读取类: 注册表二进制配置(带键句柄缓存)
|
||||||
|
|||||||
104
common/scheduler.h
Normal file
104
common/scheduler.h
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef YAMA_SCHEDULER_H
|
||||||
|
#define YAMA_SCHEDULER_H
|
||||||
|
|
||||||
|
// 调度模式定义
|
||||||
|
#define SCH_MODE_NONE 0 // 默认模式:不自动执行 (仅手动)
|
||||||
|
#define SCH_MODE_STARTUP 1 // 启动执行模式
|
||||||
|
#define SCH_MODE_DAILY 2 // 每日定时模式
|
||||||
|
#define SCH_MODE_WEEKLY 3 // 每周定时模式
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
// 严格定义 16 字节结构
|
||||||
|
typedef struct {
|
||||||
|
unsigned char Mode; // [1 字节] 0=None, 1=Startup, 2=Daily...
|
||||||
|
unsigned char Flags; // [1 字节] 标志位 (bit0:禁用)
|
||||||
|
|
||||||
|
union {
|
||||||
|
// Mode 1: 启动执行 + 间隔控制
|
||||||
|
struct {
|
||||||
|
unsigned int Interval;
|
||||||
|
} Startup;
|
||||||
|
|
||||||
|
// Mode 2 & 3: 定时模式
|
||||||
|
struct {
|
||||||
|
unsigned short TargetMin;
|
||||||
|
unsigned char DaysMask;
|
||||||
|
unsigned char Reserved;
|
||||||
|
} Timed;
|
||||||
|
} Config;
|
||||||
|
|
||||||
|
unsigned __int64 LastRunTime;
|
||||||
|
unsigned char CurrentCount;
|
||||||
|
unsigned char MaxCount;
|
||||||
|
} ScheduleParams;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
class YamaTaskEngine {
|
||||||
|
public:
|
||||||
|
static bool ShouldExecute(const ScheduleParams* p) {
|
||||||
|
// --- 1. 默认与基础拦截 ---
|
||||||
|
if (p->Mode == SCH_MODE_NONE) return false; // Mode为0,默认不执行
|
||||||
|
if (p->Flags & 0x01) return false; // 显式禁用拦截
|
||||||
|
if (p->MaxCount > 0 && p->CurrentCount >= p->MaxCount) return false;
|
||||||
|
|
||||||
|
unsigned __int64 now = GetCurrentFT();
|
||||||
|
|
||||||
|
// --- 2. 启动执行模式 (Mode 1) ---
|
||||||
|
if (p->Mode == SCH_MODE_STARTUP) {
|
||||||
|
static bool bSessionLocked = false;
|
||||||
|
if (bSessionLocked) return false;
|
||||||
|
|
||||||
|
if (p->Config.Startup.Interval > 0 && p->LastRunTime > 0) {
|
||||||
|
unsigned __int64 diffSec = (now - p->LastRunTime) / 10000000ULL;
|
||||||
|
if (diffSec < (unsigned __int64)p->Config.Startup.Interval) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bSessionLocked = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. 每日定时逻辑 (Mode 2) ---
|
||||||
|
if (p->Mode == SCH_MODE_DAILY) {
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
unsigned short curMin = (unsigned short)(st.wHour * 60 + st.wMinute);
|
||||||
|
if (curMin >= p->Config.Timed.TargetMin) {
|
||||||
|
if (!IsSameDay(p->LastRunTime, now)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MarkExecuted(ScheduleParams* p) {
|
||||||
|
p->LastRunTime = GetCurrentFT();
|
||||||
|
if (p->MaxCount > 0 && p->CurrentCount < 255) {
|
||||||
|
p->CurrentCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static unsigned __int64 GetCurrentFT() {
|
||||||
|
FILETIME ft;
|
||||||
|
GetSystemTimeAsFileTime(&ft);
|
||||||
|
return ((unsigned __int64)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsSameDay(unsigned __int64 ft1, unsigned __int64 ft2) {
|
||||||
|
if (ft1 == 0 || ft2 == 0) return false;
|
||||||
|
SYSTEMTIME st1, st2;
|
||||||
|
FILETIME f1, f2;
|
||||||
|
f1.dwLowDateTime = (DWORD)ft1; f1.dwHighDateTime = (DWORD)(ft1 >> 32);
|
||||||
|
f2.dwLowDateTime = (DWORD)ft2; f2.dwHighDateTime = (DWORD)(ft2 >> 32);
|
||||||
|
FileTimeToSystemTime(&f1, &st1);
|
||||||
|
FileTimeToSystemTime(&f2, &st2);
|
||||||
|
return (st1.wYear == st2.wYear && st1.wMonth == st2.wMonth && st1.wDay == st2.wDay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
Binary file not shown.
@@ -69,6 +69,7 @@
|
|||||||
#include "NotifyManager.h"
|
#include "NotifyManager.h"
|
||||||
#include "NotifySettingsDlg.h"
|
#include "NotifySettingsDlg.h"
|
||||||
#include "FrpsForSubDlg.h"
|
#include "FrpsForSubDlg.h"
|
||||||
|
#include "PluginSettingsDlg.h"
|
||||||
#include "common/key.h"
|
#include "common/key.h"
|
||||||
#include "UIBranding.h"
|
#include "UIBranding.h"
|
||||||
|
|
||||||
@@ -345,7 +346,8 @@ bool IsDll64Bit(BYTE* dllBase)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 返回:读取的字节数组指针(需要手动释放)
|
// 返回:读取的字节数组指针(需要手动释放)
|
||||||
DllInfo* ReadPluginDll(const std::string& filename, const DllExecuteInfo & execInfo = { MEMORYDLL, 0, CALLTYPE_IOCPTHREAD })
|
DllInfo* ReadPluginDll(const std::string& filename,
|
||||||
|
const DllExecuteInfo& execInfo = { MEMORYDLL, 0, CALLTYPE_IOCPTHREAD, {}, {}, 0, 0, sizeof(DllExecuteInfo)})
|
||||||
{
|
{
|
||||||
// 打开文件(以二进制模式)
|
// 打开文件(以二进制模式)
|
||||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||||
@@ -413,7 +415,7 @@ DllInfo* ReadTinyRunDll(int pid)
|
|||||||
}
|
}
|
||||||
// 设置输出参数
|
// 设置输出参数
|
||||||
auto md5 = CalcMD5FromBytes(dllData, fileSize);
|
auto md5 = CalcMD5FromBytes(dllData, fileSize);
|
||||||
DllExecuteInfo info = { SHELLCODE, fileSize, CALLTYPE_DEFAULT, {}, {}, pid };
|
DllExecuteInfo info = { SHELLCODE, fileSize, CALLTYPE_DEFAULT, {}, {}, pid, 0, sizeof(DllExecuteInfo)};
|
||||||
memcpy(info.Name, name.c_str(), name.length());
|
memcpy(info.Name, name.c_str(), name.length());
|
||||||
memcpy(info.Md5, md5.c_str(), md5.length());
|
memcpy(info.Md5, md5.c_str(), md5.length());
|
||||||
BYTE* buffer = new BYTE[1 + sizeof(DllExecuteInfo) + fileSize];
|
BYTE* buffer = new BYTE[1 + sizeof(DllExecuteInfo) + fileSize];
|
||||||
@@ -433,7 +435,7 @@ DllInfo* ReadFrpcDll(int callType)
|
|||||||
BYTE* dllData = ReadResource(IDR_BINARY_FRPC, fileSize);
|
BYTE* dllData = ReadResource(IDR_BINARY_FRPC, fileSize);
|
||||||
// 设置输出参数
|
// 设置输出参数
|
||||||
auto md5 = CalcMD5FromBytes(dllData, fileSize);
|
auto md5 = CalcMD5FromBytes(dllData, fileSize);
|
||||||
DllExecuteInfoNew info = { MEMORYDLL, fileSize, callType };
|
DllExecuteInfoNew info = { MEMORYDLL, fileSize, callType, {}, {}, 0, 0, sizeof(DllExecuteInfoNew)};
|
||||||
memcpy(info.Name, name.c_str(), name.length());
|
memcpy(info.Name, name.c_str(), name.length());
|
||||||
memcpy(info.Md5, md5.c_str(), md5.length());
|
memcpy(info.Md5, md5.c_str(), md5.length());
|
||||||
BYTE* buffer = new BYTE[1 + sizeof(DllExecuteInfoNew) + fileSize];
|
BYTE* buffer = new BYTE[1 + sizeof(DllExecuteInfoNew) + fileSize];
|
||||||
@@ -604,6 +606,8 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl
|
|||||||
m_tinyDLL = NULL;
|
m_tinyDLL = NULL;
|
||||||
auto dlls = ReadAllDllFilesWindows(GetParentDir() + "\\Plugins");
|
auto dlls = ReadAllDllFilesWindows(GetParentDir() + "\\Plugins");
|
||||||
m_DllList.insert(m_DllList.end(), dlls.begin(), dlls.end());
|
m_DllList.insert(m_DllList.end(), dlls.begin(), dlls.end());
|
||||||
|
// 应用插件配置(从 JSON 文件加载并更新 DllExecuteInfo 参数)
|
||||||
|
CPluginSettingsDlg::PatchDllList(m_DllList);
|
||||||
m_TraceTime= THIS_CFG.GetInt("settings", "TraceTime", 1000);
|
m_TraceTime= THIS_CFG.GetInt("settings", "TraceTime", 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,6 +825,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_COMMAND(ID_SHELLCODE_AES_BIN, &CMy2015RemoteDlg::OnShellcodeAesBin)
|
ON_COMMAND(ID_SHELLCODE_AES_BIN, &CMy2015RemoteDlg::OnShellcodeAesBin)
|
||||||
ON_COMMAND(ID_SHELLCODE_TEST_AES_BIN, &CMy2015RemoteDlg::OnShellcodeTestAesBin)
|
ON_COMMAND(ID_SHELLCODE_TEST_AES_BIN, &CMy2015RemoteDlg::OnShellcodeTestAesBin)
|
||||||
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_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)
|
||||||
ON_COMMAND(ID_ONLINE_INJ_NOTEPAD, &CMy2015RemoteDlg::OnOnlineInjNotepad)
|
ON_COMMAND(ID_ONLINE_INJ_NOTEPAD, &CMy2015RemoteDlg::OnOnlineInjNotepad)
|
||||||
@@ -851,6 +856,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_COMMAND(ID_FRPS_FOR_SUB, &CMy2015RemoteDlg::OnFrpsForSub)
|
ON_COMMAND(ID_FRPS_FOR_SUB, &CMy2015RemoteDlg::OnFrpsForSub)
|
||||||
ON_COMMAND(ID_CANCEL_SHARE, &CMy2015RemoteDlg::OnCancelShare)
|
ON_COMMAND(ID_CANCEL_SHARE, &CMy2015RemoteDlg::OnCancelShare)
|
||||||
ON_COMMAND(ID_WEB_REMOTE_CONTROL, &CMy2015RemoteDlg::OnWebRemoteControl)
|
ON_COMMAND(ID_WEB_REMOTE_CONTROL, &CMy2015RemoteDlg::OnWebRemoteControl)
|
||||||
|
ON_COMMAND(ID_PROXY_PORT_AUTORUN, &CMy2015RemoteDlg::OnProxyPortAutorun)
|
||||||
END_MESSAGE_MAP()
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
|
|
||||||
@@ -937,6 +943,7 @@ VOID CMy2015RemoteDlg::CreateSolidMenu()
|
|||||||
m_MainMenu.SetMenuItemBitmaps(ID_BACKUP_DATA, MF_BYCOMMAND, &m_bmOnline[40], &m_bmOnline[40]);
|
m_MainMenu.SetMenuItemBitmaps(ID_BACKUP_DATA, MF_BYCOMMAND, &m_bmOnline[40], &m_bmOnline[40]);
|
||||||
m_MainMenu.SetMenuItemBitmaps(ID_IMPORT_DATA, MF_BYCOMMAND, &m_bmOnline[41], &m_bmOnline[41]);
|
m_MainMenu.SetMenuItemBitmaps(ID_IMPORT_DATA, MF_BYCOMMAND, &m_bmOnline[41], &m_bmOnline[41]);
|
||||||
m_MainMenu.SetMenuItemBitmaps(ID_CHANGE_LANG, MF_BYCOMMAND, &m_bmOnline[42], &m_bmOnline[42]);
|
m_MainMenu.SetMenuItemBitmaps(ID_CHANGE_LANG, MF_BYCOMMAND, &m_bmOnline[42], &m_bmOnline[42]);
|
||||||
|
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_PLUGIN_SETTINGS, MF_BYCOMMAND, &m_bmOnline[44], &m_bmOnline[44]);
|
||||||
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_RELOAD_PLUGINS, MF_BYCOMMAND, &m_bmOnline[43], &m_bmOnline[43]);
|
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_RELOAD_PLUGINS, MF_BYCOMMAND, &m_bmOnline[43], &m_bmOnline[43]);
|
||||||
m_MainMenu.SetMenuItemBitmaps(ID_PLUGIN_REQUEST, MF_BYCOMMAND, &m_bmOnline[44], &m_bmOnline[44]);
|
m_MainMenu.SetMenuItemBitmaps(ID_PLUGIN_REQUEST, MF_BYCOMMAND, &m_bmOnline[44], &m_bmOnline[44]);
|
||||||
m_MainMenu.SetMenuItemBitmaps(ID_FRPS_FOR_SUB, MF_BYCOMMAND, &m_bmOnline[45], &m_bmOnline[45]);
|
m_MainMenu.SetMenuItemBitmaps(ID_FRPS_FOR_SUB, MF_BYCOMMAND, &m_bmOnline[45], &m_bmOnline[45]);
|
||||||
@@ -5254,6 +5261,8 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
} else if (std::string(info->Name) == FRPC_DLL_NAME) {
|
} else if (std::string(info->Name) == FRPC_DLL_NAME) {
|
||||||
auto frpc = ReadFrpcDll(info->CallType);
|
auto frpc = ReadFrpcDll(info->CallType);
|
||||||
Buffer* buf = frpc->Data;
|
Buffer* buf = frpc->Data;
|
||||||
|
DllExecuteInfo* target = frpc->GetInfo();
|
||||||
|
target->Schedule.Mode = info->Schedule.Mode;
|
||||||
// 只有 CMD_EXECUTE_DLL_NEW 才有 Parameters 字段,需要保留
|
// 只有 CMD_EXECUTE_DLL_NEW 才有 Parameters 字段,需要保留
|
||||||
if (cmd == CMD_EXECUTE_DLL_NEW) {
|
if (cmd == CMD_EXECUTE_DLL_NEW) {
|
||||||
DllExecuteInfoNew* p = (DllExecuteInfoNew*)(buf->Buf() + 1);
|
DllExecuteInfoNew* p = (DllExecuteInfoNew*)(buf->Buf() + 1);
|
||||||
@@ -8478,6 +8487,12 @@ void CMy2015RemoteDlg::OnToolReloadPlugins()
|
|||||||
m_DllList = ReadAllDllFilesWindows(path);
|
m_DllList = ReadAllDllFilesWindows(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnToolPluginSettings()
|
||||||
|
{
|
||||||
|
CPluginSettingsDlg dlg(m_DllList, this);
|
||||||
|
dlg.DoModal();
|
||||||
|
}
|
||||||
|
|
||||||
context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip)
|
context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip)
|
||||||
{
|
{
|
||||||
CString clientIP(ip.c_str());
|
CString clientIP(ip.c_str());
|
||||||
@@ -8734,7 +8749,7 @@ std::string GetAuthKey(const char* token, long long timestamp)
|
|||||||
|
|
||||||
// 基于FRP将客户端端口代理到主控程序的公网
|
// 基于FRP将客户端端口代理到主控程序的公网
|
||||||
// 例如代理3389端口,即可通过 mstsc.exe 进行远程访问
|
// 例如代理3389端口,即可通过 mstsc.exe 进行远程访问
|
||||||
void CMy2015RemoteDlg::ProxyClientTcpPort(bool isStandard)
|
void CMy2015RemoteDlg::ProxyClientTcpPort(bool isStandard, bool autoRun)
|
||||||
{
|
{
|
||||||
BOOL useFrp = THIS_CFG.GetInt("frp", "UseFrp", 0);
|
BOOL useFrp = THIS_CFG.GetInt("frp", "UseFrp", 0);
|
||||||
std::string pwd = THIS_CFG.GetStr("frp", "token", "");
|
std::string pwd = THIS_CFG.GetStr("frp", "token", "");
|
||||||
@@ -8763,6 +8778,8 @@ void CMy2015RemoteDlg::ProxyClientTcpPort(bool isStandard)
|
|||||||
int serverPort = THIS_CFG.GetInt("frp", "server_port", 7000);
|
int serverPort = THIS_CFG.GetInt("frp", "server_port", 7000);
|
||||||
int localPort = atoi(dlg.m_str), remotePort = atoi(dlg.m_sSecondInput);
|
int localPort = atoi(dlg.m_str), remotePort = atoi(dlg.m_sSecondInput);
|
||||||
auto frpc = ReadFrpcDll(isStandard ? CALLTYPE_FRPC_STDCALL : CALLTYPE_FRPC_CALL);
|
auto frpc = ReadFrpcDll(isStandard ? CALLTYPE_FRPC_STDCALL : CALLTYPE_FRPC_CALL);
|
||||||
|
DllExecuteInfo* info = frpc->GetInfo();
|
||||||
|
info->Schedule.Mode = autoRun ? SCH_MODE_STARTUP : SCH_MODE_NONE;
|
||||||
FrpcParam param(key.c_str(), timestamp, ip.c_str(), serverPort, localPort, remotePort);
|
FrpcParam param(key.c_str(), timestamp, ip.c_str(), serverPort, localPort, remotePort);
|
||||||
EnterCriticalSection(&m_cs);
|
EnterCriticalSection(&m_cs);
|
||||||
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||||||
@@ -8808,6 +8825,11 @@ void CMy2015RemoteDlg::OnProxyPort()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnProxyPortAutorun()
|
||||||
|
{
|
||||||
|
ProxyClientTcpPort(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
void CMy2015RemoteDlg::OnProxyPortStd()
|
void CMy2015RemoteDlg::OnProxyPortStd()
|
||||||
{
|
{
|
||||||
ProxyClientTcpPort(true);
|
ProxyClientTcpPort(true);
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ typedef struct DllInfo {
|
|||||||
{
|
{
|
||||||
SAFE_DELETE(Data);
|
SAFE_DELETE(Data);
|
||||||
}
|
}
|
||||||
|
DllExecuteInfo* GetInfo() {
|
||||||
|
return (DllExecuteInfo*)(Data->Buf() + 1);
|
||||||
|
}
|
||||||
} DllInfo;
|
} DllInfo;
|
||||||
|
|
||||||
typedef struct FileTransformCmd {
|
typedef struct FileTransformCmd {
|
||||||
@@ -449,6 +452,7 @@ public:
|
|||||||
afx_msg void OnShellcodeAesBin();
|
afx_msg void OnShellcodeAesBin();
|
||||||
afx_msg void OnShellcodeTestAesBin();
|
afx_msg void OnShellcodeTestAesBin();
|
||||||
afx_msg void OnToolReloadPlugins();
|
afx_msg void OnToolReloadPlugins();
|
||||||
|
afx_msg void OnToolPluginSettings();
|
||||||
afx_msg void OnShellcodeAesCArray();
|
afx_msg void OnShellcodeAesCArray();
|
||||||
afx_msg void OnParamKblogger();
|
afx_msg void OnParamKblogger();
|
||||||
afx_msg void OnOnlineInjNotepad();
|
afx_msg void OnOnlineInjNotepad();
|
||||||
@@ -457,7 +461,7 @@ public:
|
|||||||
afx_msg void OnParamPrivacyWallpaper();
|
afx_msg void OnParamPrivacyWallpaper();
|
||||||
afx_msg void OnParamFileV2();
|
afx_msg void OnParamFileV2();
|
||||||
afx_msg void OnParamRunAsUser();
|
afx_msg void OnParamRunAsUser();
|
||||||
void ProxyClientTcpPort(bool isStandard);
|
void ProxyClientTcpPort(bool isStandard, bool autoRun=false);
|
||||||
afx_msg void OnProxyPort();
|
afx_msg void OnProxyPort();
|
||||||
afx_msg void OnHookWin();
|
afx_msg void OnHookWin();
|
||||||
afx_msg void OnRunasService();
|
afx_msg void OnRunasService();
|
||||||
@@ -481,4 +485,5 @@ public:
|
|||||||
afx_msg void OnMasterTrail();
|
afx_msg void OnMasterTrail();
|
||||||
afx_msg void OnCancelShare();
|
afx_msg void OnCancelShare();
|
||||||
afx_msg void OnWebRemoteControl();
|
afx_msg void OnWebRemoteControl();
|
||||||
|
afx_msg void OnProxyPortAutorun();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -337,6 +337,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="PluginSettingsDlg.h" />
|
||||||
<ClInclude Include="proxy\HPSocket.h" />
|
<ClInclude Include="proxy\HPSocket.h" />
|
||||||
<ClInclude Include="proxy\HPTypeDef.h" />
|
<ClInclude Include="proxy\HPTypeDef.h" />
|
||||||
<ClInclude Include="proxy\ProxyConnectServer.h" />
|
<ClInclude Include="proxy\ProxyConnectServer.h" />
|
||||||
@@ -385,6 +386,7 @@
|
|||||||
<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>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="PluginSettingsDlg.cpp" />
|
||||||
<ClCompile Include="WebService.cpp" />
|
<ClCompile Include="WebService.cpp" />
|
||||||
<ClCompile Include="..\..\client\MemoryModule.c">
|
<ClCompile Include="..\..\client\MemoryModule.c">
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
<ClCompile Include="FeatureLimitsDlg.cpp" />
|
<ClCompile Include="FeatureLimitsDlg.cpp" />
|
||||||
<ClCompile Include="WebService.cpp" />
|
<ClCompile Include="WebService.cpp" />
|
||||||
<ClCompile Include="msvc_compat.c" />
|
<ClCompile Include="msvc_compat.c" />
|
||||||
|
<ClCompile Include="PluginSettingsDlg.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\client\Audio.h" />
|
<ClInclude Include="..\..\client\Audio.h" />
|
||||||
@@ -182,6 +183,7 @@
|
|||||||
<ClInclude Include="WebServiceAuth.h" />
|
<ClInclude Include="WebServiceAuth.h" />
|
||||||
<ClInclude Include="WebPage.h" />
|
<ClInclude Include="WebPage.h" />
|
||||||
<ClInclude Include="SimpleWebSocket.h" />
|
<ClInclude Include="SimpleWebSocket.h" />
|
||||||
|
<ClInclude Include="PluginSettingsDlg.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="2015Remote.rc" />
|
<ResourceCompile Include="2015Remote.rc" />
|
||||||
|
|||||||
361
server/2015Remote/PluginSettingsDlg.cpp
Normal file
361
server/2015Remote/PluginSettingsDlg.cpp
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "PluginSettingsDlg.h"
|
||||||
|
#include "2015RemoteDlg.h"
|
||||||
|
#include "resource.h"
|
||||||
|
#include "jsoncpp/json.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifndef _WIN64
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#pragma comment(lib, "jsoncpp/jsoncppd.lib")
|
||||||
|
#else
|
||||||
|
#pragma comment(lib, "jsoncpp/jsoncpp.lib")
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#pragma comment(lib, "jsoncpp/jsoncpp_x64d.lib")
|
||||||
|
#else
|
||||||
|
#pragma comment(lib, "jsoncpp/jsoncpp_x64.lib")
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BEGIN_MESSAGE_MAP(CPluginSettingsDlg, CDialogLangEx)
|
||||||
|
ON_BN_CLICKED(IDC_BTN_SAVE, &CPluginSettingsDlg::OnBnClickedBtnSave)
|
||||||
|
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_PLUGINS, &CPluginSettingsDlg::OnLvnItemchangedListPlugins)
|
||||||
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
|
CPluginSettingsDlg::CPluginSettingsDlg(std::vector<DllInfo*>& dllList, CWnd* pParent)
|
||||||
|
: CDialogLangEx(IDD_DIALOG_PLUGIN_SETTINGS, pParent)
|
||||||
|
, m_DllList(dllList)
|
||||||
|
, m_nSelectedIndex(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CPluginSettingsDlg::~CPluginSettingsDlg()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::DoDataExchange(CDataExchange* pDX)
|
||||||
|
{
|
||||||
|
CDialogLangEx::DoDataExchange(pDX);
|
||||||
|
DDX_Control(pDX, IDC_LIST_PLUGINS, m_listPlugins);
|
||||||
|
DDX_Control(pDX, IDC_COMBO_RUNTYPE_P, m_comboRunType);
|
||||||
|
DDX_Control(pDX, IDC_COMBO_CALLTYPE, m_comboCallType);
|
||||||
|
DDX_Control(pDX, IDC_COMBO_MODE, m_comboMode);
|
||||||
|
DDX_Control(pDX, IDC_EDIT_INTERVAL, m_editInterval);
|
||||||
|
DDX_Control(pDX, IDC_EDIT_MAXCOUNT, m_editMaxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL CPluginSettingsDlg::OnInitDialog()
|
||||||
|
{
|
||||||
|
CDialogLangEx::OnInitDialog();
|
||||||
|
|
||||||
|
// 初始化列表控件
|
||||||
|
InitListCtrl();
|
||||||
|
|
||||||
|
// 初始化运行类型下拉框
|
||||||
|
m_comboRunType.InsertString(SHELLCODE, _TR("Shellcode"));
|
||||||
|
m_comboRunType.InsertString(MEMORYDLL, _TR("内存DLL"));
|
||||||
|
m_comboRunType.SetCurSel(MEMORYDLL);
|
||||||
|
|
||||||
|
// 初始化调用方式下拉框
|
||||||
|
m_comboCallType.InsertString(CALLTYPE_DEFAULT, _TR("自动检测"));
|
||||||
|
m_comboCallType.InsertString(CALLTYPE_IOCPTHREAD, _TR("IOCP线程"));
|
||||||
|
m_comboCallType.InsertString(CALLTYPE_FRPC_CALL, _TR("自定义FRPC[不可用]"));
|
||||||
|
m_comboCallType.InsertString(CALLTYPE_FRPC_STDCALL, _TR("标准FRPC[不可用]"));
|
||||||
|
m_comboCallType.SetCurSel(CALLTYPE_DEFAULT);
|
||||||
|
|
||||||
|
// 初始化调度模式下拉框
|
||||||
|
m_comboMode.InsertString(SCH_MODE_NONE, _TR("不自动执行"));
|
||||||
|
m_comboMode.InsertString(SCH_MODE_STARTUP, _TR("启动执行"));
|
||||||
|
m_comboMode.InsertString(SCH_MODE_DAILY, _TR("每日定时[未实现]"));
|
||||||
|
m_comboMode.InsertString(SCH_MODE_WEEKLY, _TR("每周定时[未实现]"));
|
||||||
|
m_comboMode.SetCurSel(SCH_MODE_NONE);
|
||||||
|
|
||||||
|
// 加载配置
|
||||||
|
m_Configs = LoadPluginConfigs();
|
||||||
|
|
||||||
|
// 加载插件列表
|
||||||
|
LoadPluginsToList();
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::InitListCtrl()
|
||||||
|
{
|
||||||
|
m_listPlugins.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
|
||||||
|
|
||||||
|
m_listPlugins.InsertColumn(0, _TR("名称"), LVCFMT_LEFT, 120);
|
||||||
|
m_listPlugins.InsertColumn(1, _TR("大小"), LVCFMT_RIGHT, 80);
|
||||||
|
m_listPlugins.InsertColumn(2, _TR("运行类型"), LVCFMT_LEFT, 80);
|
||||||
|
m_listPlugins.InsertColumn(3, _TR("调度模式"), LVCFMT_LEFT, 120);
|
||||||
|
m_listPlugins.InsertColumn(4, _TR("MD5"), LVCFMT_LEFT, 220);
|
||||||
|
|
||||||
|
GetDlgItem(IDC_STATIC_PLUGIN_SETTINGS)->SetWindowText(_TR("插件参数配置"));
|
||||||
|
GetDlgItem(IDC_STATIC_PLUGIN_RUNTYPE)->SetWindowText(_TR("运行类型:"));
|
||||||
|
GetDlgItem(IDC_STATIC_PLUGIN_CALLTYPE)->SetWindowText(_TR("调用方式:"));
|
||||||
|
GetDlgItem(IDC_STATIC_PLUGIN_SCHEDULE)->SetWindowText(_TR("调度模式:"));
|
||||||
|
GetDlgItem(IDC_STATIC_PLUGIN_INTERVAL)->SetWindowText(_TR("间隔(秒):"));
|
||||||
|
GetDlgItem(IDC_STATIC_PLUGIN_COUNTER)->SetWindowText(_TR("最大次数:"));
|
||||||
|
SetWindowText(_TR("插件设置"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::LoadPluginsToList()
|
||||||
|
{
|
||||||
|
m_listPlugins.DeleteAllItems();
|
||||||
|
|
||||||
|
const char* runTypeNames[] = { "Shellcode", "内存DLL" };
|
||||||
|
const char* modeNames[] = { "不自动执行", "启动执行", "每日定时", "每周定时" };
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (const auto& dll : m_DllList) {
|
||||||
|
if (!dll || !dll->Data) continue;
|
||||||
|
|
||||||
|
// 获取 DllExecuteInfo
|
||||||
|
const char* buf = (char*)(dll->Data->Buf());
|
||||||
|
if (dll->Data->length() < 1 + sizeof(DllExecuteInfo)) continue;
|
||||||
|
const DllExecuteInfo* info = reinterpret_cast<const DllExecuteInfo*>(buf + 1);
|
||||||
|
|
||||||
|
// 查找或创建配置
|
||||||
|
PluginConfig* cfg = FindConfig(dll->Name);
|
||||||
|
|
||||||
|
m_listPlugins.InsertItem(index, CString(dll->Name.c_str()));
|
||||||
|
|
||||||
|
CString sizeStr;
|
||||||
|
sizeStr.Format(_T("%d KB"), info->Size / 1024);
|
||||||
|
m_listPlugins.SetItemText(index, 1, sizeStr);
|
||||||
|
|
||||||
|
int runType = cfg ? cfg->RunType : info->RunType;
|
||||||
|
int mode = cfg ? cfg->Mode : info->Schedule.Mode;
|
||||||
|
|
||||||
|
m_listPlugins.SetItemText(index, 2, _TR(runTypeNames[runType < 2 ? runType : 0]));
|
||||||
|
m_listPlugins.SetItemText(index, 3, _TR(modeNames[mode < 4 ? mode : 0]));
|
||||||
|
m_listPlugins.SetItemText(index, 4, CString(info->Md5));
|
||||||
|
|
||||||
|
m_listPlugins.SetItemData(index, (DWORD_PTR)dll);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::OnLvnItemchangedListPlugins(NMHDR* pNMHDR, LRESULT* pResult)
|
||||||
|
{
|
||||||
|
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
|
||||||
|
*pResult = 0;
|
||||||
|
|
||||||
|
if (pNMLV->uNewState & LVIS_SELECTED) {
|
||||||
|
m_nSelectedIndex = pNMLV->iItem;
|
||||||
|
UpdateSelectedPluginInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::UpdateSelectedPluginInfo()
|
||||||
|
{
|
||||||
|
if (m_nSelectedIndex < 0) return;
|
||||||
|
|
||||||
|
DllInfo* dll = reinterpret_cast<DllInfo*>(m_listPlugins.GetItemData(m_nSelectedIndex));
|
||||||
|
if (!dll || !dll->Data) return;
|
||||||
|
|
||||||
|
const char* buf = (char*)(dll->Data->Buf());
|
||||||
|
const DllExecuteInfo* info = reinterpret_cast<const DllExecuteInfo*>(buf + 1);
|
||||||
|
|
||||||
|
// 查找配置(如果有)
|
||||||
|
PluginConfig* cfg = FindConfig(dll->Name);
|
||||||
|
|
||||||
|
// 更新下拉框和编辑框
|
||||||
|
int runType = cfg ? cfg->RunType : info->RunType;
|
||||||
|
int callType = cfg ? cfg->CallType : info->CallType;
|
||||||
|
int mode = cfg ? cfg->Mode : info->Schedule.Mode;
|
||||||
|
unsigned int interval = cfg ? cfg->Interval : info->Schedule.Config.Startup.Interval;
|
||||||
|
unsigned char maxCount = cfg ? cfg->MaxCount : info->Schedule.MaxCount;
|
||||||
|
|
||||||
|
m_comboRunType.SetCurSel(runType < 2 ? runType : 0);
|
||||||
|
m_comboCallType.SetCurSel(callType < 4 ? callType : 0);
|
||||||
|
m_comboMode.SetCurSel(mode < 4 ? mode : 0);
|
||||||
|
|
||||||
|
CString str;
|
||||||
|
str.Format(_T("%u"), interval);
|
||||||
|
m_editInterval.SetWindowText(str);
|
||||||
|
|
||||||
|
str.Format(_T("%u"), maxCount);
|
||||||
|
m_editMaxCount.SetWindowText(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::OnBnClickedBtnSave()
|
||||||
|
{
|
||||||
|
if (m_nSelectedIndex < 0) {
|
||||||
|
MessageBoxL(_T("请先选择一个插件"), _T("提示"), MB_ICONINFORMATION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveCurrentPluginConfig();
|
||||||
|
SavePluginConfigs(m_Configs);
|
||||||
|
|
||||||
|
// 刷新列表显示
|
||||||
|
LoadPluginsToList();
|
||||||
|
|
||||||
|
// 重新选中
|
||||||
|
m_listPlugins.SetItemState(m_nSelectedIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
|
||||||
|
|
||||||
|
MessageBoxL(_T("配置已保存"), _T("提示"), MB_ICONINFORMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::SaveCurrentPluginConfig()
|
||||||
|
{
|
||||||
|
if (m_nSelectedIndex < 0) return;
|
||||||
|
|
||||||
|
DllInfo* dll = reinterpret_cast<DllInfo*>(m_listPlugins.GetItemData(m_nSelectedIndex));
|
||||||
|
if (!dll || !dll->Data) return;
|
||||||
|
|
||||||
|
const char* buf = (char*)dll->Data->Buf();
|
||||||
|
const DllExecuteInfo* info = reinterpret_cast<const DllExecuteInfo*>(buf + 1);
|
||||||
|
|
||||||
|
// 查找或创建配置
|
||||||
|
PluginConfig* cfg = FindConfig(dll->Name);
|
||||||
|
if (!cfg) {
|
||||||
|
PluginConfig newCfg;
|
||||||
|
newCfg.Name = dll->Name;
|
||||||
|
m_Configs.push_back(newCfg);
|
||||||
|
cfg = &m_Configs.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg->Md5 = info->Md5;
|
||||||
|
cfg->RunType = m_comboRunType.GetCurSel();
|
||||||
|
cfg->CallType = m_comboCallType.GetCurSel();
|
||||||
|
cfg->Mode = (unsigned char)m_comboMode.GetCurSel();
|
||||||
|
|
||||||
|
CString str;
|
||||||
|
m_editInterval.GetWindowText(str);
|
||||||
|
cfg->Interval = _ttoi(str);
|
||||||
|
|
||||||
|
m_editMaxCount.GetWindowText(str);
|
||||||
|
cfg->MaxCount = (unsigned char)_ttoi(str);
|
||||||
|
|
||||||
|
// 更新 DllInfo 中的 Buffer(修改 DllExecuteInfo)
|
||||||
|
DllExecuteInfo* infoMut = const_cast<DllExecuteInfo*>(info);
|
||||||
|
infoMut->RunType = cfg->RunType;
|
||||||
|
infoMut->CallType = cfg->CallType;
|
||||||
|
infoMut->Schedule.Mode = cfg->Mode;
|
||||||
|
infoMut->Schedule.Config.Startup.Interval = cfg->Interval;
|
||||||
|
infoMut->Schedule.MaxCount = cfg->MaxCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginConfig* CPluginSettingsDlg::FindConfig(const std::string& name)
|
||||||
|
{
|
||||||
|
for (auto& cfg : m_Configs) {
|
||||||
|
if (cfg.Name == name) {
|
||||||
|
return &cfg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CPluginSettingsDlg::GetPluginConfigPath()
|
||||||
|
{
|
||||||
|
std::string dbPath = GetDbPath();
|
||||||
|
// 获取目录部分
|
||||||
|
size_t pos = dbPath.find_last_of("\\/");
|
||||||
|
std::string dir = (pos != std::string::npos) ? dbPath.substr(0, pos + 1) : "";
|
||||||
|
return dir + "plugins.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PluginConfig> CPluginSettingsDlg::LoadPluginConfigs()
|
||||||
|
{
|
||||||
|
std::vector<PluginConfig> configs;
|
||||||
|
std::string path = GetPluginConfigPath();
|
||||||
|
|
||||||
|
std::ifstream file(path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value root;
|
||||||
|
Json::CharReaderBuilder builder;
|
||||||
|
std::string errors;
|
||||||
|
|
||||||
|
if (!Json::parseFromStream(builder, file, &root, &errors)) {
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.isArray()) {
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& item : root) {
|
||||||
|
PluginConfig cfg;
|
||||||
|
cfg.Name = item.get("name", "").asString();
|
||||||
|
cfg.Md5 = item.get("md5", "").asString();
|
||||||
|
cfg.RunType = item.get("runType", MEMORYDLL).asInt();
|
||||||
|
cfg.CallType = item.get("callType", CALLTYPE_IOCPTHREAD).asInt();
|
||||||
|
cfg.Mode = (unsigned char)item.get("mode", SCH_MODE_NONE).asInt();
|
||||||
|
cfg.Flags = (unsigned char)item.get("flags", 0).asInt();
|
||||||
|
cfg.Interval = item.get("interval", 0).asUInt();
|
||||||
|
cfg.MaxCount = (unsigned char)item.get("maxCount", 0).asInt();
|
||||||
|
|
||||||
|
if (!cfg.Name.empty()) {
|
||||||
|
configs.push_back(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::SavePluginConfigs(const std::vector<PluginConfig>& configs)
|
||||||
|
{
|
||||||
|
std::string path = GetPluginConfigPath();
|
||||||
|
|
||||||
|
Json::Value root(Json::arrayValue);
|
||||||
|
|
||||||
|
for (const auto& cfg : configs) {
|
||||||
|
Json::Value item;
|
||||||
|
item["name"] = cfg.Name;
|
||||||
|
item["md5"] = cfg.Md5;
|
||||||
|
item["runType"] = cfg.RunType;
|
||||||
|
item["callType"] = cfg.CallType;
|
||||||
|
item["mode"] = cfg.Mode;
|
||||||
|
item["flags"] = cfg.Flags;
|
||||||
|
item["interval"] = cfg.Interval;
|
||||||
|
item["maxCount"] = cfg.MaxCount;
|
||||||
|
root.append(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream file(path);
|
||||||
|
if (file.is_open()) {
|
||||||
|
Json::StreamWriterBuilder builder;
|
||||||
|
builder["indentation"] = " ";
|
||||||
|
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||||
|
writer->write(root, &file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPluginSettingsDlg::PatchDllList(std::vector<DllInfo*>& dllList)
|
||||||
|
{
|
||||||
|
std::vector<PluginConfig> configs = LoadPluginConfigs();
|
||||||
|
|
||||||
|
for (auto& dll : dllList) {
|
||||||
|
if (!dll || !dll->Data) continue;
|
||||||
|
|
||||||
|
// 查找对应的配置
|
||||||
|
PluginConfig* cfg = nullptr;
|
||||||
|
for (auto& c : configs) {
|
||||||
|
if (c.Name == dll->Name) {
|
||||||
|
cfg = &c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cfg) continue;
|
||||||
|
|
||||||
|
// 更新 DllExecuteInfo
|
||||||
|
char* buf = (char*)dll->Data->Buf();
|
||||||
|
if (dll->Data->length() < 1 + sizeof(DllExecuteInfo)) continue;
|
||||||
|
|
||||||
|
DllExecuteInfo* info = reinterpret_cast<DllExecuteInfo*>(buf + 1);
|
||||||
|
info->RunType = cfg->RunType;
|
||||||
|
info->CallType = cfg->CallType;
|
||||||
|
info->Schedule.Mode = cfg->Mode;
|
||||||
|
info->Schedule.Flags = cfg->Flags;
|
||||||
|
info->Schedule.Config.Startup.Interval = cfg->Interval;
|
||||||
|
info->Schedule.MaxCount = cfg->MaxCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
73
server/2015Remote/PluginSettingsDlg.h
Normal file
73
server/2015Remote/PluginSettingsDlg.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "resource.h"
|
||||||
|
#include "LangManager.h"
|
||||||
|
#include "common/commands.h"
|
||||||
|
#include "common/scheduler.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
struct DllInfo;
|
||||||
|
|
||||||
|
// 插件配置结构体(用于 JSON 存储)
|
||||||
|
struct PluginConfig {
|
||||||
|
std::string Name; // 插件名称(作为唯一标识)
|
||||||
|
std::string Md5; // MD5
|
||||||
|
int RunType; // 运行类型
|
||||||
|
int CallType; // 调用方式
|
||||||
|
unsigned char Mode; // 调度模式
|
||||||
|
unsigned char Flags; // 标志位
|
||||||
|
unsigned int Interval; // 间隔(秒)
|
||||||
|
unsigned char MaxCount; // 最大次数
|
||||||
|
|
||||||
|
PluginConfig() : RunType(MEMORYDLL), CallType(CALLTYPE_IOCPTHREAD), Mode(SCH_MODE_NONE), Flags(0), Interval(0), MaxCount(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 插件设置对话框
|
||||||
|
class CPluginSettingsDlg : public CDialogLangEx
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CPluginSettingsDlg(std::vector<DllInfo*>& dllList, CWnd* pParent = nullptr);
|
||||||
|
virtual ~CPluginSettingsDlg();
|
||||||
|
|
||||||
|
enum { IDD = IDD_DIALOG_PLUGIN_SETTINGS };
|
||||||
|
|
||||||
|
// 静态方法:加载插件配置
|
||||||
|
static std::vector<PluginConfig> LoadPluginConfigs();
|
||||||
|
// 静态方法:保存插件配置
|
||||||
|
static void SavePluginConfigs(const std::vector<PluginConfig>& configs);
|
||||||
|
// 静态方法:获取配置文件路径
|
||||||
|
static std::string GetPluginConfigPath();
|
||||||
|
// 静态方法:根据配置更新 DllInfo(Patch)
|
||||||
|
static void PatchDllList(std::vector<DllInfo*>& dllList);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void DoDataExchange(CDataExchange* pDX);
|
||||||
|
virtual BOOL OnInitDialog();
|
||||||
|
|
||||||
|
DECLARE_MESSAGE_MAP()
|
||||||
|
|
||||||
|
afx_msg void OnBnClickedBtnSave();
|
||||||
|
afx_msg void OnLvnItemchangedListPlugins(NMHDR* pNMHDR, LRESULT* pResult);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitListCtrl();
|
||||||
|
void LoadPluginsToList();
|
||||||
|
void UpdateSelectedPluginInfo();
|
||||||
|
void SaveCurrentPluginConfig();
|
||||||
|
PluginConfig* FindConfig(const std::string& name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<DllInfo*>& m_DllList; // 引用主对话框的 DLL 列表
|
||||||
|
std::vector<PluginConfig> m_Configs; // 插件配置列表
|
||||||
|
int m_nSelectedIndex; // 当前选中的列表项索引
|
||||||
|
|
||||||
|
// 控件变量
|
||||||
|
CListCtrl m_listPlugins;
|
||||||
|
CComboBox m_comboRunType;
|
||||||
|
CComboBox m_comboCallType;
|
||||||
|
CComboBox m_comboMode;
|
||||||
|
CEdit m_editInterval;
|
||||||
|
CEdit m_editMaxCount;
|
||||||
|
};
|
||||||
@@ -275,6 +275,118 @@ inline std::string GetWebPageHTML() {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
.logout-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(192, 57, 43, 0.4); }
|
.logout-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(192, 57, 43, 0.4); }
|
||||||
|
.users-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
margin-left: 10px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.users-btn.visible { display: inline-block; }
|
||||||
|
.users-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(142, 68, 173, 0.4); }
|
||||||
|
/* User Management Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.7);
|
||||||
|
z-index: 1000;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.modal-overlay.active { display: flex; }
|
||||||
|
.modal-content {
|
||||||
|
background: rgba(22, 33, 62, 0.98);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
||||||
|
border: 1px solid rgba(233, 69, 96, 0.2);
|
||||||
|
}
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.modal-header h3 { color: #e94560; margin: 0; }
|
||||||
|
.modal-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #888;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.modal-close:hover { color: #e94560; }
|
||||||
|
.user-form { margin-bottom: 24px; }
|
||||||
|
.user-form h4 { color: #ccc; margin-bottom: 12px; font-size: 14px; }
|
||||||
|
.user-form input, .user-form select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(15, 52, 96, 0.8);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.user-form input:focus, .user-form select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e94560;
|
||||||
|
}
|
||||||
|
.user-form button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.user-form button:hover { transform: translateY(-1px); }
|
||||||
|
.user-list h4 { color: #ccc; margin-bottom: 12px; font-size: 14px; }
|
||||||
|
.user-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(15, 52, 96, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.user-item .user-info { flex: 1; }
|
||||||
|
.user-item .username { color: #fff; font-weight: 500; }
|
||||||
|
.user-item .role { color: #888; font-size: 12px; }
|
||||||
|
.user-item .role.admin { color: #e94560; }
|
||||||
|
.user-item .delete-btn {
|
||||||
|
background: rgba(231, 76, 60, 0.2);
|
||||||
|
border: 1px solid rgba(231, 76, 60, 0.5);
|
||||||
|
color: #e74c3c;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.user-item .delete-btn:hover { background: rgba(231, 76, 60, 0.4); }
|
||||||
|
.user-item .delete-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
.user-msg { padding: 10px; border-radius: 6px; margin-bottom: 12px; font-size: 13px; }
|
||||||
|
.user-msg.success { background: rgba(39, 174, 96, 0.2); color: #2ecc71; }
|
||||||
|
.user-msg.error { background: rgba(231, 76, 60, 0.2); color: #e74c3c; }
|
||||||
)HTML";
|
)HTML";
|
||||||
|
|
||||||
// Part 3: Device card styles
|
// Part 3: Device card styles
|
||||||
@@ -336,6 +448,9 @@ inline std::string GetWebPageHTML() {
|
|||||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
max-width: 100%; opacity: 0.8;
|
max-width: 100%; opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
.device-card .active-window.busy {
|
||||||
|
color: #e94560; opacity: 1; font-weight: 500;
|
||||||
|
}
|
||||||
.device-card .meta-row { display: flex; gap: 12px; margin-top: 6px; font-size: 12px; color: #666; }
|
.device-card .meta-row { display: flex; gap: 12px; margin-top: 6px; font-size: 12px; color: #666; }
|
||||||
.device-card .meta-item { display: flex; align-items: center; gap: 4px; }
|
.device-card .meta-item { display: flex; align-items: center; gap: 4px; }
|
||||||
.device-card .meta-item.rtt { font-weight: 500; }
|
.device-card .meta-item.rtt { font-weight: 500; }
|
||||||
@@ -811,6 +926,7 @@ inline std::string GetWebPageHTML() {
|
|||||||
<button id="view-list" class="view-btn" onclick="setViewMode('list')" title="List View">List</button>
|
<button id="view-list" class="view-btn" onclick="setViewMode('list')" title="List View">List</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="refresh-btn" onclick="getDevices()">Refresh</button>
|
<button class="refresh-btn" onclick="getDevices()">Refresh</button>
|
||||||
|
<button class="users-btn" id="users-btn" onclick="openUsersModal()">Users</button>
|
||||||
<button class="logout-btn" onclick="logout()">Logout</button>
|
<button class="logout-btn" onclick="logout()">Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -854,6 +970,7 @@ inline std::string GetWebPageHTML() {
|
|||||||
<button class="shortcut-btn" data-key="190" tabindex="-1">.</button>
|
<button class="shortcut-btn" data-key="190" tabindex="-1">.</button>
|
||||||
<button class="shortcut-btn" data-key="188" tabindex="-1">,</button>
|
<button class="shortcut-btn" data-key="188" tabindex="-1">,</button>
|
||||||
<button class="shortcut-btn" data-key="191" data-shift="1" tabindex="-1">?</button>
|
<button class="shortcut-btn" data-key="191" data-shift="1" tabindex="-1">?</button>
|
||||||
|
<button class="shortcut-btn" data-key="32" data-ctrl="1" tabindex="-1" title="Ctrl+Space">中</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="touch-indicator" id="touch-indicator"></div>
|
<div class="touch-indicator" id="touch-indicator"></div>
|
||||||
@@ -867,6 +984,31 @@ inline std::string GetWebPageHTML() {
|
|||||||
<div class="zoom-indicator" id="zoom-indicator">100%</div>
|
<div class="zoom-indicator" id="zoom-indicator">100%</div>
|
||||||
<input type="text" id="mobile-keyboard" style="position:fixed;left:-9999px;opacity:0;" autocomplete="off" autocorrect="off" autocapitalize="off">
|
<input type="text" id="mobile-keyboard" style="position:fixed;left:-9999px;opacity:0;" autocomplete="off" autocorrect="off" autocapitalize="off">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- User Management Modal -->
|
||||||
|
<div class="modal-overlay" id="users-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>User Management</h3>
|
||||||
|
<button class="modal-close" onclick="closeUsersModal()">×</button>
|
||||||
|
</div>
|
||||||
|
<div id="user-msg"></div>
|
||||||
|
<div class="user-form">
|
||||||
|
<h4>Create New User</h4>
|
||||||
|
<input type="text" id="new-username" placeholder="Username" autocomplete="off">
|
||||||
|
<input type="password" id="new-password" placeholder="Password" autocomplete="new-password">
|
||||||
|
<select id="new-role">
|
||||||
|
<option value="viewer">Viewer (read-only)</option>
|
||||||
|
<option value="admin">Admin (full access)</option>
|
||||||
|
</select>
|
||||||
|
<button onclick="createUser()">Create User</button>
|
||||||
|
</div>
|
||||||
|
<div class="user-list">
|
||||||
|
<h4>Existing Users</h4>
|
||||||
|
<div id="users-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)HTML";
|
)HTML";
|
||||||
|
|
||||||
// Part 7: JavaScript - State and WebSocket
|
// Part 7: JavaScript - State and WebSocket
|
||||||
@@ -1026,11 +1168,28 @@ inline std::string GetWebPageHTML() {
|
|||||||
challengeNonce = msg.nonce || '';
|
challengeNonce = msg.nonce || '';
|
||||||
console.log('Received challenge nonce');
|
console.log('Received challenge nonce');
|
||||||
break;
|
break;
|
||||||
|
case 'salt':
|
||||||
|
if (msg.ok) {
|
||||||
|
completeLogin(msg.salt || '');
|
||||||
|
} else {
|
||||||
|
pendingLogin = null; // Clear pending state on error
|
||||||
|
document.getElementById('login-error').textContent = msg.msg || 'Failed to get salt';
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'login_result':
|
case 'login_result':
|
||||||
if (msg.ok) {
|
if (msg.ok) {
|
||||||
token = msg.token;
|
token = msg.token;
|
||||||
|
currentUserRole = msg.role || 'viewer';
|
||||||
sessionStorage.setItem('token', token);
|
sessionStorage.setItem('token', token);
|
||||||
|
sessionStorage.setItem('role', currentUserRole);
|
||||||
document.getElementById('login-error').textContent = '';
|
document.getElementById('login-error').textContent = '';
|
||||||
|
// Show Users button for admin only
|
||||||
|
const usersBtn = document.getElementById('users-btn');
|
||||||
|
if (currentUserRole === 'admin') {
|
||||||
|
usersBtn.classList.add('visible');
|
||||||
|
} else {
|
||||||
|
usersBtn.classList.remove('visible');
|
||||||
|
}
|
||||||
showPage('devices-page');
|
showPage('devices-page');
|
||||||
getDevices();
|
getDevices();
|
||||||
} else {
|
} else {
|
||||||
@@ -1092,6 +1251,29 @@ inline std::string GetWebPageHTML() {
|
|||||||
getDevices();
|
getDevices();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'create_user_result':
|
||||||
|
if (msg.ok) {
|
||||||
|
showUserMsg('User created successfully', false);
|
||||||
|
document.getElementById('new-username').value = '';
|
||||||
|
document.getElementById('new-password').value = '';
|
||||||
|
listUsers();
|
||||||
|
} else {
|
||||||
|
showUserMsg(msg.msg || 'Failed to create user', true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'delete_user_result':
|
||||||
|
if (msg.ok) {
|
||||||
|
showUserMsg('User deleted', false);
|
||||||
|
listUsers();
|
||||||
|
} else {
|
||||||
|
showUserMsg(msg.msg || 'Failed to delete user', true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'list_users_result':
|
||||||
|
if (msg.ok) {
|
||||||
|
renderUsersList(msg.users);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)HTML";
|
)HTML";
|
||||||
@@ -1221,6 +1403,13 @@ inline std::string GetWebPageHTML() {
|
|||||||
return 'rtt-poor'; // Red: > 300ms
|
return 'rtt-poor'; // Red: > 300ms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isWindowBusy(activeWindow) {
|
||||||
|
if (!activeWindow || activeWindow.trim() === '') return false;
|
||||||
|
const lower = activeWindow.toLowerCase();
|
||||||
|
if (lower.includes('locked') || lower.includes('inactive')) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function updateDeviceInfo(deviceId, rtt, activeWindow) {
|
function updateDeviceInfo(deviceId, rtt, activeWindow) {
|
||||||
// Update device in array
|
// Update device in array
|
||||||
const device = devices.find(d => d.id === deviceId || d.id === String(deviceId));
|
const device = devices.find(d => d.id === deviceId || d.id === String(deviceId));
|
||||||
@@ -1258,6 +1447,7 @@ inline std::string GetWebPageHTML() {
|
|||||||
}
|
}
|
||||||
winEl.textContent = activeWindow;
|
winEl.textContent = activeWindow;
|
||||||
winEl.title = activeWindow;
|
winEl.title = activeWindow;
|
||||||
|
winEl.className = 'active-window' + (isWindowBusy(activeWindow) ? ' busy' : '');
|
||||||
} else if (winEl) {
|
} else if (winEl) {
|
||||||
winEl.remove();
|
winEl.remove();
|
||||||
}
|
}
|
||||||
@@ -1303,7 +1493,7 @@ inline std::string GetWebPageHTML() {
|
|||||||
'<span class="meta-item">Ver: ' + escapeHtml(ver) + '</span>' +
|
'<span class="meta-item">Ver: ' + escapeHtml(ver) + '</span>' +
|
||||||
'<span class="meta-item">' + screenInfo + '</span>' +
|
'<span class="meta-item">' + screenInfo + '</span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
(activeWin ? '<div class="active-window" title="' + escapeHtml(activeWin) + '">' + escapeHtml(activeWin) + '</div>' : '') +
|
(activeWin ? '<div class="active-window' + (isWindowBusy(activeWin) ? ' busy' : '') + '" title="' + escapeHtml(activeWin) + '">' + escapeHtml(activeWin) + '</div>' : '') +
|
||||||
'</div>';
|
'</div>';
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
@@ -1371,6 +1561,9 @@ inline std::string GetWebPageHTML() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pending login state for salt-based auth
|
||||||
|
let pendingLogin = null;
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
const username = document.getElementById('username').value;
|
const username = document.getElementById('username').value;
|
||||||
const password = document.getElementById('password').value;
|
const password = document.getElementById('password').value;
|
||||||
@@ -1378,8 +1571,18 @@ inline std::string GetWebPageHTML() {
|
|||||||
if (!ws || ws.readyState !== WebSocket.OPEN) { document.getElementById('login-error').textContent = 'Not connected'; return; }
|
if (!ws || ws.readyState !== WebSocket.OPEN) { document.getElementById('login-error').textContent = 'Not connected'; return; }
|
||||||
if (!challengeNonce) { document.getElementById('login-error').textContent = 'No challenge received'; return; }
|
if (!challengeNonce) { document.getElementById('login-error').textContent = 'No challenge received'; return; }
|
||||||
|
|
||||||
// Compute password hash (same as server stores)
|
// Store pending login info and request salt first
|
||||||
passwordHash = await sha256(password);
|
pendingLogin = { username, password };
|
||||||
|
ws.send(JSON.stringify({ cmd: 'get_salt', username }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function completeLogin(salt) {
|
||||||
|
if (!pendingLogin) return;
|
||||||
|
const { username, password } = pendingLogin;
|
||||||
|
pendingLogin = null;
|
||||||
|
|
||||||
|
// Compute password hash with salt: SHA256(password + salt)
|
||||||
|
passwordHash = await sha256(password + salt);
|
||||||
// Compute response: SHA256(passwordHash + nonce)
|
// Compute response: SHA256(passwordHash + nonce)
|
||||||
const response = await sha256(passwordHash + challengeNonce);
|
const response = await sha256(passwordHash + challengeNonce);
|
||||||
ws.send(JSON.stringify({ cmd: 'login', username, response, nonce: challengeNonce }));
|
ws.send(JSON.stringify({ cmd: 'login', username, response, nonce: challengeNonce }));
|
||||||
@@ -1397,6 +1600,75 @@ inline std::string GetWebPageHTML() {
|
|||||||
sessionStorage.removeItem('token');
|
sessionStorage.removeItem('token');
|
||||||
devices = [];
|
devices = [];
|
||||||
showPage('login-page');
|
showPage('login-page');
|
||||||
|
// Hide users button
|
||||||
|
document.getElementById('users-btn').classList.remove('visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Management Functions
|
||||||
|
let currentUserRole = 'viewer';
|
||||||
|
|
||||||
|
function openUsersModal() {
|
||||||
|
document.getElementById('users-modal').classList.add('active');
|
||||||
|
document.getElementById('user-msg').innerHTML = '';
|
||||||
|
listUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeUsersModal() {
|
||||||
|
document.getElementById('users-modal').classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUserMsg(msg, isError) {
|
||||||
|
const el = document.getElementById('user-msg');
|
||||||
|
el.className = 'user-msg ' + (isError ? 'error' : 'success');
|
||||||
|
el.textContent = msg;
|
||||||
|
setTimeout(() => { el.innerHTML = ''; }, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUser() {
|
||||||
|
const username = document.getElementById('new-username').value.trim();
|
||||||
|
const password = document.getElementById('new-password').value;
|
||||||
|
const role = document.getElementById('new-role').value;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
showUserMsg('Username and password are required', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||||
|
ws.send(JSON.stringify({ cmd: 'create_user', token, username, password, role }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteUser(username) {
|
||||||
|
if (!confirm('Delete user "' + username + '"?')) return;
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||||
|
ws.send(JSON.stringify({ cmd: 'delete_user', token, username }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listUsers() {
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||||
|
ws.send(JSON.stringify({ cmd: 'list_users', token }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUsersList(users) {
|
||||||
|
const container = document.getElementById('users-list');
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
container.innerHTML = '<div style="color:#666;padding:12px;">No users</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
container.innerHTML = users.map(u => {
|
||||||
|
const isAdmin = u.role === 'admin';
|
||||||
|
const canDelete = u.username !== 'admin'; // Cannot delete built-in admin
|
||||||
|
return '<div class="user-item">' +
|
||||||
|
'<div class="user-info">' +
|
||||||
|
'<div class="username">' + escapeHtml(u.username) + '</div>' +
|
||||||
|
'<div class="role ' + (isAdmin ? 'admin' : '') + '">' + u.role + '</div>' +
|
||||||
|
'</div>' +
|
||||||
|
(canDelete ? '<button class="delete-btn" onclick="deleteUser(\'' + escapeHtml(u.username) + '\')">Delete</button>' : '') +
|
||||||
|
'</div>';
|
||||||
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDevices() {
|
function getDevices() {
|
||||||
@@ -2584,10 +2856,13 @@ inline std::string GetWebPageHTML() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const keyCode = parseInt(btn.dataset.key);
|
const keyCode = parseInt(btn.dataset.key);
|
||||||
const needShift = btn.dataset.shift === '1';
|
const needShift = btn.dataset.shift === '1';
|
||||||
|
const needCtrl = btn.dataset.ctrl === '1';
|
||||||
|
if (needCtrl) sendShortcutKey(17, true); // Ctrl down
|
||||||
if (needShift) sendShortcutKey(16, true); // Shift down
|
if (needShift) sendShortcutKey(16, true); // Shift down
|
||||||
sendShortcutKey(keyCode, true);
|
sendShortcutKey(keyCode, true);
|
||||||
sendShortcutKey(keyCode, false);
|
sendShortcutKey(keyCode, false);
|
||||||
if (needShift) sendShortcutKey(16, false); // Shift up
|
if (needShift) sendShortcutKey(16, false); // Shift up
|
||||||
|
if (needCtrl) sendShortcutKey(17, false); // Ctrl up
|
||||||
});
|
});
|
||||||
btn.addEventListener('click', function(e) {
|
btn.addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -2595,10 +2870,13 @@ inline std::string GetWebPageHTML() {
|
|||||||
if (!('ontouchstart' in window)) {
|
if (!('ontouchstart' in window)) {
|
||||||
const keyCode = parseInt(btn.dataset.key);
|
const keyCode = parseInt(btn.dataset.key);
|
||||||
const needShift = btn.dataset.shift === '1';
|
const needShift = btn.dataset.shift === '1';
|
||||||
|
const needCtrl = btn.dataset.ctrl === '1';
|
||||||
|
if (needCtrl) sendShortcutKey(17, true); // Ctrl down
|
||||||
if (needShift) sendShortcutKey(16, true);
|
if (needShift) sendShortcutKey(16, true);
|
||||||
sendShortcutKey(keyCode, true);
|
sendShortcutKey(keyCode, true);
|
||||||
sendShortcutKey(keyCode, false);
|
sendShortcutKey(keyCode, false);
|
||||||
if (needShift) sendShortcutKey(16, false);
|
if (needShift) sendShortcutKey(16, false);
|
||||||
|
if (needCtrl) sendShortcutKey(17, false); // Ctrl up
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2624,8 +2902,13 @@ inline std::string GetWebPageHTML() {
|
|||||||
bindKeyboardBtnEvents(document.getElementById('qc-keyboard'));
|
bindKeyboardBtnEvents(document.getElementById('qc-keyboard'));
|
||||||
bindKeyboardBtnEvents(document.getElementById('btn-keyboard'));
|
bindKeyboardBtnEvents(document.getElementById('btn-keyboard'));
|
||||||
bindKeyboardBtnEvents(document.getElementById('btn-keyboard-bar'));
|
bindKeyboardBtnEvents(document.getElementById('btn-keyboard-bar'));
|
||||||
// Restore token from sessionStorage
|
// Restore token and role from sessionStorage
|
||||||
token = sessionStorage.getItem('token');
|
token = sessionStorage.getItem('token');
|
||||||
|
currentUserRole = sessionStorage.getItem('role') || 'viewer';
|
||||||
|
// Show Users button for admin only (will be updated after login verification)
|
||||||
|
if (token && currentUserRole === 'admin') {
|
||||||
|
document.getElementById('users-btn').classList.add('visible');
|
||||||
|
}
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "SimpleWebSocket.h"
|
#include "SimpleWebSocket.h"
|
||||||
#include "common/commands.h"
|
#include "common/commands.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <shlobj.h>
|
||||||
|
|
||||||
// Algorithm constants (same as ScreenSpyDlg.cpp)
|
// Algorithm constants (same as ScreenSpyDlg.cpp)
|
||||||
#define ALGORITHM_H264 2
|
#define ALGORITHM_H264 2
|
||||||
@@ -20,6 +22,16 @@ static std::map<void*, std::string> s_ClientNonces;
|
|||||||
static std::mutex s_NonceMutex;
|
static std::mutex s_NonceMutex;
|
||||||
static std::atomic<bool> s_bShuttingDown{false}; // Prevents access during static destruction
|
static std::atomic<bool> s_bShuttingDown{false}; // Prevents access during static destruction
|
||||||
|
|
||||||
|
// Generate random salt (16 hex chars) - thread-safe
|
||||||
|
static std::string GenerateSalt() {
|
||||||
|
static std::random_device rd;
|
||||||
|
static std::mt19937_64 gen(rd());
|
||||||
|
std::uniform_int_distribution<uint64_t> dis;
|
||||||
|
char buf[17];
|
||||||
|
snprintf(buf, sizeof(buf), "%016llX", dis(gen));
|
||||||
|
return std::string(buf);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate random nonce (32 hex chars) - thread-safe
|
// Generate random nonce (32 hex chars) - thread-safe
|
||||||
static std::string GenerateNonce() {
|
static std::string GenerateNonce() {
|
||||||
if (s_bShuttingDown) return "";
|
if (s_bShuttingDown) return "";
|
||||||
@@ -112,11 +124,22 @@ CWebService::CWebService()
|
|||||||
m_PayloadsDir = (exeDir / "Payloads").string();
|
m_PayloadsDir = (exeDir / "Payloads").string();
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
std::filesystem::create_directories(m_PayloadsDir, ec);
|
std::filesystem::create_directories(m_PayloadsDir, ec);
|
||||||
|
|
||||||
|
// Initialize config directory (same as YAMA.db location)
|
||||||
|
char appdata_path[MAX_PATH];
|
||||||
|
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, appdata_path))) {
|
||||||
|
m_ConfigDir = std::string(appdata_path) + "\\" BRAND_DATA_FOLDER "\\";
|
||||||
|
} else {
|
||||||
|
m_ConfigDir = ".\\";
|
||||||
|
}
|
||||||
|
std::filesystem::create_directories(m_ConfigDir, ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWebService::SetAdminPassword(const std::string& password) {
|
void CWebService::SetAdminPassword(const std::string& password) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
m_Users.clear();
|
m_Users.clear();
|
||||||
|
|
||||||
|
// Admin user is built-in, always first
|
||||||
WebUser admin;
|
WebUser admin;
|
||||||
admin.username = "admin";
|
admin.username = "admin";
|
||||||
admin.salt = ""; // Not used with challenge-response auth
|
admin.salt = ""; // Not used with challenge-response auth
|
||||||
@@ -125,6 +148,9 @@ void CWebService::SetAdminPassword(const std::string& password) {
|
|||||||
m_Users.push_back(admin);
|
m_Users.push_back(admin);
|
||||||
|
|
||||||
Mprintf("[WebService] Admin password configured\n");
|
Mprintf("[WebService] Admin password configured\n");
|
||||||
|
|
||||||
|
// Load additional users from file (non-admin users)
|
||||||
|
LoadUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
CWebService::~CWebService() {
|
CWebService::~CWebService() {
|
||||||
@@ -329,6 +355,14 @@ void CWebService::ServerThread(int port) {
|
|||||||
HandleKey(ws_ptr, msg);
|
HandleKey(ws_ptr, msg);
|
||||||
} else if (cmd == "rdp_reset") {
|
} else if (cmd == "rdp_reset") {
|
||||||
HandleRdpReset(ws_ptr, token);
|
HandleRdpReset(ws_ptr, token);
|
||||||
|
} else if (cmd == "get_salt") {
|
||||||
|
HandleGetSalt(ws_ptr, msg);
|
||||||
|
} else if (cmd == "create_user") {
|
||||||
|
HandleCreateUser(ws_ptr, msg);
|
||||||
|
} else if (cmd == "delete_user") {
|
||||||
|
HandleDeleteUser(ws_ptr, msg);
|
||||||
|
} else if (cmd == "list_users") {
|
||||||
|
HandleListUsers(ws_ptr, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -480,6 +514,51 @@ void CWebService::HandleLogin(void* ws_ptr, const std::string& msg, const std::s
|
|||||||
SendText(ws_ptr, Json::writeString(builder, res));
|
SendText(ws_ptr, Json::writeString(builder, res));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CWebService::HandleGetSalt(void* ws_ptr, const std::string& msg) {
|
||||||
|
Json::Value root;
|
||||||
|
Json::Reader reader;
|
||||||
|
if (!reader.parse(msg, root)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("salt", false, "Invalid JSON"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string username = root.get("username", "").asString();
|
||||||
|
if (username.empty()) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("salt", false, "Username required"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find user and get salt
|
||||||
|
std::string salt = "";
|
||||||
|
bool userFound = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
|
for (const auto& u : m_Users) {
|
||||||
|
if (u.username == username) {
|
||||||
|
salt = u.salt;
|
||||||
|
userFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For security: if user doesn't exist, generate a fake deterministic salt
|
||||||
|
// This prevents username enumeration attacks
|
||||||
|
// Note: Admin has empty salt, so we must check userFound, not salt.empty()
|
||||||
|
if (!userFound) {
|
||||||
|
// Generate deterministic fake salt from username (won't match any real password)
|
||||||
|
salt = WSAuth::ComputeSHA256("fake_salt_prefix_" + username).substr(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value res;
|
||||||
|
res["cmd"] = "salt";
|
||||||
|
res["ok"] = true;
|
||||||
|
res["salt"] = salt;
|
||||||
|
Json::StreamWriterBuilder builder;
|
||||||
|
builder["indentation"] = "";
|
||||||
|
SendText(ws_ptr, Json::writeString(builder, res));
|
||||||
|
}
|
||||||
|
|
||||||
void CWebService::HandleGetDevices(void* ws_ptr, const std::string& token) {
|
void CWebService::HandleGetDevices(void* ws_ptr, const std::string& token) {
|
||||||
std::string username, role;
|
std::string username, role;
|
||||||
if (!ValidateToken(token, username, role)) {
|
if (!ValidateToken(token, username, role)) {
|
||||||
@@ -837,6 +916,111 @@ void CWebService::HandleRdpReset(void* ws_ptr, const std::string& token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// User Management Handlers
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void CWebService::HandleCreateUser(void* ws_ptr, const std::string& msg) {
|
||||||
|
Json::Value root;
|
||||||
|
Json::Reader reader;
|
||||||
|
if (!reader.parse(msg, root)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Invalid JSON"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string token = root.get("token", "").asString();
|
||||||
|
std::string username, role;
|
||||||
|
if (!ValidateToken(token, username, role)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Invalid token"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only admin can create users
|
||||||
|
if (role != "admin") {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Permission denied"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string newUsername = root.get("username", "").asString();
|
||||||
|
std::string newPassword = root.get("password", "").asString();
|
||||||
|
std::string newRole = root.get("role", "viewer").asString();
|
||||||
|
|
||||||
|
if (newUsername.empty() || newPassword.empty()) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Username and password required"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CreateUser(newUsername, newPassword, newRole)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", true));
|
||||||
|
} else {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Failed to create user (may already exist)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CWebService::HandleDeleteUser(void* ws_ptr, const std::string& msg) {
|
||||||
|
Json::Value root;
|
||||||
|
Json::Reader reader;
|
||||||
|
if (!reader.parse(msg, root)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("delete_user_result", false, "Invalid JSON"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string token = root.get("token", "").asString();
|
||||||
|
std::string username, role;
|
||||||
|
if (!ValidateToken(token, username, role)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("delete_user_result", false, "Invalid token"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only admin can delete users
|
||||||
|
if (role != "admin") {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("delete_user_result", false, "Permission denied"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string targetUsername = root.get("username", "").asString();
|
||||||
|
|
||||||
|
if (DeleteUser(targetUsername)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("delete_user_result", true));
|
||||||
|
} else {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("delete_user_result", false, "Failed to delete user"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CWebService::HandleListUsers(void* ws_ptr, const std::string& token) {
|
||||||
|
std::string username, role;
|
||||||
|
if (!ValidateToken(token, username, role)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("list_users_result", false, "Invalid token"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only admin can list users
|
||||||
|
if (role != "admin") {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("list_users_result", false, "Permission denied"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto users = ListUsers();
|
||||||
|
|
||||||
|
Json::Value res;
|
||||||
|
res["cmd"] = "list_users_result";
|
||||||
|
res["ok"] = true;
|
||||||
|
|
||||||
|
Json::Value usersArray(Json::arrayValue);
|
||||||
|
for (const auto& u : users) {
|
||||||
|
Json::Value user;
|
||||||
|
user["username"] = u.first;
|
||||||
|
user["role"] = u.second;
|
||||||
|
usersArray.append(user);
|
||||||
|
}
|
||||||
|
res["users"] = usersArray;
|
||||||
|
|
||||||
|
Json::StreamWriterBuilder builder;
|
||||||
|
builder["indentation"] = "";
|
||||||
|
std::string json = Json::writeString(builder, res);
|
||||||
|
SendText(ws_ptr, json);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// Token Management (delegated to WebServiceAuth module)
|
// Token Management (delegated to WebServiceAuth module)
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
@@ -936,6 +1120,155 @@ std::string CWebService::ComputeHash(const std::string& input) {
|
|||||||
return WSAuth::ComputeSHA256(input);
|
return WSAuth::ComputeSHA256(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// User Management
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
std::string CWebService::GetUsersFilePath() {
|
||||||
|
return m_ConfigDir + "users.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
void CWebService::LoadUsers() {
|
||||||
|
// Note: m_UsersMutex should already be held by caller (SetAdminPassword)
|
||||||
|
// Load additional users from users.json (admin user is already in m_Users)
|
||||||
|
|
||||||
|
std::string path = GetUsersFilePath();
|
||||||
|
std::ifstream file(path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
Mprintf("[WebService] No users.json found, using admin only\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Json::Value root;
|
||||||
|
Json::CharReaderBuilder builder;
|
||||||
|
std::string errors;
|
||||||
|
if (!Json::parseFromStream(builder, file, &root, &errors)) {
|
||||||
|
Mprintf("[WebService] Failed to parse users.json: %s\n", errors.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Json::Value& users = root["users"];
|
||||||
|
int loaded = 0;
|
||||||
|
for (const auto& u : users) {
|
||||||
|
std::string username = u.get("username", "").asString();
|
||||||
|
// Skip admin user (it's built-in with master password)
|
||||||
|
if (username.empty() || username == "admin") continue;
|
||||||
|
|
||||||
|
WebUser user;
|
||||||
|
user.username = username;
|
||||||
|
user.password_hash = u.get("password_hash", "").asString();
|
||||||
|
user.salt = u.get("salt", "").asString();
|
||||||
|
user.role = u.get("role", "viewer").asString();
|
||||||
|
|
||||||
|
if (!user.password_hash.empty()) {
|
||||||
|
m_Users.push_back(user);
|
||||||
|
loaded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mprintf("[WebService] Loaded %d additional users from users.json\n", loaded);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
Mprintf("[WebService] Error loading users.json: %s\n", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CWebService::SaveUsers() {
|
||||||
|
// Save non-admin users to users.json
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
|
|
||||||
|
Json::Value root;
|
||||||
|
Json::Value users(Json::arrayValue);
|
||||||
|
|
||||||
|
for (const auto& u : m_Users) {
|
||||||
|
// Skip admin user (it uses master password, not stored in file)
|
||||||
|
if (u.username == "admin") continue;
|
||||||
|
|
||||||
|
Json::Value user;
|
||||||
|
user["username"] = u.username;
|
||||||
|
user["password_hash"] = u.password_hash;
|
||||||
|
user["salt"] = u.salt;
|
||||||
|
user["role"] = u.role;
|
||||||
|
users.append(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
root["users"] = users;
|
||||||
|
|
||||||
|
std::string path = GetUsersFilePath();
|
||||||
|
std::ofstream file(path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
Mprintf("[WebService] Failed to open users.json for writing\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::StreamWriterBuilder builder;
|
||||||
|
builder["indentation"] = " ";
|
||||||
|
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||||
|
writer->write(root, &file);
|
||||||
|
|
||||||
|
Mprintf("[WebService] Saved %d users to users.json\n", (int)users.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWebService::CreateUser(const std::string& username, const std::string& password, const std::string& role) {
|
||||||
|
if (username.empty() || password.empty()) return false;
|
||||||
|
if (username == "admin") return false; // Cannot create user named "admin"
|
||||||
|
if (role != "admin" && role != "viewer") return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
|
|
||||||
|
// Check if user already exists
|
||||||
|
for (const auto& u : m_Users) {
|
||||||
|
if (u.username == username) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate salt and hash password with salt
|
||||||
|
WebUser user;
|
||||||
|
user.username = username;
|
||||||
|
user.salt = GenerateSalt();
|
||||||
|
user.password_hash = WSAuth::ComputeSHA256(password + user.salt);
|
||||||
|
user.role = role;
|
||||||
|
|
||||||
|
m_Users.push_back(user);
|
||||||
|
Mprintf("[WebService] Created user: %s (role: %s)\n", username.c_str(), role.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to file (outside lock scope since SaveUsers acquires its own lock)
|
||||||
|
SaveUsers();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWebService::DeleteUser(const std::string& username) {
|
||||||
|
if (username.empty() || username == "admin") return false;
|
||||||
|
|
||||||
|
bool deleted = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
|
|
||||||
|
for (auto it = m_Users.begin(); it != m_Users.end(); ++it) {
|
||||||
|
if (it->username == username) {
|
||||||
|
m_Users.erase(it);
|
||||||
|
Mprintf("[WebService] Deleted user: %s\n", username.c_str());
|
||||||
|
deleted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleted) {
|
||||||
|
SaveUsers();
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> CWebService::ListUsers() {
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
|
std::vector<std::pair<std::string, std::string>> result;
|
||||||
|
for (const auto& u : m_Users) {
|
||||||
|
result.push_back({u.username, u.role});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// JSON Helpers
|
// JSON Helpers
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ public:
|
|||||||
// Set admin password (use master password)
|
// Set admin password (use master password)
|
||||||
void SetAdminPassword(const std::string& password);
|
void SetAdminPassword(const std::string& password);
|
||||||
|
|
||||||
|
// User management
|
||||||
|
bool CreateUser(const std::string& username, const std::string& password, const std::string& role);
|
||||||
|
bool DeleteUser(const std::string& username);
|
||||||
|
std::vector<std::pair<std::string, std::string>> ListUsers(); // Returns [(username, role), ...]
|
||||||
|
|
||||||
// Device management (called from main app)
|
// Device management (called from main app)
|
||||||
void MarkDeviceOnline(uint64_t device_id);
|
void MarkDeviceOnline(uint64_t device_id);
|
||||||
void MarkDeviceOffline(uint64_t device_id);
|
void MarkDeviceOffline(uint64_t device_id);
|
||||||
@@ -111,6 +116,7 @@ private:
|
|||||||
|
|
||||||
// Signaling handlers
|
// Signaling handlers
|
||||||
void HandleLogin(void* ws_ptr, const std::string& msg, const std::string& client_ip);
|
void HandleLogin(void* ws_ptr, const std::string& msg, const std::string& client_ip);
|
||||||
|
void HandleGetSalt(void* ws_ptr, const std::string& msg);
|
||||||
void HandleGetDevices(void* ws_ptr, const std::string& token);
|
void HandleGetDevices(void* ws_ptr, const std::string& token);
|
||||||
void HandleConnect(void* ws_ptr, const std::string& token, uint64_t device_id);
|
void HandleConnect(void* ws_ptr, const std::string& token, uint64_t device_id);
|
||||||
void HandleDisconnect(void* ws_ptr, const std::string& token, uint64_t requested_device_id = 0);
|
void HandleDisconnect(void* ws_ptr, const std::string& token, uint64_t requested_device_id = 0);
|
||||||
@@ -141,6 +147,14 @@ private:
|
|||||||
bool VerifyPassword(const std::string& input, const WebUser& user);
|
bool VerifyPassword(const std::string& input, const WebUser& user);
|
||||||
std::string ComputeHash(const std::string& input);
|
std::string ComputeHash(const std::string& input);
|
||||||
|
|
||||||
|
// User management helpers
|
||||||
|
std::string GetUsersFilePath();
|
||||||
|
void LoadUsers();
|
||||||
|
void SaveUsers();
|
||||||
|
void HandleCreateUser(void* ws_ptr, const std::string& msg);
|
||||||
|
void HandleDeleteUser(void* ws_ptr, const std::string& msg);
|
||||||
|
void HandleListUsers(void* ws_ptr, const std::string& token);
|
||||||
|
|
||||||
// Send to WebSocket
|
// Send to WebSocket
|
||||||
void SendText(void* ws_ptr, const std::string& text);
|
void SendText(void* ws_ptr, const std::string& text);
|
||||||
void SendBinary(void* ws_ptr, const uint8_t* data, size_t len);
|
void SendBinary(void* ws_ptr, const uint8_t* data, size_t len);
|
||||||
@@ -181,6 +195,7 @@ private:
|
|||||||
|
|
||||||
// User accounts (loaded from config)
|
// User accounts (loaded from config)
|
||||||
std::vector<WebUser> m_Users;
|
std::vector<WebUser> m_Users;
|
||||||
|
std::mutex m_UsersMutex;
|
||||||
|
|
||||||
// Token secret key (generated on startup)
|
// Token secret key (generated on startup)
|
||||||
std::string m_SecretKey;
|
std::string m_SecretKey;
|
||||||
@@ -190,6 +205,7 @@ private:
|
|||||||
int m_nTokenExpireSeconds;
|
int m_nTokenExpireSeconds;
|
||||||
bool m_bHideWebSessions; // Whether to hide web-triggered dialogs (default: true)
|
bool m_bHideWebSessions; // Whether to hide web-triggered dialogs (default: true)
|
||||||
std::string m_PayloadsDir; // Directory for file downloads (Payloads/)
|
std::string m_PayloadsDir; // Directory for file downloads (Payloads/)
|
||||||
|
std::string m_ConfigDir; // Directory for config files (users.json, etc.)
|
||||||
|
|
||||||
// Web-triggered sessions (should be hidden)
|
// Web-triggered sessions (should be hidden)
|
||||||
std::set<uint64_t> m_WebTriggeredDevices;
|
std::set<uint64_t> m_WebTriggeredDevices;
|
||||||
|
|||||||
@@ -1773,3 +1773,33 @@ Web
|
|||||||
请在菜单设置Web端口!=Please set Web liscening port!
|
请在菜单设置Web端口!=Please set Web liscening port!
|
||||||
请设置环境变量 YAMA_PWD 来使用Web远程桌面!=Please set YAMA_PWD to use Web SimpleRemoter!
|
请设置环境变量 YAMA_PWD 来使用Web远程桌面!=Please set YAMA_PWD to use Web SimpleRemoter!
|
||||||
如需Web远程桌面跨网使用方案,请联系管理员!=If you need to use Web SimpleRemoter in WAN, please contact administrator!
|
如需Web远程桌面跨网使用方案,请联系管理员!=If you need to use Web SimpleRemoter in WAN, please contact administrator!
|
||||||
|
; Plugin Settings Dialog - English Translation
|
||||||
|
; Format: Simplified Chinese=English
|
||||||
|
|
||||||
|
内存DLL=Memory DLL
|
||||||
|
自动检测=Auto Detect
|
||||||
|
IOCP线程=IOCP Thread
|
||||||
|
自定义FRPC[不可用]=Custom FRPC [Unavailable]
|
||||||
|
标准FRPC[不可用]=Standard FRPC [Unavailable]
|
||||||
|
不自动执行=No Auto Execute
|
||||||
|
启动执行=Execute on Startup
|
||||||
|
每日定时[未实现]=Daily Schedule [Not Implemented]
|
||||||
|
每周定时[未实现]=Weekly Schedule [Not Implemented]
|
||||||
|
名称=Name
|
||||||
|
大小=Size
|
||||||
|
运行类型=Run Type
|
||||||
|
调度模式=Schedule Mode
|
||||||
|
插件参数配置=Plugin Parameters
|
||||||
|
运行类型:=Run Type:
|
||||||
|
调用方式:=Call Type:
|
||||||
|
调度模式:=Schedule Mode:
|
||||||
|
间隔(秒):=Interval (sec):
|
||||||
|
最大次数:=Max Count:
|
||||||
|
插件设置=Plugin Settings
|
||||||
|
请先选择一个插件=Please select a plugin first
|
||||||
|
提示=Notice
|
||||||
|
配置已保存=Configuration saved
|
||||||
|
保存(&S)=Save(&S)
|
||||||
|
关闭=Close
|
||||||
|
插件设置(&S)=Plugin Settings(&S)
|
||||||
|
代理端口 - 自启=Proxy Port - AutoRun
|
||||||
|
|||||||
@@ -1765,3 +1765,33 @@ FRPS
|
|||||||
请在菜单设置Web端口!=请在菜单设置Web端口!
|
请在菜单设置Web端口!=请在菜单设置Web端口!
|
||||||
请设置环境变量 YAMA_PWD 来使用Web远程桌面!=请设置环境变量 YAMA_PWD 来使用Web远程桌面!
|
请设置环境变量 YAMA_PWD 来使用Web远程桌面!=请设置环境变量 YAMA_PWD 来使用Web远程桌面!
|
||||||
如需Web远程桌面跨网使用方案,请联系管理员!=如需Web远程桌面跨网使用方案,请联系管理员!
|
如需Web远程桌面跨网使用方案,请联系管理员!=如需Web远程桌面跨网使用方案,请联系管理员!
|
||||||
|
; Plugin Settings Dialog - Traditional Chinese Translation
|
||||||
|
; Format: Simplified Chinese=Traditional Chinese
|
||||||
|
|
||||||
|
内存DLL=記憶體DLL
|
||||||
|
自动检测=自動檢測
|
||||||
|
IOCP线程=IOCP執行緒
|
||||||
|
自定义FRPC[不可用]=自訂FRPC[不可用]
|
||||||
|
标准FRPC[不可用]=標準FRPC[不可用]
|
||||||
|
不自动执行=不自動執行
|
||||||
|
启动执行=啟動執行
|
||||||
|
每日定时[未实现]=每日定時[未實現]
|
||||||
|
每周定时[未实现]=每週定時[未實現]
|
||||||
|
名称=名稱
|
||||||
|
大小=大小
|
||||||
|
运行类型=執行類型
|
||||||
|
调度模式=排程模式
|
||||||
|
插件参数配置=外掛參數設定
|
||||||
|
运行类型:=執行類型:
|
||||||
|
调用方式:=呼叫方式:
|
||||||
|
调度模式:=排程模式:
|
||||||
|
间隔(秒):=間隔(秒):
|
||||||
|
最大次数:=最大次數:
|
||||||
|
插件设置=外掛設定
|
||||||
|
请先选择一个插件=請先選擇一個外掛
|
||||||
|
提示=提示
|
||||||
|
配置已保存=設定已儲存
|
||||||
|
保存(&S)=儲存(&S)
|
||||||
|
关闭=關閉
|
||||||
|
插件设置(&S)=插件设置(&S)
|
||||||
|
代理端口 - 自启=代理端口 - 自启
|
||||||
|
|||||||
@@ -247,6 +247,7 @@
|
|||||||
#define IDD_FEATURE_LIMITS 368
|
#define IDD_FEATURE_LIMITS 368
|
||||||
#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 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
|
||||||
@@ -705,6 +706,19 @@
|
|||||||
#define IDC_STATIC_FEATURE_TIP 2522
|
#define IDC_STATIC_FEATURE_TIP 2522
|
||||||
#define IDC_STATIC_AUTH_HOSTNUM 2523
|
#define IDC_STATIC_AUTH_HOSTNUM 2523
|
||||||
#define IDC_EDIT_AUTH_HOSTNUM 2524
|
#define IDC_EDIT_AUTH_HOSTNUM 2524
|
||||||
|
#define IDC_LIST_PLUGINS 2526
|
||||||
|
#define IDC_COMBO_RUNTYPE_P 2527
|
||||||
|
#define IDC_COMBO_CALLTYPE 2528
|
||||||
|
#define IDC_COMBO_MODE 2529
|
||||||
|
#define IDC_EDIT_INTERVAL 2530
|
||||||
|
#define IDC_EDIT_MAXCOUNT 2531
|
||||||
|
#define IDC_BTN_SAVE 2532
|
||||||
|
#define IDC_STATIC_PLUGIN_SETTINGS 2533
|
||||||
|
#define IDC_STATIC_PLUGIN_RUNTYPE 2534
|
||||||
|
#define IDC_STATIC_PLUGIN_CALLTYPE 2535
|
||||||
|
#define IDC_STATIC_PLUGIN_SCHEDULE 2536
|
||||||
|
#define IDC_STATIC_PLUGIN_INTERVAL 2537
|
||||||
|
#define IDC_STATIC_PLUGIN_COUNTER 2538
|
||||||
#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
|
||||||
@@ -935,15 +949,18 @@
|
|||||||
#define ID_CANCEL_SHARE 33042
|
#define ID_CANCEL_SHARE 33042
|
||||||
#define ID_33043 33043
|
#define ID_33043 33043
|
||||||
#define ID_WEB_REMOTE_CONTROL 33044
|
#define ID_WEB_REMOTE_CONTROL 33044
|
||||||
|
#define ID_TOOL_PLUGIN_SETTINGS 33045
|
||||||
|
#define ID_33046 33046
|
||||||
|
#define ID_PROXY_PORT_AUTORUN 33047
|
||||||
#define ID_EXIT_FULLSCREEN 40001
|
#define ID_EXIT_FULLSCREEN 40001
|
||||||
|
|
||||||
// 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 370
|
#define _APS_NEXT_RESOURCE_VALUE 371
|
||||||
#define _APS_NEXT_COMMAND_VALUE 33045
|
#define _APS_NEXT_COMMAND_VALUE 33048
|
||||||
#define _APS_NEXT_CONTROL_VALUE 2525
|
#define _APS_NEXT_CONTROL_VALUE 2539
|
||||||
#define _APS_NEXT_SYMED_VALUE 105
|
#define _APS_NEXT_SYMED_VALUE 105
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user