Compare commits
6 Commits
92f3df8464
...
a3fa2f5a81
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3fa2f5a81 | ||
|
|
df91de78f3 | ||
|
|
a89f8dd28f | ||
|
|
6113b4653d | ||
|
|
f11fc93ba8 | ||
|
|
773c78ac0f |
@@ -272,6 +272,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
|
||||
FrpcParam* f = (FrpcParam*)user;
|
||||
Mprintf("MemoryGetProcAddress '%s' %s\n", info.Name, proc ? "success" : "failed");
|
||||
int r = 0;
|
||||
uint64_t start = time(0);
|
||||
if (proc) {
|
||||
r=proc(f->privilegeKey, f->timestamp, f->serverAddr, f->serverPort, f->localPort, f->remotePort,
|
||||
&CKernelManager::g_IsAppExit);
|
||||
@@ -279,7 +280,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
|
||||
else {
|
||||
This->m_cfg->SetStr("settings", info.Name + std::string(".md5"), "");
|
||||
}
|
||||
if (r) {
|
||||
if (r || (time(0)-start < 15)) {
|
||||
char buf[100];
|
||||
sprintf_s(buf, "Run %s [proxy %d] failed: %d", info.Name, f->localPort, r);
|
||||
Mprintf("%s\n", buf);
|
||||
@@ -295,6 +296,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
|
||||
FrpcParam* f = (FrpcParam*)user;
|
||||
Mprintf("MemoryGetProcAddress '%s' %s\n", info.Name, proc ? "success" : "failed");
|
||||
int r = 0;
|
||||
uint64_t start = time(0);
|
||||
if (proc) {
|
||||
r = proc(f->privilegeKey, f->serverAddr, f->serverPort, f->localPort, f->remotePort,
|
||||
&CKernelManager::g_IsAppExit);
|
||||
@@ -302,7 +304,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
|
||||
else {
|
||||
This->m_cfg->SetStr("settings", info.Name + std::string(".md5"), "");
|
||||
}
|
||||
if (r) {
|
||||
if (r || (time(0)-start < 15)) {
|
||||
char buf[100];
|
||||
sprintf_s(buf, "Run %s [proxy %d] failed: %d", info.Name, f->localPort, r);
|
||||
Mprintf("%s\n", buf);
|
||||
|
||||
@@ -50,47 +50,71 @@ public:
|
||||
char line[4096];
|
||||
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
// 去除行尾换行符
|
||||
size_t len = strlen(line);
|
||||
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
|
||||
line[--len] = '\0';
|
||||
|
||||
if (len == 0)
|
||||
continue;
|
||||
|
||||
// 跳过注释
|
||||
if (line[0] == ';' || line[0] == '#')
|
||||
continue;
|
||||
|
||||
// 检测 section 头: [SectionName]
|
||||
// 真正的 section 头:']' 后面没有 '='(否则是 key=value)
|
||||
if (line[0] == '[') {
|
||||
char* end = strchr(line, ']');
|
||||
if (end) {
|
||||
char* eqAfter = strchr(end + 1, '=');
|
||||
if (!eqAfter) {
|
||||
// 纯 section 头,如 [Strings]
|
||||
*end = '\0';
|
||||
currentSection = line + 1;
|
||||
continue;
|
||||
}
|
||||
// ']' 后有 '=',如 [使用FRP]=[Using FRP],当作 key=value 处理
|
||||
}
|
||||
ParseLine(line, currentSection);
|
||||
}
|
||||
|
||||
// 不在任何 section 内则跳过
|
||||
if (currentSection.empty())
|
||||
continue;
|
||||
|
||||
// 解析 key=value(只按第一个 '=' 分割,不 trim)
|
||||
// key 和 value 均做反转义(\n \r \t \\ \")
|
||||
char* eq = strchr(line, '=');
|
||||
if (eq && eq != line) {
|
||||
*eq = '\0';
|
||||
std::string key = Unescape(std::string(line));
|
||||
std::string value = Unescape(std::string(eq + 1));
|
||||
m_sections[currentSection][key] = value;
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 从内存加载 INI 数据,返回是否成功
|
||||
// 用于加载嵌入的资源数据
|
||||
bool LoadFromMemory(const char* data, size_t size)
|
||||
{
|
||||
Clear();
|
||||
|
||||
if (!data || size == 0)
|
||||
return false;
|
||||
|
||||
std::string currentSection;
|
||||
const char* p = data;
|
||||
const char* end = data + size;
|
||||
|
||||
while (p < end) {
|
||||
// 找到行尾
|
||||
const char* lineEnd = p;
|
||||
while (lineEnd < end && *lineEnd != '\n' && *lineEnd != '\r')
|
||||
lineEnd++;
|
||||
|
||||
// 复制行内容
|
||||
size_t lineLen = lineEnd - p;
|
||||
if (lineLen > 0 && lineLen < 4096) {
|
||||
char line[4096];
|
||||
memcpy(line, p, lineLen);
|
||||
line[lineLen] = '\0';
|
||||
ParseLine(line, currentSection);
|
||||
}
|
||||
|
||||
// 跳过换行符
|
||||
p = lineEnd;
|
||||
while (p < end && (*p == '\n' || *p == '\r'))
|
||||
p++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 追加加载(不清除现有数据,用于覆盖)
|
||||
bool LoadFileAppend(const char* filePath)
|
||||
{
|
||||
if (!filePath || !filePath[0])
|
||||
return false;
|
||||
|
||||
FILE* f = nullptr;
|
||||
#ifdef _MSC_VER
|
||||
if (fopen_s(&f, filePath, "r") != 0 || !f)
|
||||
return false;
|
||||
#else
|
||||
f = fopen(filePath, "r");
|
||||
if (!f)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
std::string currentSection;
|
||||
char line[4096];
|
||||
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
ParseLine(line, currentSection);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
@@ -138,6 +162,52 @@ public:
|
||||
private:
|
||||
TSections m_sections;
|
||||
|
||||
// 解析单行 INI 内容
|
||||
void ParseLine(char* line, std::string& currentSection)
|
||||
{
|
||||
// 去除行尾换行符
|
||||
size_t len = strlen(line);
|
||||
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
|
||||
line[--len] = '\0';
|
||||
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
// 跳过注释
|
||||
if (line[0] == ';' || line[0] == '#')
|
||||
return;
|
||||
|
||||
// 检测 section 头: [SectionName]
|
||||
// 真正的 section 头:']' 后面没有 '='(否则是 key=value)
|
||||
if (line[0] == '[') {
|
||||
char* end = strchr(line, ']');
|
||||
if (end) {
|
||||
char* eqAfter = strchr(end + 1, '=');
|
||||
if (!eqAfter) {
|
||||
// 纯 section 头,如 [Strings]
|
||||
*end = '\0';
|
||||
currentSection = line + 1;
|
||||
return;
|
||||
}
|
||||
// ']' 后有 '=',如 [使用FRP]=[Using FRP],当作 key=value 处理
|
||||
}
|
||||
}
|
||||
|
||||
// 不在任何 section 内则跳过
|
||||
if (currentSection.empty())
|
||||
return;
|
||||
|
||||
// 解析 key=value(只按第一个 '=' 分割,不 trim)
|
||||
// key 和 value 均做反转义(\n \r \t \\ \")
|
||||
char* eq = strchr(line, '=');
|
||||
if (eq && eq != line) {
|
||||
*eq = '\0';
|
||||
std::string key = Unescape(std::string(line));
|
||||
std::string value = Unescape(std::string(eq + 1));
|
||||
m_sections[currentSection][key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 反转义:将字面量 \n \r \t \\ \" 转为对应的控制字符
|
||||
static std::string Unescape(const std::string& s)
|
||||
{
|
||||
|
||||
@@ -330,6 +330,7 @@ enum {
|
||||
CMD_SET_GROUP = 242, // 修改分组
|
||||
CMD_EXECUTE_DLL_NEW = 243, // 执行代码
|
||||
CMD_PEER_TO_PEER = 244, // P2P通信
|
||||
TOKEN_CLIENTID = 245,
|
||||
};
|
||||
|
||||
enum MachineCommand {
|
||||
|
||||
@@ -41,6 +41,7 @@ CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_L
|
||||
|
||||
// 全局状态
|
||||
State g_bExit = S_CLIENT_NORMAL;
|
||||
static std::atomic<bool> g_needResendLogin(false); // 分组变更后需要重发登录信息
|
||||
|
||||
// 客户端 ID(V2 文件传输需要)
|
||||
uint64_t g_myClientID = 0;
|
||||
@@ -517,6 +518,8 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
|
||||
// Update global settings
|
||||
memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName));
|
||||
strncpy(g_SETTINGS.szGroupName, groupName.c_str(), sizeof(g_SETTINGS.szGroupName) - 1);
|
||||
// 标记需要重发登录信息(让服务端更新分组显示)
|
||||
g_needResendLogin.store(true);
|
||||
Mprintf("** [%p] Group changed to: %s ***\n", user, groupName.c_str());
|
||||
} else {
|
||||
Mprintf("** [%p] Received unimplemented command: %d ***\n", user, int(szBuffer[0]));
|
||||
@@ -1039,6 +1042,21 @@ int main(int argc, char* argv[])
|
||||
|
||||
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
|
||||
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit) {
|
||||
// 检查是否需要重发登录信息(分组变更后)
|
||||
if (g_needResendLogin.exchange(false)) {
|
||||
// 更新 szPCName(hostname/groupname 格式)
|
||||
std::string grp = g_SETTINGS.szGroupName;
|
||||
if (!grp.empty()) {
|
||||
std::string pcNameWithGroup = std::string(hostname) + "/" + grp;
|
||||
strncpy(logInfo.szPCName, pcNameWithGroup.c_str(), sizeof(logInfo.szPCName) - 1);
|
||||
} else {
|
||||
strncpy(logInfo.szPCName, hostname, sizeof(logInfo.szPCName) - 1);
|
||||
}
|
||||
logInfo.szPCName[sizeof(logInfo.szPCName) - 1] = '\0';
|
||||
ClientObject->SendLoginInfo(logInfo);
|
||||
Mprintf(">> Resent login info after group change\n");
|
||||
}
|
||||
|
||||
// 等待心跳间隔(每秒检查一次退出条件,保证及时响应)
|
||||
int interval = g_heartbeatInterval > 0 ? g_heartbeatInterval : 30;
|
||||
for (int i = 0; i < interval; ++i) {
|
||||
|
||||
117
macos/install.sh
117
macos/install.sh
@@ -4,100 +4,101 @@
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
GHOST_SRC="${1:-$SCRIPT_DIR/build/bin/ghost}"
|
||||
GHOST_DST="/usr/local/bin/ghost"
|
||||
PLIST_DST="/Library/LaunchDaemons/com.ghost.client.plist"
|
||||
APP_DIR="/Applications/GhostClient.app"
|
||||
APP_BIN="$APP_DIR/Contents/MacOS/ghost"
|
||||
|
||||
echo "=== GhostClient 安装程序 ==="
|
||||
echo "源文件: $GHOST_SRC"
|
||||
echo ""
|
||||
|
||||
# 检查源文件
|
||||
if [ ! -f "$GHOST_SRC" ]; then
|
||||
echo ""
|
||||
echo "错误: 找不到 $GHOST_SRC"
|
||||
echo ""
|
||||
echo "请先编译: ./build.sh"
|
||||
echo ""
|
||||
echo "或指定路径: $0 <ghost可执行文件路径>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "源文件: $GHOST_SRC"
|
||||
echo ""
|
||||
|
||||
set -e
|
||||
|
||||
# 1. 停止旧服务(只停止安装目录的,不影响调试目录)
|
||||
echo "[1/6] 停止旧服务..."
|
||||
sudo launchctl unload "$PLIST_DST" 2>/dev/null || true
|
||||
sudo pkill -9 -f "$GHOST_DST" 2>/dev/null || true
|
||||
# 1. 停止旧进程
|
||||
echo "[1/6] 停止旧进程..."
|
||||
pkill -9 -f "$APP_BIN" 2>/dev/null || true
|
||||
|
||||
# 2. 复制程序
|
||||
echo "[2/6] 安装程序到 $GHOST_DST..."
|
||||
sudo cp "$GHOST_SRC" "$GHOST_DST"
|
||||
sudo chmod +x "$GHOST_DST"
|
||||
# 2. 重置系统权限(关键步骤!避免权限缓存导致空白桌面)
|
||||
echo "[2/6] 重置系统权限..."
|
||||
echo " (这会清除屏幕录制和辅助功能的旧授权,需要重新授权)"
|
||||
tccutil reset ScreenCapture 2>/dev/null || true
|
||||
tccutil reset Accessibility 2>/dev/null || true
|
||||
|
||||
# 3. 清除隔离属性
|
||||
echo "[3/6] 清除隔离属性..."
|
||||
sudo xattr -cr "$GHOST_DST"
|
||||
# 3. 创建应用程序包
|
||||
echo "[3/6] 创建应用程序..."
|
||||
sudo rm -rf "$APP_DIR"
|
||||
sudo mkdir -p "$APP_DIR/Contents/MacOS"
|
||||
sudo mkdir -p "$APP_DIR/Contents/Resources"
|
||||
|
||||
# 4. 签名
|
||||
echo "[4/6] 签名程序..."
|
||||
sudo codesign --force --deep --sign - "$GHOST_DST"
|
||||
# 复制 ghost 到 app bundle 内部
|
||||
sudo cp "$GHOST_SRC" "$APP_BIN"
|
||||
sudo chmod +x "$APP_BIN"
|
||||
|
||||
# 5. 创建 launchd plist
|
||||
echo "[5/6] 创建 launchd 服务..."
|
||||
sudo tee "$PLIST_DST" > /dev/null << 'EOF'
|
||||
# 创建 Info.plist
|
||||
sudo tee "$APP_DIR/Contents/Info.plist" > /dev/null << 'EOF'
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>ghost</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.ghost.client</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/ghost</string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<key>CFBundleName</key>
|
||||
<string>GhostClient</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/ghost.log</string>
|
||||
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/ghost.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
sudo chown root:wheel "$PLIST_DST"
|
||||
sudo chmod 644 "$PLIST_DST"
|
||||
# 4. 清除隔离属性
|
||||
echo "[4/6] 清除隔离属性..."
|
||||
sudo xattr -cr "$APP_DIR"
|
||||
|
||||
# 6. 完成
|
||||
echo "[6/6] 安装完成!"
|
||||
# 5. 签名应用
|
||||
echo "[5/6] 签名应用..."
|
||||
sudo codesign --force --deep --sign - "$APP_DIR"
|
||||
|
||||
# 6. 添加到登录项(开机自启)
|
||||
echo "[6/7] 添加到登录项..."
|
||||
osascript -e 'tell application "System Events" to delete login item "GhostClient"' 2>/dev/null || true
|
||||
osascript -e 'tell application "System Events" to make login item at end with properties {path:"/Applications/GhostClient.app", hidden:true}' 2>/dev/null && echo " 已添加开机自启" || echo " 添加失败,请手动添加"
|
||||
|
||||
# 7. 完成
|
||||
echo "[7/7] 安装完成!"
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "重要: 首次运行需要授权系统权限"
|
||||
echo " 下一步: 授权系统权限"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "请执行以下步骤:"
|
||||
echo "1. 启动应用 (会自动弹出权限请求):"
|
||||
echo ""
|
||||
echo "1. 手动运行以触发权限请求:"
|
||||
echo " $GHOST_DST"
|
||||
echo " open /Applications/GhostClient.app"
|
||||
echo ""
|
||||
echo "2. 授权后按 Ctrl+C 停止程序(权限需重启生效)"
|
||||
echo "2. 授权以下权限 (在系统设置中勾选 GhostClient):"
|
||||
echo " - 屏幕录制: 允许捕获屏幕画面"
|
||||
echo " - 辅助功能: 允许控制鼠标键盘"
|
||||
echo ""
|
||||
echo "3. 启动服务:"
|
||||
echo " sudo launchctl load $PLIST_DST"
|
||||
echo "3. 授权后重启应用:"
|
||||
echo ""
|
||||
echo "如未弹出授权对话框,手动添加:"
|
||||
echo " 系统设置 > 隐私与安全性 > 屏幕录制 > 添加 ghost"
|
||||
echo " 系统设置 > 隐私与安全性 > 辅助功能 > 添加 ghost"
|
||||
echo " pkill -f GhostClient && open /Applications/GhostClient.app"
|
||||
echo ""
|
||||
echo "常用命令:"
|
||||
echo " 启动: sudo launchctl start com.ghost.client"
|
||||
echo " 停止: sudo launchctl stop com.ghost.client"
|
||||
echo " 卸载: sudo launchctl unload $PLIST_DST"
|
||||
echo " 日志: tail -f /var/log/ghost.log"
|
||||
echo "4. 查看日志确认运行状态:"
|
||||
echo ""
|
||||
echo " tail -f /tmp/ghost.log"
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
// Global state
|
||||
static std::atomic<bool> g_running(true);
|
||||
static std::atomic<bool> g_needResendLogin(false); // 分组变更后需要重发登录信息
|
||||
|
||||
// Client ID (calculated from system info, used by ScreenHandler)
|
||||
uint64_t g_myClientID = 0;
|
||||
@@ -805,6 +806,8 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
|
||||
// Update global settings
|
||||
memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName));
|
||||
strncpy(g_SETTINGS.szGroupName, groupName.c_str(), sizeof(g_SETTINGS.szGroupName) - 1);
|
||||
// 标记需要重发登录信息(让服务端更新分组显示)
|
||||
g_needResendLogin.store(true);
|
||||
Mprintf("** [%p] Group changed to: %s ***\n", user, groupName.c_str());
|
||||
} else {
|
||||
Mprintf("** [%p] Received unimplemented command: %d ***\n", user, int(szBuffer[0]));
|
||||
@@ -914,6 +917,13 @@ int main(int argc, const char* argv[])
|
||||
|
||||
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
|
||||
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit) {
|
||||
// 检查是否需要重发登录信息(分组变更后)
|
||||
if (g_needResendLogin.exchange(false)) {
|
||||
fillLoginInfo(logInfo);
|
||||
ClientObject->SendLoginInfo(logInfo);
|
||||
Mprintf(">> Resent login info after group change\n");
|
||||
}
|
||||
|
||||
// 等待心跳间隔(每秒检查一次退出条件,保证及时响应)
|
||||
int interval = g_heartbeatInterval > 0 ? g_heartbeatInterval : 30;
|
||||
for (int i = 0; i < interval; ++i) {
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
#!/bin/bash
|
||||
# macOS Ghost Client 卸载脚本
|
||||
|
||||
APP_DIR="/Applications/GhostClient.app"
|
||||
|
||||
echo "=== GhostClient 卸载程序 ==="
|
||||
|
||||
# 1. 停止并卸载 launchd 服务
|
||||
echo "[1/4] 停止服务..."
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.ghost.client.plist 2>/dev/null
|
||||
launchctl unload ~/Library/LaunchAgents/com.ghost.client.plist 2>/dev/null
|
||||
|
||||
# 2. 杀死残留进程
|
||||
echo "[2/4] 终止进程..."
|
||||
sudo pkill -9 -f "/usr/local/bin/ghost" 2>/dev/null
|
||||
|
||||
# 3. 删除文件
|
||||
echo "[3/4] 删除文件..."
|
||||
sudo rm -f /Library/LaunchDaemons/com.ghost.client.plist
|
||||
rm -f ~/Library/LaunchAgents/com.ghost.client.plist
|
||||
sudo rm -f /usr/local/bin/ghost
|
||||
rm -rf ~/.config/ghost
|
||||
sudo rm -f /var/log/ghost.log
|
||||
|
||||
# 4. 完成
|
||||
echo "[4/4] 卸载完成!"
|
||||
echo ""
|
||||
echo "注意: 系统权限(屏幕录制/辅助功能)未重置。"
|
||||
|
||||
# 1. 停止进程
|
||||
echo "[1/3] 停止进程..."
|
||||
pkill -9 -f "$APP_DIR" 2>/dev/null || true
|
||||
|
||||
# 2. 删除文件
|
||||
echo "[2/3] 删除文件..."
|
||||
sudo rm -rf "$APP_DIR"
|
||||
rm -rf ~/.config/ghost 2>/dev/null || true
|
||||
rm -f /tmp/ghost.log 2>/dev/null || true
|
||||
|
||||
# 3. 移除登录项
|
||||
echo "[3/4] 移除登录项..."
|
||||
osascript -e 'tell application "System Events" to delete login item "GhostClient"' 2>/dev/null || true
|
||||
|
||||
# 4. 重置系统权限
|
||||
echo "[4/4] 重置系统权限..."
|
||||
tccutil reset ScreenCapture 2>/dev/null || true
|
||||
tccutil reset Accessibility 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " 卸载完成"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "如需重置系统权限(会影响所有应用),请手动执行:"
|
||||
echo " tccutil reset ScreenCapture"
|
||||
echo " tccutil reset Accessibility"
|
||||
echo " tccutil reset SystemPolicyAllFiles"
|
||||
|
||||
@@ -526,14 +526,13 @@ BOOL CMy2015RemoteApp::InitInstance()
|
||||
SetChineseThreadLocale();
|
||||
|
||||
// 加载语言包(必须在显示任何文本之前)
|
||||
// 内嵌资源支持 en_US 和 zh_TW,无需外部文件
|
||||
auto lang = THIS_CFG.GetStr("settings", "Language", "en_US");
|
||||
auto langDir = THIS_CFG.GetStr("settings", "LangDir", "./lang");
|
||||
langDir = langDir.empty() ? "./lang" : langDir;
|
||||
if (PathFileExists(langDir.c_str())) {
|
||||
g_Lang.Init(langDir.c_str());
|
||||
g_Lang.Load(lang.c_str());
|
||||
Mprintf("语言包目录已经指定[%s], 语言数量: %d\n", langDir.c_str(), g_Lang.GetLanguageCount());
|
||||
}
|
||||
g_Lang.Init(langDir.c_str()); // 初始化目录(用于磁盘补丁文件)
|
||||
g_Lang.Load(lang.c_str()); // 加载语言(优先内嵌资源,再覆盖磁盘文件)
|
||||
Mprintf("语言: %s, 目录: %s\n", lang.c_str(), langDir.c_str());
|
||||
|
||||
// 创建并显示启动画面
|
||||
CSplashDlg* pSplash = new CSplashDlg();
|
||||
|
||||
Binary file not shown.
@@ -176,9 +176,9 @@ bool SupportsFileTransferV2(context* ctx) {
|
||||
}
|
||||
|
||||
// 授权日志频率控制:首次必须记录,状态变化必须记录,相同状态每小时记录一次
|
||||
static bool ShouldLogAuth(const std::string& sn, bool success) {
|
||||
static bool ShouldLogAuth(const std::string& sn, int success) {
|
||||
struct AuthLogState {
|
||||
bool lastStatus;
|
||||
int lastStatus;
|
||||
time_t lastLogTime;
|
||||
};
|
||||
static std::map<std::string, AuthLogState> s_cache;
|
||||
@@ -397,7 +397,7 @@ DllInfo* ReadTinyRunDll(int pid)
|
||||
{
|
||||
std::string name = TINY_DLL_NAME;
|
||||
DWORD fileSize = 0;
|
||||
BYTE * dllData = ReadResource(IDR_TINYRUN_X64, fileSize);
|
||||
BYTE * dllData = ReadResource(IDR_TINYRUN_X64, fileSize, ResFileName::TINYRUN_X64);
|
||||
std::string s(skCrypt(FLAG_FINDEN)), ip, port;
|
||||
int offset = MemoryFind((char*)dllData, s.c_str(), fileSize, s.length());
|
||||
if (offset != -1) {
|
||||
@@ -433,7 +433,7 @@ DllInfo* ReadFrpcDll(int callType)
|
||||
{
|
||||
std::string name = FRPC_DLL_NAME;
|
||||
DWORD fileSize = 0;
|
||||
BYTE* dllData = ReadResource(IDR_BINARY_FRPC, fileSize);
|
||||
BYTE* dllData = ReadResource(IDR_BINARY_FRPC, fileSize, ResFileName::FRPC_DLL);
|
||||
// 设置输出参数
|
||||
auto md5 = CalcMD5FromBytes(dllData, fileSize);
|
||||
DllExecuteInfoNew info = { MEMORYDLL, fileSize, callType, {}, {}, 0, 0, sizeof(DllExecuteInfoNew)};
|
||||
@@ -1862,7 +1862,8 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
||||
}
|
||||
if (strlen(v->Admin) && v->Port > 0) {
|
||||
DWORD size = 0;
|
||||
LPBYTE data = ReadResource(sizeof(void*) == 8 ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, size);
|
||||
LPBYTE data = ReadResource(sizeof(void*) == 8 ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, size,
|
||||
sizeof(void*) == 8 ? ResFileName::TINYRUN_X64 : ResFileName::TINYRUN_X86);
|
||||
if (data) {
|
||||
int offset = MemoryFind((char*)data, FLAG_FINDEN, size, strlen(FLAG_FINDEN));
|
||||
if (offset != -1) {
|
||||
@@ -2212,7 +2213,7 @@ void CMy2015RemoteDlg::InitFrpClients()
|
||||
|
||||
// 加载 FRP DLL(只加载一次)
|
||||
DWORD size = 0;
|
||||
LPBYTE frpcData = ReadResource(IDR_BINARY_FRPC, size);
|
||||
LPBYTE frpcData = ReadResource(IDR_BINARY_FRPC, size, ResFileName::FRPC_DLL);
|
||||
if (frpcData == nullptr) {
|
||||
Mprintf("[FRP] Failed to read FRP DLL\n");
|
||||
return;
|
||||
@@ -2301,7 +2302,7 @@ bool CMy2015RemoteDlg::InitLocalFrpsServer()
|
||||
|
||||
// 加载 FRPS DLL
|
||||
DWORD size = 0;
|
||||
LPBYTE frpsData = ReadResource(IDR_BINARY_FRPS, size);
|
||||
LPBYTE frpsData = ReadResource(IDR_BINARY_FRPS, size, ResFileName::FRPS_DLL);
|
||||
if (frpsData == nullptr) {
|
||||
Mprintf("[FRPS] Failed to read FRPS DLL from resource\n");
|
||||
return false;
|
||||
@@ -2634,7 +2635,7 @@ void CMy2015RemoteDlg::StartFrpcAuto(const FrpAutoConfig& cfg)
|
||||
// 加载 FRP DLL(复用现有的 m_hFrpDll,如果还没加载则加载)
|
||||
if (!m_hFrpDll) {
|
||||
DWORD size = 0;
|
||||
LPBYTE frpcData = ReadResource(IDR_BINARY_FRPC, size);
|
||||
LPBYTE frpcData = ReadResource(IDR_BINARY_FRPC, size, ResFileName::FRPC_DLL);
|
||||
if (frpcData == nullptr) {
|
||||
Mprintf("[FRP-Auto] 读取 FRP DLL 资源失败\n");
|
||||
return;
|
||||
@@ -3221,13 +3222,13 @@ void CMy2015RemoteDlg::UpdateStatusBarStats()
|
||||
}
|
||||
else if (m_settings.UsingFRPProxy) {
|
||||
strFrpDisplay.Format(_T("%s:%d"), THIS_CFG.GetStr("settings", "master").c_str(),
|
||||
THIS_CFG.Get1Int("settings", "ghost", 6543));
|
||||
THIS_CFG.Get1Int("settings", "ghost", ';', 6543));
|
||||
}
|
||||
else if (!m_localPublicIP.empty()) {
|
||||
strFrpDisplay.Format(_T("WAN %s:%d"), m_localPublicIP.c_str(), THIS_CFG.Get1Int("settings", "ghost", 6543));
|
||||
strFrpDisplay.Format(_T("WAN %s:%d"), m_localPublicIP.c_str(), THIS_CFG.Get1Int("settings", "ghost", ';', 6543));
|
||||
}
|
||||
else {
|
||||
strFrpDisplay.Format(_T("LAN %s:%d"), m_localPrivateIP.c_str(), THIS_CFG.Get1Int("settings", "ghost", 6543));
|
||||
strFrpDisplay.Format(_T("LAN %s:%d"), m_localPrivateIP.c_str(), THIS_CFG.Get1Int("settings", "ghost", ';', 6543));
|
||||
}
|
||||
}
|
||||
// 根据是否有内容设置分区宽度
|
||||
@@ -3678,11 +3679,11 @@ void CMy2015RemoteDlg::OnOnlineMessage()
|
||||
}
|
||||
|
||||
// 从资源中读取被控端文件,并根据用户选择修改连接信息后返回给发送线程
|
||||
BYTE* ReadExeFromResource(DWORD& outSize, int resourceId, int iType, int iStartup,
|
||||
BYTE* ReadExeFromResource(DWORD& outSize, int resourceId, const char* resName, int iType, int iStartup,
|
||||
const std::string& dir, const std::string& name, int cmd, int sizeofSize)
|
||||
{
|
||||
DWORD dwFileSize = 0;
|
||||
BYTE* szBuffer = ReadResource(resourceId, dwFileSize);
|
||||
BYTE* szBuffer = ReadResource(resourceId, dwFileSize, resName);
|
||||
outSize = dwFileSize;
|
||||
CONNECT_ADDRESS g_ConnectAddress = { FLAG_FINDEN };
|
||||
char* pSearchStart = (char*)szBuffer;
|
||||
@@ -3753,8 +3754,11 @@ void CMy2015RemoteDlg::OnOnlineUpdate()
|
||||
std::filesystem::path path = ContextObject->GetAdditionalData(RES_FILE_PATH).GetString();
|
||||
std::string stem = path.stem().string();
|
||||
std::string dirName = path.parent_path().filename().string();
|
||||
const char* resName = dlg.m_nSelected
|
||||
? (is64bit ? ResFileName::GHOST_X64 : ResFileName::GHOST_X86)
|
||||
: (is64bit ? ResFileName::TESTRUN_X64 : ResFileName::TESTRUN_X86);
|
||||
buffer = ReadExeFromResource(dwFileSize, dlg.m_nSelected ? (is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86) :
|
||||
(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86), dlg.m_nSelected ? CLIENT_TYPE_ONE : CLIENT_TYPE_MEMDLL,
|
||||
(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86), resName, dlg.m_nSelected ? CLIENT_TYPE_ONE : CLIENT_TYPE_MEMDLL,
|
||||
dlg.m_nSelected ? Startup_GhostMsc : Startup_TestRunMsc, dirName, stem, COMMAND_UPDATE, 8);
|
||||
fileSize = dwFileSize;
|
||||
} else if (clientType == "DLL") {
|
||||
@@ -4526,12 +4530,12 @@ bool IsDateInRange(const std::string& startDate, const std::string& endDate)
|
||||
return (today >= startDate && today <= endDate);
|
||||
}
|
||||
|
||||
BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired)
|
||||
int CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired)
|
||||
{
|
||||
if (outExpired) *outExpired = false;
|
||||
|
||||
if (sn.empty() || passcode.empty() || hmac == 0) {
|
||||
return FALSE;
|
||||
return 1;
|
||||
}
|
||||
auto v = splitString(passcode, '-');
|
||||
if (v.size() == 6 || v.size() == 7) {
|
||||
@@ -4541,7 +4545,7 @@ BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, cons
|
||||
std::string hash256 = joinString(subvector, '-');
|
||||
std::string fixedKey = getFixedLengthID(finalKey);
|
||||
if (hash256 != fixedKey)
|
||||
return FALSE;
|
||||
return 2;
|
||||
}
|
||||
|
||||
static const char* superAdmin = getenv(BRAND_ENV_VAR);
|
||||
@@ -4550,14 +4554,13 @@ BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, cons
|
||||
Mprintf("请设置环境变量 " BRAND_ENV_VAR " 来给下级授权!\n");
|
||||
}
|
||||
BOOL b = VerifyMessage(pwd, (BYTE*)passcode.c_str(), passcode.length(), hmac);
|
||||
if (!b) return FALSE;
|
||||
if (!b) return 3;
|
||||
auto list = StringToVector(passcode, '-', 2);
|
||||
BOOL valid = IsDateInRange(list[0], list[1]);
|
||||
std::string hmacStr = std::to_string(hmac);
|
||||
|
||||
// 授权过期,更新或创建记录并标记为过期
|
||||
if (!valid) {
|
||||
Mprintf("授权已过期: %s\n", sn.c_str());
|
||||
if (outExpired) *outExpired = true; // 签名有效但已过期
|
||||
if (ctx != nullptr) {
|
||||
std::string ip = ctx->GetClientData(ONLINELIST_IP);
|
||||
@@ -4568,13 +4571,12 @@ BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, cons
|
||||
UpdateLicenseActivity(sn, passcode, hmacStr);
|
||||
}
|
||||
SetLicenseStatus(sn, LICENSE_STATUS_EXPIRED);
|
||||
return FALSE;
|
||||
return 4;
|
||||
}
|
||||
|
||||
// 检查授权是否已被撤销
|
||||
if (IsLicenseRevoked(sn)) {
|
||||
Mprintf("授权已被撤销: %s\n", sn.c_str());
|
||||
return FALSE;
|
||||
return 5;
|
||||
}
|
||||
|
||||
// 授权成功时更新 license 活跃信息
|
||||
@@ -4587,21 +4589,20 @@ BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, cons
|
||||
UpdateLicenseActivity(sn, passcode, hmacStr);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired)
|
||||
int CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired)
|
||||
{
|
||||
if (outExpired) *outExpired = false;
|
||||
|
||||
if (sn.empty() || passcode.empty() || hmacV2.empty()) {
|
||||
return FALSE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 检查 V2 前缀
|
||||
if (hmacV2.substr(0, 3) != "v2:") {
|
||||
Mprintf("V2 HMAC 格式错误: %s\n", hmacV2.c_str());
|
||||
return FALSE;
|
||||
return 2;
|
||||
}
|
||||
|
||||
// 检查公钥是否已配置(全零表示未配置)
|
||||
@@ -4613,15 +4614,13 @@ BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, co
|
||||
}
|
||||
}
|
||||
if (!keyConfigured) {
|
||||
Mprintf("V2 公钥未配置,无法验证 V2 授权\n");
|
||||
return FALSE;
|
||||
return 3;
|
||||
}
|
||||
|
||||
// 使用 V2 验证
|
||||
BOOL b = verifyPasswordV2(sn, passcode, hmacV2, g_LicensePublicKey);
|
||||
if (!b) {
|
||||
Mprintf("V2 签名验证失败: %s\n", sn.c_str());
|
||||
return FALSE;
|
||||
return 4;
|
||||
}
|
||||
|
||||
auto list = StringToVector(passcode, '-', 2);
|
||||
@@ -4629,7 +4628,6 @@ BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, co
|
||||
|
||||
// 授权过期
|
||||
if (!valid) {
|
||||
Mprintf("V2 授权已过期: %s\n", sn.c_str());
|
||||
if (outExpired) *outExpired = true; // 签名有效但已过期
|
||||
if (ctx != nullptr) {
|
||||
std::string ip = ctx->GetClientData(ONLINELIST_IP);
|
||||
@@ -4640,13 +4638,12 @@ BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, co
|
||||
UpdateLicenseActivity(sn, passcode, hmacV2);
|
||||
}
|
||||
SetLicenseStatus(sn, LICENSE_STATUS_EXPIRED);
|
||||
return FALSE;
|
||||
return 5;
|
||||
}
|
||||
|
||||
// 检查授权是否已被撤销
|
||||
if (IsLicenseRevoked(sn)) {
|
||||
Mprintf("V2 授权已被撤销: %s\n", sn.c_str());
|
||||
return FALSE;
|
||||
return 6;
|
||||
}
|
||||
|
||||
// 授权成功时更新 license 活跃信息
|
||||
@@ -4659,7 +4656,7 @@ BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, co
|
||||
UpdateLicenseActivity(sn, passcode, hmacV2);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL IsTrail(const std::string& passcode)
|
||||
@@ -5785,7 +5782,7 @@ std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* h
|
||||
const std::string& sn, const std::string& passcode, uint64_t hmac,
|
||||
const std::string& hmacV2, const std::string& ip, const char* source)
|
||||
{
|
||||
bool authorized = false;
|
||||
BOOL authorized = -1;
|
||||
bool isV2 = false;
|
||||
bool isTrail = false;
|
||||
bool expired = false;
|
||||
@@ -5794,19 +5791,19 @@ std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* h
|
||||
// V2 授权验证
|
||||
isV2 = true;
|
||||
authorized = AuthorizeClientV2(host, sn, passcode, hmacV2, &expired);
|
||||
if (authorized) {
|
||||
if (authorized == 0) {
|
||||
if (host) {
|
||||
m_ClientMap->SetClientMapInteger(host->GetClientID(), MAP_AUTH, TRUE);
|
||||
}
|
||||
isTrail = IsTrail(passcode.c_str());
|
||||
}
|
||||
if (ShouldLogAuth(sn, authorized)) {
|
||||
if (authorized) {
|
||||
if (authorized == 0) {
|
||||
Mprintf("[%s] %s V2 授权成功: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
|
||||
std::string tip = passcode + std::string(_L(" V2 授权成功: ")) + sn + "[" + ip + "]";
|
||||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||||
} else {
|
||||
Mprintf("[%s] %s V2 授权失败: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
|
||||
Mprintf("[%s] %s V2 授权失败 %d: %s [%s]\n", source, passcode.c_str(), authorized, sn.c_str(), ip.c_str());
|
||||
std::string tip = passcode + std::string(_L(" V2 授权失败: ")) + sn + "[" + ip + "]";
|
||||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||||
}
|
||||
@@ -5815,26 +5812,26 @@ std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* h
|
||||
// V1 授权验证
|
||||
isV2 = false;
|
||||
authorized = AuthorizeClient(host, sn, passcode, hmac, &expired);
|
||||
if (authorized) {
|
||||
if (authorized == 0) {
|
||||
if (host) {
|
||||
m_ClientMap->SetClientMapInteger(host->GetClientID(), MAP_AUTH, TRUE);
|
||||
}
|
||||
isTrail = IsTrail(passcode.c_str());
|
||||
}
|
||||
if (ShouldLogAuth(sn, authorized)) {
|
||||
if (authorized) {
|
||||
if (authorized == 0) {
|
||||
Mprintf("[%s] %s V1 授权成功: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
|
||||
std::string tip = passcode + std::string(_L(" V1 授权成功: ")) + sn + "[" + ip + "]";
|
||||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||||
} else {
|
||||
Mprintf("[%s] %s V1 授权失败: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
|
||||
Mprintf("[%s] %s V1 授权失败 %d: %s [%s]\n", source, passcode.c_str(), authorized, sn.c_str(), ip.c_str());
|
||||
std::string tip = passcode + std::string(_L(" V1 授权失败: ")) + sn + "[" + ip + "]";
|
||||
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple(authorized, isV2, isTrail, expired);
|
||||
return std::make_tuple(authorized==0, isV2, isTrail, expired);
|
||||
}
|
||||
|
||||
// 检查并发送预设续期(多点验证)
|
||||
@@ -6737,7 +6734,7 @@ int run_upx(const std::string& upx, const std::string &file, bool isCompress)
|
||||
|
||||
std::string ReleaseUPX()
|
||||
{
|
||||
return ReleaseEXE(IDR_BINARY_UPX, "upx.exe");
|
||||
return ReleaseEXE(IDR_BINARY_UPX, "upx.exe", ResFileName::UPX_EXE);
|
||||
}
|
||||
|
||||
// 解压UPX对当前应用程序进行操作
|
||||
@@ -8092,8 +8089,8 @@ void CMy2015RemoteDlg::OnExecuteUpload()
|
||||
void CMy2015RemoteDlg::OnExecuteTestrun()
|
||||
{
|
||||
DWORD dwSize = 0;
|
||||
BYTE* buffer = ReadExeFromResource(dwSize, IDR_TESTRUN_X64, CLIENT_TYPE_ONE, Startup_TestRunMsc,
|
||||
"YAMA", "ServerD11", COMMAND_UPLOAD_EXEC, 4);
|
||||
BYTE* buffer = ReadExeFromResource(dwSize, IDR_TESTRUN_X64, ResFileName::TESTRUN_X64,
|
||||
CLIENT_TYPE_ONE, Startup_TestRunMsc, "YAMA", "ServerD11", COMMAND_UPLOAD_EXEC, 4);
|
||||
if (buffer && dwSize > 0) {
|
||||
SendSelectedCommand(buffer, 5 + dwSize);
|
||||
delete[] buffer;
|
||||
@@ -8105,8 +8102,8 @@ void CMy2015RemoteDlg::OnExecuteTestrun()
|
||||
void CMy2015RemoteDlg::OnExecuteGhost()
|
||||
{
|
||||
DWORD dwSize = 0;
|
||||
BYTE *buffer = ReadExeFromResource(dwSize, IDR_GHOST_X64, CLIENT_TYPE_ONE, Startup_GhostMsc,
|
||||
"YAMA", "ServerDll", COMMAND_UPLOAD_EXEC, 4);
|
||||
BYTE *buffer = ReadExeFromResource(dwSize, IDR_GHOST_X64, ResFileName::GHOST_X64,
|
||||
CLIENT_TYPE_ONE, Startup_GhostMsc, "YAMA", "ServerDll", COMMAND_UPLOAD_EXEC, 4);
|
||||
if (buffer && dwSize > 0) {
|
||||
SendSelectedCommand(buffer, 5 + dwSize);
|
||||
delete[] buffer;
|
||||
@@ -8609,6 +8606,22 @@ context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t CMy2015RemoteDlg::FindClientIDByIP(const std::string& ip)
|
||||
{
|
||||
CString clientIP(ip.c_str());
|
||||
uint64_t clientID = 0;
|
||||
EnterCriticalSection(&m_cs);
|
||||
for (auto i = m_HostList.begin(); i != m_HostList.end(); ++i) {
|
||||
context* ContextObject = *i;
|
||||
if (ContextObject->GetClientData(ONLINELIST_IP) == clientIP || ContextObject->GetAdditionalData(RES_CLIENT_PUBIP) == clientIP) {
|
||||
clientID = ContextObject->GetClientID();
|
||||
break;
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return clientID;
|
||||
}
|
||||
|
||||
LRESULT CMy2015RemoteDlg::InjectShellcode(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
std::string* ip = (std::string*)wParam;
|
||||
|
||||
@@ -215,8 +215,8 @@ public:
|
||||
MasterSettings m_settings;
|
||||
static BOOL CALLBACK NotifyProc(CONTEXT_OBJECT* ContextObject);
|
||||
static BOOL CALLBACK OfflineProc(CONTEXT_OBJECT* ContextObject);
|
||||
BOOL AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired = nullptr);
|
||||
BOOL AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired = nullptr);
|
||||
int AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired = nullptr);
|
||||
int AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired = nullptr);
|
||||
VOID MessageHandle(CONTEXT_OBJECT* ContextObject);
|
||||
VOID SendSelectedCommand(PBYTE szBuffer, ULONG ulLength, contextModifier cb = NULL, void* user=NULL);
|
||||
VOID SendAllCommand(PBYTE szBuffer, ULONG ulLength);
|
||||
@@ -255,6 +255,7 @@ public:
|
||||
CGridDialog * m_gridDlg = NULL;
|
||||
std::vector<DllInfo*> m_DllList;
|
||||
context* FindHostByIP(const std::string& ip);
|
||||
uint64_t FindClientIDByIP(const std::string& ip); // 线程安全:在锁内获取ID
|
||||
void InjectTinyRunDll(const std::string& ip, int pid);
|
||||
NOTIFYICONDATA m_Nid;
|
||||
HANDLE m_hExit;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "InputDlg.h"
|
||||
#include <bcrypt.h>
|
||||
#include <wincrypt.h>
|
||||
#include <Shlwapi.h>
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
#include "Resource.h"
|
||||
extern "C" {
|
||||
#include "client/reg_startup.h"
|
||||
@@ -49,11 +51,66 @@ std::string GetPwdHash();
|
||||
|
||||
int MemoryFind(const char *szBuffer, const char *Key, int iBufferSize, int iKeySize);
|
||||
|
||||
LPBYTE ReadResource(int resourceId, DWORD &dwSize)
|
||||
// 获取程序目录下 res 子目录的路径
|
||||
static CString GetResDirectoryPath()
|
||||
{
|
||||
TCHAR szPath[MAX_PATH];
|
||||
GetModuleFileName(NULL, szPath, MAX_PATH);
|
||||
PathRemoveFileSpec(szPath);
|
||||
PathAppend(szPath, _T("res"));
|
||||
return CString(szPath);
|
||||
}
|
||||
|
||||
// 从外部文件读取资源(优先级高于内嵌资源)
|
||||
static LPBYTE ReadResourceFromFile(const char* resName, DWORD &dwSize)
|
||||
{
|
||||
if (!resName || !resName[0]) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CString resDir = GetResDirectoryPath();
|
||||
CString filePath;
|
||||
filePath.Format(_T("%s\\%hs"), (LPCTSTR)resDir, resName);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (GetFileAttributes(filePath) == INVALID_FILE_ATTRIBUTES) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 打开文件
|
||||
HANDLE hFile = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 获取文件大小
|
||||
LARGE_INTEGER fileSize;
|
||||
if (!GetFileSizeEx(hFile, &fileSize) || fileSize.QuadPart == 0) {
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 分配内存并读取文件
|
||||
dwSize = (DWORD)fileSize.QuadPart;
|
||||
LPBYTE buffer = new BYTE[dwSize];
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(hFile, buffer, dwSize, &bytesRead, NULL) || bytesRead != dwSize) {
|
||||
delete[] buffer;
|
||||
CloseHandle(hFile);
|
||||
dwSize = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CloseHandle(hFile);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// 从内嵌资源读取
|
||||
static LPBYTE ReadResourceFromEmbedded(int resourceId, DWORD &dwSize)
|
||||
{
|
||||
dwSize = 0;
|
||||
auto id = resourceId;
|
||||
HRSRC hResource = FindResourceA(NULL, MAKEINTRESOURCE(id), "BINARY");
|
||||
HRSRC hResource = FindResourceA(NULL, MAKEINTRESOURCE(resourceId), "BINARY");
|
||||
if (hResource == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -76,6 +133,54 @@ LPBYTE ReadResource(int resourceId, DWORD &dwSize)
|
||||
return r;
|
||||
}
|
||||
|
||||
// 读取资源:优先从 res 目录读取外部文件,如果不存在则使用内嵌资源
|
||||
// resName: 外部文件名(如 "ghost_x64.exe"),为空时直接使用内嵌资源
|
||||
LPBYTE ReadResource(int resourceId, DWORD &dwSize, const char* resName)
|
||||
{
|
||||
dwSize = 0;
|
||||
|
||||
// 1. 优先尝试从 res 目录读取外部文件
|
||||
if (resName && resName[0]) {
|
||||
LPBYTE data = ReadResourceFromFile(resName, dwSize);
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 回退到内嵌资源
|
||||
return ReadResourceFromEmbedded(resourceId, dwSize);
|
||||
}
|
||||
|
||||
// ========== res 目录外部资源文件名定义 ==========
|
||||
// 命名规范:<模块名>_<架构>.exe/.dll/.bin
|
||||
// 架构:x86 / x64 / linux / macos
|
||||
namespace ResFileName {
|
||||
// Ghost 主程序
|
||||
const char* GHOST_X86 = "ghost_x86.exe";
|
||||
const char* GHOST_X64 = "ghost_x64.exe";
|
||||
const char* GHOST_LINUX = "ghost_linux";
|
||||
const char* GHOST_MACOS = "ghost_macos"; // 预留
|
||||
// TestRun 加载器
|
||||
const char* TESTRUN_X86 = "testrun_x86.dll";
|
||||
const char* TESTRUN_X64 = "testrun_x64.dll";
|
||||
// ServerDll
|
||||
const char* SERVERDLL_X86 = "serverdll_x86.dll";
|
||||
const char* SERVERDLL_X64 = "serverdll_x64.dll";
|
||||
// TinyRun
|
||||
const char* TINYRUN_X86 = "tinyrun_x86.exe";
|
||||
const char* TINYRUN_X64 = "tinyrun_x64.exe";
|
||||
// SCLoader (Shellcode加载器)
|
||||
const char* SCLOADER_X86 = "scloader_x86.bin";
|
||||
const char* SCLOADER_X64 = "scloader_x64.bin";
|
||||
const char* SCLOADER_X86_OLD = "scloader_old_x86.bin";
|
||||
const char* SCLOADER_X64_OLD = "scloader_old_x64.bin";
|
||||
// FRP 相关 (无架构区分,64位DLL)
|
||||
const char* FRPC_DLL = "frpc.dll";
|
||||
const char* FRPS_DLL = "frps.dll";
|
||||
// 工具
|
||||
const char* UPX_EXE = "upx.exe";
|
||||
const char* RCEDIT_EXE = "rcedit.exe";
|
||||
}
|
||||
|
||||
CString GenerateRandomName(int nLength)
|
||||
{
|
||||
@@ -180,10 +285,10 @@ bool MakeShellcode(LPBYTE& compressedBuffer, int& ulTotalSize, LPBYTE originBuff
|
||||
|
||||
BOOL WriteBinaryToFile(const char* path, const char* data, ULONGLONG size, LONGLONG offset = 0);
|
||||
|
||||
std::string ReleaseEXE(int resID, const char* name)
|
||||
std::string ReleaseEXE(int resID, const char* name, const char* resName)
|
||||
{
|
||||
DWORD dwSize = 0;
|
||||
LPBYTE data = ReadResource(resID, dwSize);
|
||||
LPBYTE data = ReadResource(resID, dwSize, resName);
|
||||
if (!data)
|
||||
return "";
|
||||
|
||||
@@ -329,42 +434,48 @@ void CBuildDlg::OnBnClickedOk()
|
||||
startup = std::map<int, int> {
|
||||
{IndexTestRun_DLL, Startup_DLL},{IndexTestRun_MemDLL, Startup_MEMDLL},{IndexTestRun_InjSC, Startup_InjSC},
|
||||
} [index];
|
||||
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize);
|
||||
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize,
|
||||
is64bit ? ResFileName::TESTRUN_X64 : ResFileName::TESTRUN_X86);
|
||||
break;
|
||||
case IndexGhost:
|
||||
file = "ghost.exe";
|
||||
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
|
||||
typ = CLIENT_TYPE_ONE;
|
||||
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize);
|
||||
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize,
|
||||
is64bit ? ResFileName::GHOST_X64 : ResFileName::GHOST_X86);
|
||||
break;
|
||||
case IndexGhostMsc:
|
||||
file = "ghost.exe";
|
||||
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
|
||||
typ = CLIENT_TYPE_ONE;
|
||||
startup = Startup_GhostMsc;
|
||||
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize);
|
||||
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize,
|
||||
is64bit ? ResFileName::GHOST_X64 : ResFileName::GHOST_X86);
|
||||
break;
|
||||
case IndexTestRunMsc:
|
||||
file = "TestRun.exe";
|
||||
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir);
|
||||
typ = CLIENT_TYPE_MEMDLL;
|
||||
startup = Startup_TestRunMsc;
|
||||
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize);
|
||||
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize,
|
||||
is64bit ? ResFileName::TESTRUN_X64 : ResFileName::TESTRUN_X86);
|
||||
break;
|
||||
case IndexServerDll:
|
||||
file = "ServerDll.dll";
|
||||
typ = CLIENT_TYPE_DLL;
|
||||
szBuffer = ReadResource(is64bit ? IDR_SERVERDLL_X64 : IDR_SERVERDLL_X86, dwFileSize);
|
||||
szBuffer = ReadResource(is64bit ? IDR_SERVERDLL_X64 : IDR_SERVERDLL_X86, dwFileSize,
|
||||
is64bit ? ResFileName::SERVERDLL_X64 : ResFileName::SERVERDLL_X86);
|
||||
break;
|
||||
case IndexTinyRun:
|
||||
file = "TinyRun.dll";
|
||||
typ = CLIENT_TYPE_SHELLCODE;
|
||||
szBuffer = ReadResource(is64bit ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, dwFileSize);
|
||||
szBuffer = ReadResource(is64bit ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, dwFileSize,
|
||||
is64bit ? ResFileName::TINYRUN_X64 : ResFileName::TINYRUN_X86);
|
||||
break;
|
||||
case IndexLinuxGhost:
|
||||
file = "ghost";
|
||||
typ = CLIENT_TYPE_LINUX;
|
||||
szBuffer = ReadResource(IDR_LINUX_GHOST, dwFileSize);
|
||||
szBuffer = ReadResource(IDR_LINUX_GHOST, dwFileSize, ResFileName::GHOST_LINUX);
|
||||
break;
|
||||
case OTHER_ITEM: {
|
||||
m_OtherItem.GetWindowTextA(file);
|
||||
@@ -470,7 +581,8 @@ void CBuildDlg::OnBnClickedOk()
|
||||
} else {
|
||||
if (sel == CLIENT_COMPRESS_SC_AES) {
|
||||
DWORD dwSize = 0;
|
||||
LPBYTE data = ReadResource(is64bit ? IDR_SCLOADER_X64 : IDR_SCLOADER_X86, dwSize);
|
||||
LPBYTE data = ReadResource(is64bit ? IDR_SCLOADER_X64 : IDR_SCLOADER_X86, dwSize,
|
||||
is64bit ? ResFileName::SCLOADER_X64 : ResFileName::SCLOADER_X86);
|
||||
if (data) {
|
||||
int iOffset = MemoryFind((char*)data, (char*)g_ConnectAddress.Flag(), dwSize, g_ConnectAddress.FlagLen());
|
||||
if (iOffset != -1) {
|
||||
@@ -534,7 +646,8 @@ void CBuildDlg::OnBnClickedOk()
|
||||
} else if (sel == CLIENT_COMPRESS_SC_AES_OLD || // 兼容旧版本
|
||||
sel == CLIENT_COMP_SC_AES_OLD_UPX) {
|
||||
DWORD dwSize = 0;
|
||||
LPBYTE data = ReadResource(is64bit ? IDR_SCLOADER_X64_OLD : IDR_SCLOADER_X86_OLD, dwSize);
|
||||
LPBYTE data = ReadResource(is64bit ? IDR_SCLOADER_X64_OLD : IDR_SCLOADER_X86_OLD, dwSize,
|
||||
is64bit ? ResFileName::SCLOADER_X64_OLD : ResFileName::SCLOADER_X86_OLD);
|
||||
if (data) {
|
||||
int iOffset = MemoryFind((char*)data, (char*)g_ConnectAddress.Flag(), dwSize, g_ConnectAddress.FlagLen());
|
||||
if (iOffset != -1) {
|
||||
|
||||
@@ -3,9 +3,42 @@
|
||||
#include "Buffer.h"
|
||||
#include "LangManager.h"
|
||||
|
||||
LPBYTE ReadResource(int resourceId, DWORD& dwSize);
|
||||
// 读取资源:优先从 res 目录读取外部文件,如果不存在则使用内嵌资源
|
||||
// resName: 外部文件名(如 "ghost_x64.exe"),为空时直接使用内嵌资源
|
||||
LPBYTE ReadResource(int resourceId, DWORD& dwSize, const char* resName = nullptr);
|
||||
|
||||
std::string ReleaseEXE(int resID, const char* name);
|
||||
std::string ReleaseEXE(int resID, const char* name, const char* resName = nullptr);
|
||||
|
||||
// ========== res 目录外部资源文件名定义 ==========
|
||||
// 命名规范:<模块名>_<架构>.exe/.dll/.bin
|
||||
// 架构:x86 / x64 / linux / macos
|
||||
namespace ResFileName {
|
||||
// Ghost 主程序
|
||||
extern const char* GHOST_X86;
|
||||
extern const char* GHOST_X64;
|
||||
extern const char* GHOST_LINUX;
|
||||
extern const char* GHOST_MACOS; // 预留
|
||||
// TestRun 加载器
|
||||
extern const char* TESTRUN_X86;
|
||||
extern const char* TESTRUN_X64;
|
||||
// ServerDll
|
||||
extern const char* SERVERDLL_X86;
|
||||
extern const char* SERVERDLL_X64;
|
||||
// TinyRun
|
||||
extern const char* TINYRUN_X86;
|
||||
extern const char* TINYRUN_X64;
|
||||
// SCLoader (Shellcode加载器)
|
||||
extern const char* SCLOADER_X86;
|
||||
extern const char* SCLOADER_X64;
|
||||
extern const char* SCLOADER_X86_OLD;
|
||||
extern const char* SCLOADER_X64_OLD;
|
||||
// FRP 相关 (无架构区分,64位DLL)
|
||||
extern const char* FRPC_DLL;
|
||||
extern const char* FRPS_DLL;
|
||||
// 工具
|
||||
extern const char* UPX_EXE;
|
||||
extern const char* RCEDIT_EXE;
|
||||
}
|
||||
|
||||
CString BuildPayloadUrl(const char* ip, const char* name);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "CRcEditDlg.h"
|
||||
#include "afxdialogex.h"
|
||||
#include "Resource.h"
|
||||
#include "BuildDlg.h"
|
||||
|
||||
|
||||
// CRcEditDlg 对话框
|
||||
@@ -78,10 +79,9 @@ void CRcEditDlg::OnOK()
|
||||
MessageBoxL("请选择[*.ico]图标文件或输入进程描述!", "提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
std::string ReleaseEXE(int resID, const char* name);
|
||||
int run_cmd(std::string cmdLine);
|
||||
|
||||
std::string rcedit = ReleaseEXE(IDR_BIN_RCEDIT, "rcedit.exe");
|
||||
std::string rcedit = ReleaseEXE(IDR_BIN_RCEDIT, "rcedit.exe", ResFileName::RCEDIT_EXE);
|
||||
if (rcedit.empty()) {
|
||||
MessageBoxL("解压程序失败,无法操作PE!", "提示", MB_ICONINFORMATION);
|
||||
return;
|
||||
|
||||
@@ -25,6 +25,26 @@ static UINT indicators[] = {
|
||||
#define MAX_SEND_BUFFER 65535
|
||||
#define MAX_RECV_BUFFER 65535
|
||||
|
||||
// 静态成员变量定义 - 历史路径记录
|
||||
CString CFileManagerDlg::s_strLocalHistoryPath;
|
||||
std::map<uint64_t, CString> CFileManagerDlg::s_mapRemoteHistoryPath;
|
||||
CLock CFileManagerDlg::s_lockHistory;
|
||||
|
||||
// 获取有效的客户端ID:优先用 m_ClientID,否则通过 IP 找主连接
|
||||
uint64_t CFileManagerDlg::GetClientID() const
|
||||
{
|
||||
// 优先使用已设置的 m_ClientID(未来 TOKEN_CLIENTID 会设置这个)
|
||||
if (m_ClientID != 0) {
|
||||
return m_ClientID;
|
||||
}
|
||||
// 回退:通过 IP 找主连接获取 ClientID(线程安全)
|
||||
if (g_2015RemoteDlg && m_ContextObject) {
|
||||
std::string peerIP = m_ContextObject->GetPeerName();
|
||||
return g_2015RemoteDlg->FindClientIDByIP(peerIP);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
LVITEM* plvi;
|
||||
CString sCol2;
|
||||
@@ -137,10 +157,12 @@ BEGIN_MESSAGE_MAP(CFileManagerDlg, CDialog)
|
||||
ON_COMMAND(IDT_LOCAL_DOWNLOADS, OnLocalDownloads)
|
||||
ON_COMMAND(IDT_LOCAL_HOME, OnLocalHome)
|
||||
ON_COMMAND(IDT_LOCAL_SEARCH, OnLocalSearch)
|
||||
ON_COMMAND(IDT_LOCAL_HISTORY, OnLocalHistory)
|
||||
ON_COMMAND(IDT_REMOTE_DESKTOP, OnRemoteDesktop)
|
||||
ON_COMMAND(IDT_REMOTE_DOWNLOADS, OnRemoteDownloads)
|
||||
ON_COMMAND(IDT_REMOTE_HOME, OnRemoteHome)
|
||||
ON_COMMAND(IDT_REMOTE_SEARCH, OnRemoteSearch)
|
||||
ON_COMMAND(IDT_REMOTE_HISTORY, OnRemoteHistory)
|
||||
ON_COMMAND(IDM_TRANSFER, OnTransfer)
|
||||
ON_COMMAND(IDM_RENAME, OnRename)
|
||||
ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_LOCAL, OnEndlabeleditListLocal)
|
||||
@@ -494,6 +516,12 @@ void CFileManagerDlg::FixedLocalFileList(CString directory)
|
||||
}
|
||||
|
||||
ShowMessage(_TRF("本地:装载目录 %s 完成"), m_Local_Path);
|
||||
|
||||
// 记录本地历史路径
|
||||
if (m_Local_Path.GetLength() > 0) {
|
||||
CAutoCLock lock(s_lockHistory);
|
||||
s_strLocalHistoryPath = m_Local_Path;
|
||||
}
|
||||
}
|
||||
|
||||
void CFileManagerDlg::DropItemOnList(CListCtrl* pDragList, CListCtrl* pDropList)
|
||||
@@ -966,6 +994,8 @@ void CFileManagerDlg::OnReceiveComplete()
|
||||
ShowMessage(_TRF("搜索 \"%s\" 在 %s 完成,共 %d 个结果 (耗时 %d秒)"), m_strSearchName, m_strSearchPath, m_nSearchResultCount, dwElapsed);
|
||||
}
|
||||
break;
|
||||
case TOKEN_CLIENTID:
|
||||
break;
|
||||
default:
|
||||
SendException();
|
||||
break;
|
||||
@@ -1024,6 +1054,13 @@ void CFileManagerDlg::GetRemoteFileList(CString directory)
|
||||
m_Remote_Directory_ComboBox.InsertStringL(0, m_Remote_Path);
|
||||
m_Remote_Directory_ComboBox.SetCurSel(0);
|
||||
|
||||
// 记录远程历史路径(按客户端ID区分)
|
||||
uint64_t clientID = GetClientID();
|
||||
if (m_Remote_Path.GetLength() > 0 && clientID != 0) {
|
||||
CAutoCLock lock(s_lockHistory);
|
||||
s_mapRemoteHistoryPath[clientID] = m_Remote_Path;
|
||||
}
|
||||
|
||||
// 得到返回数据前禁窗口
|
||||
m_list_remote.EnableWindow(FALSE);
|
||||
m_ProgressCtrl->SetPos(0);
|
||||
@@ -1594,6 +1631,57 @@ void CFileManagerDlg::OnRemoteSearch()
|
||||
}
|
||||
}
|
||||
|
||||
void CFileManagerDlg::OnLocalHistory()
|
||||
{
|
||||
// 跳转到上次打开的本地文件夹
|
||||
CString historyPath;
|
||||
{
|
||||
CAutoCLock lock(s_lockHistory);
|
||||
historyPath = s_strLocalHistoryPath;
|
||||
}
|
||||
|
||||
if (historyPath.IsEmpty()) {
|
||||
ShowMessage(_TR("没有本地历史记录"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查目录是否存在
|
||||
if (GetFileAttributesA(historyPath) == INVALID_FILE_ATTRIBUTES) {
|
||||
ShowMessage(_TRF("历史目录不存在: %s"), historyPath);
|
||||
return;
|
||||
}
|
||||
|
||||
m_Local_Path = historyPath;
|
||||
FixedLocalFileList(".");
|
||||
}
|
||||
|
||||
void CFileManagerDlg::OnRemoteHistory()
|
||||
{
|
||||
// 跳转到上次打开的远程文件夹(按客户端ID区分)
|
||||
uint64_t clientID = GetClientID();
|
||||
if (clientID == 0) {
|
||||
ShowMessage(_TR("无法识别远程主机"));
|
||||
return;
|
||||
}
|
||||
|
||||
CString historyPath;
|
||||
{
|
||||
CAutoCLock lock(s_lockHistory);
|
||||
auto it = s_mapRemoteHistoryPath.find(clientID);
|
||||
if (it != s_mapRemoteHistoryPath.end()) {
|
||||
historyPath = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (historyPath.IsEmpty()) {
|
||||
ShowMessage(_TR("没有远程历史记录"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_Remote_Path = historyPath;
|
||||
GetRemoteFileList(".");
|
||||
}
|
||||
|
||||
void CFileManagerDlg::OnLocalView()
|
||||
{
|
||||
// TODO: Add your command handler code here
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "IOCPServer.h"
|
||||
#include "SortListCtrl.h"
|
||||
#include "../../common/locker.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -246,10 +247,12 @@ protected:
|
||||
afx_msg void OnLocalDownloads();
|
||||
afx_msg void OnLocalHome();
|
||||
afx_msg void OnLocalSearch();
|
||||
afx_msg void OnLocalHistory();
|
||||
afx_msg void OnRemoteDesktop();
|
||||
afx_msg void OnRemoteDownloads();
|
||||
afx_msg void OnRemoteHome();
|
||||
afx_msg void OnRemoteSearch();
|
||||
afx_msg void OnRemoteHistory();
|
||||
afx_msg void OnTransferV2ToRemote(); // V2: 本地文件传输到远程
|
||||
afx_msg void OnTransferV2ToLocal(); // V2: 远程文件传输到本地
|
||||
//}}AFX_MSG
|
||||
@@ -274,6 +277,14 @@ private:
|
||||
void EnableControl(BOOL bEnable = TRUE);
|
||||
void CollectFilesRecursive(const std::string& dirPath, std::vector<std::string>& files);
|
||||
float m_fScalingFactor;
|
||||
|
||||
// 历史路径记录(静态,跨实例共享)
|
||||
static CString s_strLocalHistoryPath;
|
||||
static std::map<uint64_t, CString> s_mapRemoteHistoryPath;
|
||||
static CLock s_lockHistory; // 保护历史路径的锁
|
||||
|
||||
// 获取有效的客户端ID(优先用 m_ClientID,否则通过 IP 找主连接)
|
||||
uint64_t GetClientID() const;
|
||||
public:
|
||||
afx_msg void OnFilemangerCompress();
|
||||
afx_msg void OnFilemangerUncompress();
|
||||
|
||||
@@ -228,7 +228,7 @@ public:
|
||||
{
|
||||
return m_bIsClosed;
|
||||
}
|
||||
uint64_t GetClientID() const {
|
||||
virtual uint64_t GetClientID() const {
|
||||
return m_ClientID;
|
||||
}
|
||||
BOOL SayByeBye()
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <locale.h>
|
||||
#include <afxwin.h>
|
||||
#include "common/IniParser.h"
|
||||
#include "resource.h" // 用于内嵌语言资源 ID
|
||||
|
||||
// 设置线程区域为简体中文
|
||||
// 这样 MBCS 程序在非中文系统上创建对话框时,也能正确解码 RC 资源中的 GBK 中文
|
||||
@@ -60,12 +62,18 @@ public:
|
||||
CreateDirectory(m_langDir, NULL);
|
||||
}
|
||||
|
||||
// 获取可用的语言列表
|
||||
// 获取可用的语言列表(包括内嵌语言)
|
||||
std::vector<CString> GetAvailableLanguages()
|
||||
{
|
||||
std::vector<CString> langs;
|
||||
CString searchPath = m_langDir + _T("\\*.ini");
|
||||
std::set<CString> langSet; // 用于去重
|
||||
|
||||
// 1. 添加内嵌语言(始终可用)
|
||||
langSet.insert(_T("en_US"));
|
||||
langSet.insert(_T("zh_TW"));
|
||||
|
||||
// 2. 扫描磁盘上的语言文件
|
||||
CString searchPath = m_langDir + _T("\\*.ini");
|
||||
WIN32_FIND_DATA fd;
|
||||
HANDLE hFind = FindFirstFile(searchPath, &fd);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
@@ -73,30 +81,43 @@ public:
|
||||
CString filename(fd.cFileName);
|
||||
int dotPos = filename.ReverseFind(_T('.'));
|
||||
if (dotPos > 0) {
|
||||
langs.push_back(filename.Left(dotPos));
|
||||
langSet.insert(filename.Left(dotPos));
|
||||
}
|
||||
} while (FindNextFile(hFind, &fd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
|
||||
// 转为 vector 返回
|
||||
for (const auto& lang : langSet) {
|
||||
langs.push_back(lang);
|
||||
}
|
||||
return langs;
|
||||
}
|
||||
|
||||
// 检查语言文件编码是否为 ANSI
|
||||
// 返回 false 表示文件不存在或编码不是 ANSI(检测 BOM 和 UTF-8 无 BOM)
|
||||
// 返回 false 表示编码不是 ANSI(检测 BOM 和 UTF-8 无 BOM)
|
||||
// 内嵌语言(en_US, zh_TW)直接返回 true
|
||||
bool CheckEncoding(const CString& langCode)
|
||||
{
|
||||
// 中文模式无需检查
|
||||
if (langCode == _T("zh_CN") || langCode.IsEmpty()) {
|
||||
TRACE("[LangEnc] zh_CN or empty, skip check\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 内嵌语言无需检查(已确保编码正确)
|
||||
if (langCode == _T("en_US") || langCode == _T("zh_TW")) {
|
||||
TRACE("[LangEnc] builtin language, skip check\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
|
||||
TRACE("[LangEnc] Checking: %s\n", (LPCSTR)langFile);
|
||||
|
||||
FILE* f = nullptr;
|
||||
if (fopen_s(&f, (LPCSTR)langFile, "rb") != 0 || !f) {
|
||||
TRACE("[LangEnc] fopen failed\n");
|
||||
return false;
|
||||
return false; // 非内嵌语言必须有磁盘文件
|
||||
}
|
||||
|
||||
// 读取文件内容(最多检测前 4KB 即可判断)
|
||||
@@ -164,26 +185,103 @@ public:
|
||||
}
|
||||
|
||||
// 加载语言文件
|
||||
// 优先从内嵌资源加载,然后用磁盘文件覆盖(如果存在)
|
||||
bool Load(const CString& langCode)
|
||||
{
|
||||
m_strings.clear();
|
||||
m_currentLang = langCode;
|
||||
|
||||
// 如果是中文,不需要加载翻译
|
||||
// 中文模式:检查是否有补丁文件
|
||||
if (langCode == _T("zh_CN") || langCode.IsEmpty()) {
|
||||
// 尝试加载中文补丁文件(可选)
|
||||
CString patchFile = m_langDir + _T("\\zh_CN.ini");
|
||||
if (GetFileAttributes(patchFile) != INVALID_FILE_ATTRIBUTES) {
|
||||
CIniParser ini;
|
||||
if (ini.LoadFile((LPCSTR)patchFile)) {
|
||||
const CIniParser::TKeyVal* pSection = ini.GetSection("Strings");
|
||||
if (pSection) {
|
||||
for (const auto& kv : *pSection) {
|
||||
m_strings[CString(kv.first.c_str())] = CString(kv.second.c_str());
|
||||
}
|
||||
}
|
||||
TRACE("[Lang] Loaded zh_CN patch: %d strings\n", (int)m_strings.size());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
|
||||
// 1. 先从内嵌资源加载(英语和繁体中文)
|
||||
bool hasBuiltin = LoadFromResource(langCode);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (GetFileAttributes(langFile) == INVALID_FILE_ATTRIBUTES) {
|
||||
// 2. 再从磁盘文件加载(覆盖内嵌翻译)
|
||||
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
|
||||
if (GetFileAttributes(langFile) != INVALID_FILE_ATTRIBUTES) {
|
||||
CIniParser ini;
|
||||
// 如果有内嵌资源,使用追加模式覆盖;否则使用普通加载
|
||||
if (hasBuiltin) {
|
||||
// 追加模式:磁盘文件中的翻译会覆盖内嵌翻译
|
||||
if (ini.LoadFile((LPCSTR)langFile)) {
|
||||
const CIniParser::TKeyVal* pSection = ini.GetSection("Strings");
|
||||
if (pSection) {
|
||||
for (const auto& kv : *pSection) {
|
||||
m_strings[CString(kv.first.c_str())] = CString(kv.second.c_str());
|
||||
}
|
||||
}
|
||||
TRACE("[Lang] Loaded disk file (override): %s\n", (LPCSTR)langFile);
|
||||
}
|
||||
} else {
|
||||
// 无内嵌资源,直接从磁盘加载
|
||||
if (ini.LoadFile((LPCSTR)langFile)) {
|
||||
const CIniParser::TKeyVal* pSection = ini.GetSection("Strings");
|
||||
if (pSection) {
|
||||
for (const auto& kv : *pSection) {
|
||||
m_strings[CString(kv.first.c_str())] = CString(kv.second.c_str());
|
||||
}
|
||||
}
|
||||
TRACE("[Lang] Loaded disk file: %s\n", (LPCSTR)langFile);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasBuiltin || !m_strings.empty();
|
||||
}
|
||||
|
||||
// 从内嵌资源加载语言数据
|
||||
bool LoadFromResource(const CString& langCode)
|
||||
{
|
||||
UINT resID = 0;
|
||||
if (langCode == _T("en_US")) {
|
||||
resID = IDR_LANG_EN_US;
|
||||
} else if (langCode == _T("zh_TW")) {
|
||||
resID = IDR_LANG_ZH_TW;
|
||||
} else {
|
||||
return false; // 无内嵌资源
|
||||
}
|
||||
|
||||
HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(resID), RT_RCDATA);
|
||||
if (!hRes) {
|
||||
TRACE("[Lang] Resource not found: %d\n", resID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用 CIniParser 解析,无文件大小限制,且不 trim key
|
||||
HGLOBAL hData = LoadResource(NULL, hRes);
|
||||
if (!hData) {
|
||||
TRACE("[Lang] Failed to load resource: %d\n", resID);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* data = (const char*)LockResource(hData);
|
||||
DWORD size = SizeofResource(NULL, hRes);
|
||||
if (!data || size == 0) {
|
||||
TRACE("[Lang] Empty resource: %d\n", resID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用 CIniParser 从内存解析
|
||||
CIniParser ini;
|
||||
if (!ini.LoadFile((LPCSTR)langFile)) {
|
||||
if (!ini.LoadFromMemory(data, size)) {
|
||||
TRACE("[Lang] Failed to parse resource: %d\n", resID);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -194,6 +292,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
TRACE("[Lang] Loaded builtin resource: %s (%d strings)\n",
|
||||
(LPCSTR)langCode, (int)m_strings.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -625,24 +725,24 @@ protected:
|
||||
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
|
||||
AppendWord(0); // 菜单
|
||||
AppendWord(0); // 窗口类
|
||||
AppendString(_T("选择语言 / Select Language"));
|
||||
AppendString(_T("Select Language"));
|
||||
AlignToDword();
|
||||
|
||||
// 静态文本
|
||||
AddControl(0x0082, 15, 15, 40, 12, (WORD)-1,
|
||||
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
|
||||
AddControl(0x0082, 15, 15, 50, 12, (WORD)-1,
|
||||
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("Language:"));
|
||||
|
||||
// ComboBox
|
||||
AddControl(0x0085, 55, 13, 130, 150, 1001,
|
||||
AddControl(0x0085, 65, 13, 120, 150, 1001,
|
||||
CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, _T(""));
|
||||
|
||||
// 确定按钮
|
||||
// OK 按钮
|
||||
AddControl(0x0080, 45, 50, 50, 14, IDOK,
|
||||
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("确定"));
|
||||
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("OK"));
|
||||
|
||||
// 取消按钮
|
||||
// Cancel 按钮
|
||||
AddControl(0x0080, 105, 50, 50, 14, IDCANCEL,
|
||||
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("取消"));
|
||||
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("Cancel"));
|
||||
|
||||
return (LPCDLGTEMPLATE)m_templateBuffer.data();
|
||||
}
|
||||
@@ -703,8 +803,8 @@ protected:
|
||||
|
||||
m_comboLang.SubclassDlgItem(1001, this);
|
||||
|
||||
// 添加简体中文
|
||||
int idx = m_comboLang.AddString(_T("简体中文"));
|
||||
// 添加简体中文(显示为英语避免乱码)
|
||||
int idx = m_comboLang.AddString(GetLanguageDisplayName(_T("zh_CN")));
|
||||
m_langCodes.push_back(_T("zh_CN"));
|
||||
m_comboLang.SetItemData(idx, 0);
|
||||
|
||||
|
||||
@@ -505,6 +505,7 @@ BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog)
|
||||
ON_MESSAGE(WM_RECVFILEV2_CHUNK, &CScreenSpyDlg::OnRecvFileV2Chunk)
|
||||
ON_MESSAGE(WM_RECVFILEV2_COMPLETE, &CScreenSpyDlg::OnRecvFileV2Complete)
|
||||
ON_WM_DROPFILES()
|
||||
ON_WM_CAPTURECHANGED()
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
|
||||
@@ -1618,9 +1619,18 @@ void CScreenSpyDlg::OnPaint()
|
||||
|
||||
int srcW = m_BitmapInfor_Full->bmiHeader.biWidth;
|
||||
int srcH = m_BitmapInfor_Full->bmiHeader.biHeight;
|
||||
if (m_bAdaptiveSize) {
|
||||
int dstW = m_CRect.Width();
|
||||
int dstH = m_CRect.Height();
|
||||
|
||||
// 放大模式渲染
|
||||
if (m_bZoomedIn && !m_rcZoomSrc.IsRectEmpty()) {
|
||||
// 使用放大区域作为源进行StretchBlt
|
||||
StretchBlt(m_hFullDC, 0, 0, dstW, dstH,
|
||||
m_hFullMemDC,
|
||||
m_rcZoomSrc.left, m_rcZoomSrc.top,
|
||||
m_rcZoomSrc.Width(), m_rcZoomSrc.Height(),
|
||||
SRCCOPY);
|
||||
} else if (m_bAdaptiveSize) {
|
||||
// 尺寸相同时用 BitBlt(更快),否则用 StretchBlt
|
||||
if (srcW == dstW && srcH == dstH) {
|
||||
BitBlt(m_hFullDC, 0, 0, srcW, srcH, m_hFullMemDC, 0, 0, SRCCOPY);
|
||||
@@ -1631,6 +1641,24 @@ void CScreenSpyDlg::OnPaint()
|
||||
BitBlt(m_hFullDC, 0, 0, srcW, srcH, m_hFullMemDC, m_ulHScrollPos, m_ulVScrollPos, SRCCOPY);
|
||||
}
|
||||
|
||||
// 绘制框选矩形
|
||||
if (m_bSelectingZoom) {
|
||||
CRect rcSelect;
|
||||
rcSelect.left = min(m_ptZoomStart.x, m_ptZoomCurrent.x);
|
||||
rcSelect.top = min(m_ptZoomStart.y, m_ptZoomCurrent.y);
|
||||
rcSelect.right = max(m_ptZoomStart.x, m_ptZoomCurrent.x);
|
||||
rcSelect.bottom = max(m_ptZoomStart.y, m_ptZoomCurrent.y);
|
||||
|
||||
// 使用虚线边框绘制选择框
|
||||
HPEN hPen = CreatePen(PS_DASH, 1, RGB(255, 0, 0));
|
||||
HPEN hOldPen = (HPEN)SelectObject(m_hFullDC, hPen);
|
||||
HBRUSH hOldBrush = (HBRUSH)SelectObject(m_hFullDC, GetStockObject(NULL_BRUSH));
|
||||
Rectangle(m_hFullDC, rcSelect.left, rcSelect.top, rcSelect.right, rcSelect.bottom);
|
||||
SelectObject(m_hFullDC, hOldBrush);
|
||||
SelectObject(m_hFullDC, hOldPen);
|
||||
DeleteObject(hPen);
|
||||
}
|
||||
|
||||
if ((m_bIsCtrl && m_Settings.RemoteCursor) || m_bIsTraceCursor) {
|
||||
CPoint ptLocal;
|
||||
GetCursorPos(&ptLocal);
|
||||
@@ -1813,6 +1841,10 @@ void CScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam)
|
||||
switch (nID) {
|
||||
case IDM_CONTROL: {
|
||||
m_bIsCtrl = !m_bIsCtrl;
|
||||
// 进入控制模式时重置放大状态
|
||||
if (m_bIsCtrl && m_bZoomedIn) {
|
||||
ResetZoom();
|
||||
}
|
||||
SysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED);
|
||||
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO));
|
||||
// 控制模式:禁用本地 IME;查看模式:启用本地 IME
|
||||
@@ -2565,6 +2597,11 @@ void CScreenSpyDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
|
||||
|
||||
void CScreenSpyDlg::EnterFullScreen()
|
||||
{
|
||||
// 进入全屏时重置放大状态
|
||||
if (m_bZoomedIn) {
|
||||
ResetZoom();
|
||||
}
|
||||
|
||||
if (1) {
|
||||
// 1. 获取对话框当前所在的显示器
|
||||
HMONITOR hMonitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
|
||||
@@ -2652,6 +2689,11 @@ void CScreenSpyDlg::EnterFullScreen()
|
||||
// 全屏退出成功则返回true
|
||||
bool CScreenSpyDlg::LeaveFullScreen()
|
||||
{
|
||||
// 退出全屏时重置放大状态
|
||||
if (m_bZoomedIn) {
|
||||
ResetZoom();
|
||||
}
|
||||
|
||||
if (1) {
|
||||
KillTimer(1);
|
||||
if (m_pToolbar) {
|
||||
@@ -2692,14 +2734,165 @@ bool CScreenSpyDlg::LeaveFullScreen()
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========== 局部放大功能辅助函数 ==========
|
||||
|
||||
// 重置放大状态
|
||||
void CScreenSpyDlg::ResetZoom()
|
||||
{
|
||||
m_bZoomedIn = false;
|
||||
m_bSelectingZoom = false;
|
||||
m_bZoomDragging = false;
|
||||
m_rcZoomSrc.SetRectEmpty();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// 屏幕坐标转原图坐标(考虑放大状态)
|
||||
CPoint CScreenSpyDlg::ScreenToImage(CPoint pt)
|
||||
{
|
||||
if (!m_BitmapInfor_Full) return pt;
|
||||
|
||||
int dstW = m_CRect.Width();
|
||||
int dstH = m_CRect.Height();
|
||||
if (dstW <= 0 || dstH <= 0) return pt; // 防止除零
|
||||
|
||||
if (m_bZoomedIn && !m_rcZoomSrc.IsRectEmpty()) {
|
||||
// 放大状态:从显示区域映射到放大区域
|
||||
double scaleX = (double)m_rcZoomSrc.Width() / dstW;
|
||||
double scaleY = (double)m_rcZoomSrc.Height() / dstH;
|
||||
return CPoint(
|
||||
(int)(m_rcZoomSrc.left + pt.x * scaleX),
|
||||
(int)(m_rcZoomSrc.top + pt.y * scaleY)
|
||||
);
|
||||
} else if (m_bAdaptiveSize) {
|
||||
// 自适应模式:按比例缩放
|
||||
return CPoint((int)(pt.x * m_wZoom), (int)(pt.y * m_hZoom));
|
||||
} else {
|
||||
// 滚动模式:加上滚动偏移
|
||||
return CPoint(pt.x + m_ulHScrollPos, pt.y + m_ulVScrollPos);
|
||||
}
|
||||
}
|
||||
|
||||
// 原图坐标转屏幕坐标(考虑放大状态)
|
||||
CPoint CScreenSpyDlg::ImageToScreen(CPoint pt)
|
||||
{
|
||||
if (!m_BitmapInfor_Full) return pt;
|
||||
|
||||
int zoomW = m_rcZoomSrc.Width();
|
||||
int zoomH = m_rcZoomSrc.Height();
|
||||
|
||||
if (m_bZoomedIn && zoomW > 0 && zoomH > 0) {
|
||||
// 放大状态:从放大区域映射到显示区域
|
||||
int dstW = m_CRect.Width();
|
||||
int dstH = m_CRect.Height();
|
||||
double scaleX = (double)dstW / zoomW;
|
||||
double scaleY = (double)dstH / zoomH;
|
||||
return CPoint(
|
||||
(int)((pt.x - m_rcZoomSrc.left) * scaleX),
|
||||
(int)((pt.y - m_rcZoomSrc.top) * scaleY)
|
||||
);
|
||||
} else if (m_bAdaptiveSize) {
|
||||
if (m_wZoom > 0 && m_hZoom > 0) {
|
||||
return CPoint((int)(pt.x / m_wZoom), (int)(pt.y / m_hZoom));
|
||||
}
|
||||
return pt;
|
||||
} else {
|
||||
return CPoint(pt.x - m_ulHScrollPos, pt.y - m_ulVScrollPos);
|
||||
}
|
||||
}
|
||||
|
||||
void CScreenSpyDlg::OnLButtonDown(UINT nFlags, CPoint point)
|
||||
{
|
||||
// 非控制模式下的放大功能
|
||||
if (!m_bIsCtrl && !m_bIsFirst && m_BitmapInfor_Full) {
|
||||
if (m_bZoomedIn) {
|
||||
// 放大状态:开始拖拽平移
|
||||
m_bZoomDragging = true;
|
||||
m_ptZoomDragStart = point; // 保存起点用于点击检测
|
||||
m_ptZoomDragLast = point; // 用于增量拖拽计算
|
||||
SetCapture();
|
||||
return;
|
||||
} else {
|
||||
// 正常状态:开始框选放大区域
|
||||
m_bSelectingZoom = true;
|
||||
m_ptZoomStart = point;
|
||||
m_ptZoomCurrent = point;
|
||||
SetCapture();
|
||||
return;
|
||||
}
|
||||
}
|
||||
__super::OnLButtonDown(nFlags, point);
|
||||
}
|
||||
|
||||
|
||||
void CScreenSpyDlg::OnLButtonUp(UINT nFlags, CPoint point)
|
||||
{
|
||||
// 处理放大功能的鼠标释放
|
||||
if (!m_bIsCtrl && !m_bIsFirst && m_BitmapInfor_Full) {
|
||||
if (m_bSelectingZoom) {
|
||||
// 完成框选
|
||||
ReleaseCapture();
|
||||
m_bSelectingZoom = false;
|
||||
|
||||
// 计算选择区域(确保left<right, top<bottom)
|
||||
CRect rcSelect;
|
||||
rcSelect.left = min(m_ptZoomStart.x, point.x);
|
||||
rcSelect.top = min(m_ptZoomStart.y, point.y);
|
||||
rcSelect.right = max(m_ptZoomStart.x, point.x);
|
||||
rcSelect.bottom = max(m_ptZoomStart.y, point.y);
|
||||
|
||||
// 框选区域太小时视为点击,如果已放大则还原
|
||||
if (rcSelect.Width() < 20 || rcSelect.Height() < 20) {
|
||||
if (m_bZoomedIn) {
|
||||
ResetZoom();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 将屏幕坐标转换为原图坐标
|
||||
int srcW = m_BitmapInfor_Full->bmiHeader.biWidth;
|
||||
int srcH = m_BitmapInfor_Full->bmiHeader.biHeight;
|
||||
int dstW = m_CRect.Width();
|
||||
int dstH = m_CRect.Height();
|
||||
|
||||
if (m_bAdaptiveSize) {
|
||||
m_rcZoomSrc.left = (int)(rcSelect.left * m_wZoom);
|
||||
m_rcZoomSrc.top = (int)(rcSelect.top * m_hZoom);
|
||||
m_rcZoomSrc.right = (int)(rcSelect.right * m_wZoom);
|
||||
m_rcZoomSrc.bottom = (int)(rcSelect.bottom * m_hZoom);
|
||||
} else {
|
||||
m_rcZoomSrc.left = rcSelect.left + m_ulHScrollPos;
|
||||
m_rcZoomSrc.top = rcSelect.top + m_ulVScrollPos;
|
||||
m_rcZoomSrc.right = rcSelect.right + m_ulHScrollPos;
|
||||
m_rcZoomSrc.bottom = rcSelect.bottom + m_ulVScrollPos;
|
||||
}
|
||||
|
||||
// 限制在原图范围内
|
||||
m_rcZoomSrc.left = max(0L, min(m_rcZoomSrc.left, (LONG)srcW));
|
||||
m_rcZoomSrc.top = max(0L, min(m_rcZoomSrc.top, (LONG)srcH));
|
||||
m_rcZoomSrc.right = max(0L, min(m_rcZoomSrc.right, (LONG)srcW));
|
||||
m_rcZoomSrc.bottom = max(0L, min(m_rcZoomSrc.bottom, (LONG)srcH));
|
||||
|
||||
// 进入放大状态
|
||||
m_bZoomedIn = true;
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_bZoomDragging) {
|
||||
// 完成拖拽
|
||||
ReleaseCapture();
|
||||
m_bZoomDragging = false;
|
||||
|
||||
// 检查是否为点击(几乎没有移动)
|
||||
int dx = abs(point.x - m_ptZoomDragStart.x);
|
||||
int dy = abs(point.y - m_ptZoomDragStart.y);
|
||||
if (dx < 5 && dy < 5) {
|
||||
// 点击还原
|
||||
ResetZoom();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
__super::OnLButtonUp(nFlags, point);
|
||||
}
|
||||
|
||||
@@ -2725,6 +2918,66 @@ BOOL CScreenSpyDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
|
||||
|
||||
void CScreenSpyDlg::OnMouseMove(UINT nFlags, CPoint point)
|
||||
{
|
||||
// 处理放大功能的鼠标移动
|
||||
if (!m_bIsCtrl && !m_bIsFirst && m_BitmapInfor_Full) {
|
||||
if (m_bSelectingZoom) {
|
||||
// 框选中:更新当前点并重绘选择框
|
||||
m_ptZoomCurrent = point;
|
||||
Invalidate(FALSE); // FALSE表示不擦除背景,减少闪烁
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_bZoomDragging) {
|
||||
// 拖拽平移:计算偏移量并移动放大区域
|
||||
int dx = point.x - m_ptZoomDragLast.x;
|
||||
int dy = point.y - m_ptZoomDragLast.y;
|
||||
m_ptZoomDragLast = point; // 更新上一点(保持m_ptZoomDragStart不变用于点击检测)
|
||||
|
||||
// 计算缩放比例(添加除零保护)
|
||||
int srcW = m_BitmapInfor_Full->bmiHeader.biWidth;
|
||||
int srcH = m_BitmapInfor_Full->bmiHeader.biHeight;
|
||||
int dstW = m_CRect.Width();
|
||||
int dstH = m_CRect.Height();
|
||||
int zoomW = m_rcZoomSrc.Width();
|
||||
int zoomH = m_rcZoomSrc.Height();
|
||||
|
||||
if (dstW <= 0 || dstH <= 0 || zoomW <= 0 || zoomH <= 0) {
|
||||
return; // 防止除零
|
||||
}
|
||||
|
||||
double scaleX = (double)zoomW / dstW;
|
||||
double scaleY = (double)zoomH / dstH;
|
||||
|
||||
// 将屏幕偏移转换为原图偏移(方向相反)
|
||||
int imgDx = (int)(-dx * scaleX);
|
||||
int imgDy = (int)(-dy * scaleY);
|
||||
|
||||
// 移动放大区域
|
||||
m_rcZoomSrc.OffsetRect(imgDx, imgDy);
|
||||
|
||||
// 限制在原图范围内
|
||||
if (m_rcZoomSrc.left < 0) {
|
||||
m_rcZoomSrc.right -= m_rcZoomSrc.left;
|
||||
m_rcZoomSrc.left = 0;
|
||||
}
|
||||
if (m_rcZoomSrc.top < 0) {
|
||||
m_rcZoomSrc.bottom -= m_rcZoomSrc.top;
|
||||
m_rcZoomSrc.top = 0;
|
||||
}
|
||||
if (m_rcZoomSrc.right > srcW) {
|
||||
m_rcZoomSrc.left -= (m_rcZoomSrc.right - srcW);
|
||||
m_rcZoomSrc.right = srcW;
|
||||
}
|
||||
if (m_rcZoomSrc.bottom > srcH) {
|
||||
m_rcZoomSrc.top -= (m_rcZoomSrc.bottom - srcH);
|
||||
m_rcZoomSrc.bottom = srcH;
|
||||
}
|
||||
|
||||
Invalidate(FALSE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Settings.RemoteCursor) {
|
||||
if (m_pToolbar != NULL && ::IsWindow(m_pToolbar->m_hWnd) && m_pToolbar->IsWindowVisible()) {
|
||||
CRect rcToolbar;
|
||||
@@ -2807,11 +3060,26 @@ void CScreenSpyDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
|
||||
void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl)
|
||||
{
|
||||
m_bIsCtrl = ctrl;
|
||||
// 进入控制模式时重置放大状态
|
||||
if (m_bIsCtrl && m_bZoomedIn) {
|
||||
ResetZoom();
|
||||
}
|
||||
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO));
|
||||
// 控制模式:禁用本地 IME;查看模式:启用本地 IME
|
||||
ImmAssociateContext(m_hWnd, m_bIsCtrl ? NULL : m_hOldIMC);
|
||||
}
|
||||
|
||||
void CScreenSpyDlg::OnCaptureChanged(CWnd* pWnd)
|
||||
{
|
||||
// 捕获丢失时重置框选/拖拽状态
|
||||
if (m_bSelectingZoom || m_bZoomDragging) {
|
||||
m_bSelectingZoom = false;
|
||||
m_bZoomDragging = false;
|
||||
Invalidate();
|
||||
}
|
||||
__super::OnCaptureChanged(pWnd);
|
||||
}
|
||||
|
||||
void CScreenSpyDlg::OnDropFiles(HDROP hDropInfo)
|
||||
{
|
||||
if (m_bIsCtrl && m_bConnected) {
|
||||
|
||||
@@ -223,6 +223,20 @@ public:
|
||||
double m_wZoom=1, m_hZoom=1;
|
||||
bool m_bMouseTracking = false;
|
||||
|
||||
// ========== 局部放大功能 ==========
|
||||
bool m_bZoomedIn = false; // 是否处于放大状态
|
||||
CRect m_rcZoomSrc; // 放大区域(原图坐标)
|
||||
bool m_bSelectingZoom = false; // 是否正在框选
|
||||
CPoint m_ptZoomStart; // 框选起点(屏幕坐标)
|
||||
CPoint m_ptZoomCurrent; // 框选当前点(屏幕坐标)
|
||||
bool m_bZoomDragging = false; // 是否正在拖拽平移
|
||||
CPoint m_ptZoomDragStart; // 拖拽起点(用于点击检测)
|
||||
CPoint m_ptZoomDragLast; // 拖拽上一点(用于增量计算)
|
||||
|
||||
void ResetZoom(); // 重置放大状态
|
||||
CPoint ScreenToImage(CPoint pt); // 屏幕坐标转原图坐标
|
||||
CPoint ImageToScreen(CPoint pt); // 原图坐标转屏幕坐标
|
||||
|
||||
CString m_aviFile;
|
||||
CBmpToAvi m_aviStream;
|
||||
|
||||
@@ -302,6 +316,7 @@ public:
|
||||
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
|
||||
afx_msg void OnMouseLeave();
|
||||
afx_msg void OnKillFocus(CWnd* pNewWnd);
|
||||
afx_msg void OnCaptureChanged(CWnd* pWnd);
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);
|
||||
afx_msg LRESULT OnDisconnect(WPARAM wParam, LPARAM lParam);
|
||||
|
||||
@@ -1846,13 +1846,15 @@ inline std::string GetWebPageHTML() {
|
||||
|
||||
// Remote cursor mapping (Windows cursor index -> CSS cursor)
|
||||
// Index matches CursorInfo.h: IDC_APPSTARTING(0) to IDC_WAIT(15), 254=custom, 255=unsupported
|
||||
// Custom I-beam cursor with white fill and black stroke for visibility on any background
|
||||
const ibeamCursor = "url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"24\" viewBox=\"0 0 16 24\"><path fill=\"none\" stroke=\"white\" stroke-width=\"3\" d=\"M4 3h8M8 3v18M4 21h8\"/><path fill=\"none\" stroke=\"black\" stroke-width=\"1\" d=\"M4 3h8M8 3v18M4 21h8\"/></svg>') 8 12, text";
|
||||
const cursorMap = [
|
||||
'progress', // 0: IDC_APPSTARTING
|
||||
'default', // 1: IDC_ARROW
|
||||
'crosshair', // 2: IDC_CROSS
|
||||
'pointer', // 3: IDC_HAND
|
||||
'help', // 4: IDC_HELP
|
||||
'text', // 5: IDC_IBEAM
|
||||
ibeamCursor, // 5: IDC_IBEAM - custom cursor with outline
|
||||
'default', // 6: IDC_ICON (no direct CSS equivalent)
|
||||
'not-allowed', // 7: IDC_NO
|
||||
'default', // 8: IDC_SIZE (deprecated, use default)
|
||||
|
||||
@@ -1819,3 +1819,7 @@ IOCP
|
||||
插件列表为空,无法创建触发器=Plugin list is empty, cannot create trigger
|
||||
请先选择至少一个插件=Please select at least one plugin
|
||||
|
||||
没有本地历史记录=No local history
|
||||
历史目录不存在: %s=History folder not exist: %s
|
||||
无法识别远程主机=Unknown remote machine
|
||||
没有远程历史记录=No remote history
|
||||
|
||||
@@ -1810,3 +1810,7 @@ IOCP
|
||||
<< 移除=<< 移除
|
||||
插件列表为空,无法创建触发器=外掛列表為空,無法建立觸發器
|
||||
请先选择至少一个插件=請先選擇至少一個外掛
|
||||
没有本地历史记录=没有本地历史记录
|
||||
历史目录不存在: %s=历史目录不存在: %s
|
||||
无法识别远程主机=无法识别远程主机
|
||||
没有远程历史记录=没有远程历史记录
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
@@ -506,6 +506,8 @@
|
||||
#define IDT_REMOTE_DOWNLOADS 2235
|
||||
#define IDT_REMOTE_HOME 2236
|
||||
#define IDT_REMOTE_SEARCH 2237
|
||||
#define IDT_LOCAL_HISTORY 2238
|
||||
#define IDT_REMOTE_HISTORY 2239
|
||||
#define IDC_BUTTON_SAVE_LICENSE 2240
|
||||
#define IDC_LICENSE_LIST 2241
|
||||
#define IDC_COMBO_VERSION 2245
|
||||
@@ -966,6 +968,11 @@
|
||||
#define IDC_STATIC_TRIGGER_TYPE 2544
|
||||
#define IDC_STATIC_TRIGGER_ACTION 2545
|
||||
|
||||
// 内嵌语言资源 (RCDATA)
|
||||
// 注意:避免与 IDB_BITMAP_TRIGGER(372) 和 IDB_BITMAP_WEBDESKTOP(373) 冲突
|
||||
#define IDR_LANG_EN_US 380
|
||||
#define IDR_LANG_ZH_TW 381
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
|
||||
Reference in New Issue
Block a user