14 Commits
Author SHA1 Message Date
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
yuanyuanxianganddeepseek-v4-pro 9eed43bc8d Feature: Add MCP device management-control tools (kill/window/service/power)
Add P7 management-control tools behind a single McpManage flag (plus
McpReadonly=0): kill_process, operate_window, control_service and
power_action. The write tools run a ready->confirm lifecycle on their
sub-connection, using a new followUp/followUpSent field to tell the two
arrivals of the same TOKEN_*LIST apart; the write command and the follow-up
refresh command go out as two separate Send2Client calls, with a new
WaitSendIdle() (context.h / Server.h) draining the outbound buffer in between
so the same buffer is never WSASend'd concurrently.

McpSettingsDlg gains a "management operations" checkbox (IDC_MCP_MANAGE), the
main toolbar gains an MCP entry button, and en/de/zh-TW strings are synced.
power_action is fire-and-forget over the main connection (no confirmation).

Verified end-to-end against a real Windows 10/VMware client: kill_process
returns remaining:[] and operate_window/control_service return ok:true with
their confirm packet arriving after an HTTP split. Verification surfaced a
prerequisite parser bug, fixed separately in a72e6eb, where HeaderParser
latched m_bShouldUnmask FALSE on HTTP continuation chunks and dropped the
confirm packet.

Co-Authored-By: deepseek-v4-pro
2026-10-01 16:56:30 +02:00
yuanyuanxianganddeepseek-v4-pro a72e6eb391 Fix: keep m_bShouldUnmask latched TRUE once a connection is HTTP
HeaderParser::Parse reset m_bShouldUnmask to FALSE on any non-HTTP
maskType, including the continuation chunk of an HTTP connection whose
read offset is already past the "POST " header. When a large packet
(e.g. a >1460-byte process list) was split by MSS, the second chunk
latched the flag to FALSE, so the next new HTTP-masked packet was no
longer unmasked: CheckHead then read the HTTP header as the flag,
returned FLAG_UNKNOWN/PARSER_FAILED, and the packet was dropped. This
explains "first packet succeeds, subsequent packets vanish", and why
the MFC process dialog went blank after killing a process (the confirm
packet never reached NotifyProc).

Only clear the flag when the connection was not already determined to
be HTTP (initial -1, or already confirmed non-HTTP).

Co-Authored-By: deepseek-v4-pro
2026-10-01 16:18:26 +02:00
yuanyuanxianganddeepseek-v4-pro e9d60e6e23 Feature: Add Shine/HELL protocol selection to share/transfer host
Add a dedicated ShareHostDlg with a "Use Shine protocol" checkbox (default
checked) so the operator can choose Shine (0) or HELL (1) when sharing or
transferring a host. The chosen protocol is appended to the wire payload as a
trailing colon field: "IP:PORT:0/1" for share and "IP:PORT:days:0/1" for
transfer.

The client parses the trailing field from COMMAND_SHARE and
COMMAND_ASSIGN_MASTER, applies it to new connections, and persists it so
auto-share/auto-transfer on restart keeps the same protocol. Old clients
ignore the extra field, and old/missing payloads fall back to -1 (baked-in
HELL), preserving compatibility with existing servers and share_list entries.

Co-Authored-By: deepseek-v4-pro
2026-09-11 16:28:37 +02:00
yuanyuanxianganddeepseek-v4-pro 42df128f62 Feature: Support subpath deployment for Go web UI
Add optional YAMA_WEB_BASE_PATH to mount the whole web UI (index, manifest,
static assets, /health, /api/devices, /ws) under a URL prefix such as
/remoter behind a reverse proxy. index.html now uses a __BASE_PATH__
placeholder substituted at serve time, the PWA manifest start_url is rooted
at the base path, and a mount wrapper redirects the bare prefix to prefix/
and strips it before routing. Include nginx, systemd, and env-file deployment
examples for the subpath setup.

The shared placeholder is also handled by the MFC server, which serves from
the domain root and strips __BASE_PATH__ (to "") so its web remote desktop
URLs stay rooted.

Co-Authored-By: deepseek-v4-pro
2026-09-11 12:48:21 +02:00
yuanyuanxianganddeepseek-v4-pro 32cc7af371 Feature: Add configurable screen-preview quality levels menu
Add a "屏幕预览质量等级 (Screen Preview Quality Levels)" item to the
"高级 (Advanced)" menu that opens a dialog to edit the six preset
PreviewProfile levels (target encode width and JPEG quality) used by the
double-click single-preview and loop-snapshot floating windows. The dialog
edits a working copy in a full grid with per-field validation and a
restore-defaults button; on OK it persists the values to the [preview] INI
section.

ChooseScreenPreviewParams now reads from GetPreviewProfileCfg instead of the
hardcoded GetScreenPreviewProfile table, so preview requests sent after a
change use the configured parameters. LoadPreviewProfilesFromCfg loads the
saved levels at startup, falling back to the built-in defaults. Added
en_US/de_DE/zh_TW translations for the new menu and dialog strings.

Co-Authored-By: deepseek-v4-pro
2026-09-10 14:42:28 +02:00
yuanyuanxianganddeepseek-v4-pro 033ff077ef Feature: Add configurable remote-desktop quality levels menu
Add an "高级 (Advanced)" menu after "其他 (Other)" whose
"远程桌面质量等级 (Remote Desktop Quality Levels)" item opens a dialog to
edit the six preset QualityProfile levels (max FPS, max width, codec
algorithm, bitrate). The dialog edits a working copy in a full grid with
per-field validation and a restore-defaults button; on OK it persists the
values to the [quality] INI section.

The server stays authoritative: LoadQualityProfilesFromCfg loads the saved
levels at startup (falling back to GetQualityProfile defaults), and
ScreenSpyDlg now sends CMD_QUALITY_PROFILES from GetQualityProfileCfg so
new remote-desktop sessions transmit with the configured parameters. Added
en_US/de_DE/zh_TW translations for the menu and dialog strings.

Co-Authored-By: deepseek-v4-pro
2026-09-09 17:13:17 +02:00
yuanyuanxianganddeepseek-v4-pro 0416dadbba Feature: Add language-switch toolbar button and bilingual dialog
Add a 15th toolbar button "Lang/语言" after the Help button so Chinese
readers can switch language without knowing English; it opens the existing
language-select dialog. The dialog title is now bilingual ("切换语言 /
Switch Language") and its label/buttons use Chinese source strings
("语言:", "确定", "取消"), which still localize to en/de via
TranslateDialog. The toolbar bitmap is extended with a globe icon.

Co-Authored-By: deepseek-v4-pro
2026-09-08 16:34:45 +02:00
yuanyuanxianganddeepseek-v4-pro 91238da50d Feature: Add German (de_DE) language support
Add German as the fourth selectable UI language. The new de_DE.ini mirrors
en_US.ini with byte-identical Chinese keys (2028 lines, zero missing
translations, zero key mismatches), so it passes CheckEncoding and appears in
the language list automatically. German values are pure-ASCII transliterations
(ae/oe/ue/ss) because the GBK/CP936 architecture cannot encode umlauts.
LangManager.h now maps de_DE to the display name "Deutsch". Also translated the
seven Chinese value==key gaps that en_US itself left untranslated.

Co-Authored-By: deepseek-v4-pro
2026-09-06 10:33:49 +02:00
yuanyuanxianganddeepseek-v4-pro 221617fe02 Fix: Translate remaining server UI strings missing _TR/_L
Wrap the last untranslated Chinese UI strings so English mode stops showing
raw Chinese. Format strings passed to sprintf/wsprintf and the status-bar
SendMessageTimeout calls now go through _TRF, and the CString/PostMessage
literals plus a few member-initializer and Compare values go through
_TR/_L. Covers the wallet validation message, the SOCKS proxy connection
log lines, the file-manager status bar, and the machine-info progress
messages.

Add the 24 corresponding entries to lang/en_US.ini and lang/zh_TW.ini
(GBK), keeping the escaped-format-string key style. The Yes/No/Desktop/
Recent keys already existed, so the BuildDlg and CFileManagerDlg compare
fixes reuse them.

Co-Authored-By: deepseek-v4-pro
2026-09-06 09:39:59 +02:00
yuanyuanxianganddeepseek-v4-pro dd845823b6 Feature: Add get/set clipboard buttons to remote desktop toolbar
Add two toolbar buttons — get clipboard (letter C) and set clipboard (letter P) —
after the screenshot button and before the minimize button, so clipboard sync stays
reachable in fullscreen mode where the system menu cannot be opened.

The buttons reuse the existing IDM_GET_CLIPBOARD / IDM_SET_CLIPBOARD system-menu
handlers by posting WM_SYSCOMMAND to the parent. The toolbar button count grows
from 16 to 18, and the slide/animation dimensions are widened from 640 to 720.
Letter C/P icons and Simplified-Chinese source strings ("获取剪贴板", "设置剪贴板")
are added, along with English and Traditional-Chinese translations.

Co-Authored-By: deepseek-v4-pro
2026-09-03 13:29:39 +02:00
56 changed files with 5235 additions and 127 deletions

No files matched your search

+9 -2
View File
@@ -66,13 +66,15 @@ BOOL IsClientAppRunning(void* thisApp)
return S_CLIENT_NORMAL == This->g_bExit;
}
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run, BOOL shared)
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run, BOOL shared, int headerEnc)
{
auto v = StringToVector(remoteAddr, ':', 2);
if (v[0].empty() || v[1].empty())
return nullptr;
auto a = new ClientApp(g_MyApp.g_Connection, run, shared);
a->g_Connection->SetServer(v[0].c_str(), atoi(v[1].c_str()));
if (headerEnc == PROTOCOL_SHINE || headerEnc == PROTOCOL_HELL)
a->g_Connection->iHeaderEnc = headerEnc;
return a;
}
@@ -597,6 +599,9 @@ DWORD WINAPI StartClient(LPVOID lParam)
auto saved_ip = cfg.GetStr("settings", "master");
auto saved_port = cfg.GetInt("settings", "port");
settings.SetServer(saved_ip.c_str(), saved_port);
auto saved_proto = cfg.GetStr("settings", "master_proto");
if (saved_proto == "0" || saved_proto == "1")
settings.iHeaderEnc = atoi(saved_proto.c_str());
Mprintf("[StartClient] Client is assigned to %s:%d- %ds left.\n", saved_ip.c_str(), saved_port,
int(valid_to-now));
}
@@ -617,7 +622,9 @@ DWORD WINAPI StartClient(LPVOID lParam)
auto a = cfg.GetStr("settings", "share_list");
auto shareList = a.empty() ? std::vector<std::string>{} : StringToVector(a, '|');
for (int i = 0; i < shareList.size(); ++i) {
auto a = NewClientStartArg(shareList[i].c_str(), IsSharedRunning, TRUE);
auto v = StringToVector(shareList[i], ':', 3);
int proto = (v.size() >= 3 && (v[2] == "0" || v[2] == "1")) ? atoi(v[2].c_str()) : -1;
auto a = NewClientStartArg(shareList[i].c_str(), IsSharedRunning, TRUE, proto);
if (nullptr != a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
Mprintf("[StartClient] Client is shared to %s.\n", shareList[i].c_str());
}
+1 -1
View File
@@ -130,7 +130,7 @@ public:
}
};
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run = IsClientAppRunning, BOOL shared=FALSE);
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run = IsClientAppRunning, BOOL shared=FALSE, int headerEnc = -1);
// 启动核心线程,参数为:ClientApp
DWORD WINAPI StartClient(LPVOID lParam);
+6 -2
View File
@@ -36,9 +36,13 @@ CFileManager::CFileManager(CClientSocket *pClient, int h, void* user):CManager(p
// 初始化V2文件传输模块
CKernelManager* main = (CKernelManager*)pClient->GetMain();
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,
main ? main->m_LoginSignature : pClient->m_LoginSignature, 64, 50, Logf);
main ? main->m_LoginSignature : pClient->m_LoginSignature, chunkKb, durMs, Logf);
}
// 发送驱动器列表, 开始进行文件管理,建立新线程
SendDriveList();
+29 -2
View File
@@ -1019,6 +1019,15 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
case COMMAND_SHARE:
case COMMAND_ASSIGN_MASTER:
if (ulLength > 2) {
// 解析末尾协议段(Shine=0 / HELL=1,旧命令无此段则 -1 沿用烘焙值)
int shareProto = -1;
{
auto pv = StringToVector((char*)szBuffer + 2, ':', 5);
if (szBuffer[1] == SHARE_TYPE_YAMA && pv.size() >= 3 && (pv[2] == "0" || pv[2] == "1"))
shareProto = atoi(pv[2].c_str());
else if (szBuffer[1] == SHARE_TYPE_YAMA_FOREVER && pv.size() >= 4 && (pv[3] == "0" || pv[3] == "1"))
shareProto = atoi(pv[3].c_str());
}
switch (szBuffer[1]) {
case SHARE_TYPE_YAMA_FOREVER: {
auto v = StringToVector((char*)szBuffer + 2, ':', 3);
@@ -1029,6 +1038,8 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
if (now <= valid_to) break; // Avoid assign again
m_cfg->SetStr("settings", "master", v[0]);
m_cfg->SetStr("settings", "port", v[1]);
if (shareProto == PROTOCOL_SHINE || shareProto == PROTOCOL_HELL)
m_cfg->SetStr("settings", "master_proto", std::to_string(shareProto));
float days = atof(v[2].c_str());
if (days > 0) {
auto valid_to = time(0) + days*86400;
@@ -1047,6 +1058,8 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
if (v[0].empty() || v[1].empty())
break;
auto share = v[0] + ":" + v[1];
if (shareProto == PROTOCOL_SHINE || shareProto == PROTOCOL_HELL)
share += ":" + std::to_string(shareProto);
auto list = m_cfg->GetStr("settings", "share_list");
auto shareList = list.empty() ? std::vector<std::string>{} : StringToVector(list, '|');
if (VectorContains(shareList, share)) break;
@@ -1054,7 +1067,7 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
m_cfg->SetStr("settings", "share_list", VectorJoin(shareList, '|'));
Mprintf("Share client to new master: %s\n", share.c_str());
}
auto a = NewClientStartArg((char*)szBuffer + 2, IsSharedRunning, TRUE);
auto a = NewClientStartArg((char*)szBuffer + 2, IsSharedRunning, TRUE, shareProto);
if (nullptr!=a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
break;
}
@@ -1264,6 +1277,18 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
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 : {
if (!ENABLE_FILE_MNG) {
return ResponseDisable(m_ClientObject, "FILE", szBuffer + 1, ulLength - 1);
@@ -1383,7 +1408,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
// static 标志保证进程内只初始化一次,不随主连接重连反复 Init/Uninit。
static bool s_v2RecvInited = false;
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;
}
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
+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
CKernelManager* main = (CKernelManager*)ClientObject->GetMain();
m_Signature = main ? main->m_LoginSignature : ClientObject->m_LoginSignature;
if (!m_Signature.empty()) InitFileUpload({}, main ? main->m_LoginMsg : ClientObject->m_LoginMsg,
main ? main->m_LoginSignature : ClientObject->m_LoginSignature, 64, 50, Logf);
if (!m_Signature.empty()) {
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
m_isGDI = TRUE;
m_virtual = FALSE;
@@ -870,7 +875,10 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
if (m_Signature.empty()) {
m_Signature = std::string(resp.signature, resp.signature + 64);
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());
}
break;
+1
View File
@@ -357,6 +357,7 @@ enum {
COMMAND_TEXT_REPLACE = 249,
TOKEN_CLIP_TEXT = 250,
TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器
CMD_FILE_SPEED = 252, // 文件传输速度下发: [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]
};
#pragma pack(push, 1)
+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
+21 -1
View File
@@ -4,6 +4,8 @@
#include <iphlpapi.h>
#include <ws2tcpip.h>
#include <WinInet.h>
#include <map>
#include <mutex>
#include "logger.h"
#include "jsoncpp/json.h"
@@ -200,6 +202,18 @@ public:
{
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;
if (isLocalIP(ip)) {
ip = getPublicIP();
@@ -249,7 +263,13 @@ public:
InternetCloseHandle(hInternet);
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;
}
+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`,见项目记忆)。
+219
View File
@@ -0,0 +1,219 @@
# MCP P7 设计:设备管理控制(kill / window / service / power)
> 延续 `Mcp_Design.md` → `Mcp_Phase2_Design.md`(P2)→ P3/P4/P5/P6 的阶段演进。
> 本文定义 P7:把 YAMA 服务端高频「控制类」操作 MCP 化,补上从「观测」到「管控」的短板,
> 服务「自然语言即可操作服务端、对受管设备全方位管控」的最终目标。
>
> 安全门结论(已确认):新增**单一 `McpManage` 开关**统一门控本阶段全部控制类工具,
> 并要求 `McpReadonly=0`;不再为每个工具单设开关。
## 1. 目标与范围
P7 交付 4 个控制类工具,覆盖服务端日常最常用的写操作:
| 工具 | 动作 | 命令 | 连接 | 确认方式 |
|---|---|---|---|---|
| `kill_process` | 结束进程(多 pid) | `COMMAND_KILLPROCESS` | 系统子连接 | 补发 `COMMAND_PSLIST` 收新列表 |
| `operate_window` | 关闭/显示/隐藏/最小化/最大化/还原 | `CMD_WINDOW_CLOSE` / `CMD_WINDOW_TEST` | 窗口子连接 | close 自动回 `TOKEN_WSLIST`;test 补发 `COMMAND_WSLIST` |
| `control_service` | 启动/停止/设为自动/设为手动 | `COMMAND_SERVICECONFIG` | 服务子连接 | 客户端每项 Sleep(500) 后自动 `SendServicesList` |
| `power_action` | 注销/关机/重启 | `TOKEN_MACHINE_MANAGE` | 主连接 | 发完即走(无响应) |
仅 Windows 客户端支持(`clientType` 为 LNX/MAC/**APK** 提前返回 `-32005`;注意 `list_services`
只排除了 LNX/MAC、漏掉 APK,P7 需把 APK 一并排除)。
## 2. 复用现状
P7 **不改客户端**。客户端已全部实现下列处理(MFC 界面在跑),MCP 侧只需补齐
「发命令 → 接管子连接 → 发写命令 → 收确认 → 关闭」这条链:
- 系统子连接:`COMMAND_SYSTEM`(进程)/ `COMMAND_WSLIST`(窗口)→ 客户端开 `CSystemManager`
子连接(`client/KernelManager.cpp` 1169/1182,`client/SystemManager.cpp`)。
- `COMMAND_KILLPROCESS`:`CSystemManager::KillProcess`(`TerminateProcess`,无回包)。
- `CMD_WINDOW_CLOSE`:`PostMessage(hWnd, WM_CLOSE)` 后自动 `SendWindowsList`。
- `CMD_WINDOW_TEST`:`ShowWindow(hwnd, dHow)`(不自动回包)。
- `COMMAND_SERVICECONFIG`:`CServicesManager::ServicesConfig`(`client/ServicesManager.cpp` 213),
bCmd=1 启动 / 2 停止 / 3 自动 / 4 手动,每项后 `Sleep(500)` + `SendServicesList`。
- `TOKEN_MACHINE_MANAGE`:`KernelManager.cpp` 872,`ExitWindowsEx`(注销/关机/重启)。
复用现有 MCP 基建(`server/2015Remote/McpServer.{h,cpp}`):
- `ParseHostIdArg` / `FindMainContext`:入参解析 + 主连接定位。
- `BeginPending` / `WaitPending` / `ClearPending` / `TakeMainResponse`:单飞行挂起 + 响应回收。
- `OnRegeditReady`:已示范「接管子连接 → 发后续命令」的范式(P3 list_registry 三阶段),P7 直接泛化。
## 3. 子连接生命周期(P7 核心新增)
写工具与只读工具的关键差异:只读工具「子连接回一包即完成」,写工具需要
「**接管子连接 → 发写命令 → 收确认 → 关闭**」。在现有 `PendingRequest` 上增加
`followUp`(接管后要发的写命令 payload,二进制)+ `followUpSent`(是否已发写命令)两个字段:
```
followUpSent == false → 等子连接就绪(首包 TOKEN_*LIST 到达 = 就绪)
followUpSent == true → 已发写命令,等确认包
```
> 关键事实(已核实):三个工具各自的「就绪 token」与「确认 token」**是同一个 token**——
> 客户端子连接构造即回一包(就绪),写操作后再回一包(确认)。因此必须用 `followUpSent`
> 区分这两次到达,而不能靠 token 区分。这也是它与 `list_registry`(PATH/KEY 两个不同 token)
> 的差别。
流程(以 `kill_process` 为例):
1. 工具线程 `BeginPending(devId, "kill_process")` 登记单飞行(false = 设备忙),并预填
`followUp`(`COMMAND_KILLPROCESS` 多 pid 包)。
2. 主连接发 `COMMAND_SYSTEM`。
3. 客户端开系统子连接,首包 `TOKEN_PSLIST` 到达 → `MessageHandle` 调新增的
`OnSystemReady(devId, subCtx)`(类比 `OnRegeditReady`):
- 命中挂起且 `followUpSent==false` → 保存 `subCtx`、置 `followUpSent=true`、向子连接发
`followUp`,随后补发 `COMMAND_PSLIST` 索取新列表。
4. 客户端杀进程后回第二个 `TOKEN_PSLIST` → `TakeMainResponse` 取走(`followUpSent==true`
命中)→ 关闭子连接(`CancelIO`)→ 唤醒工具线程。
5. 工具线程解析新进程列表返回;超时/失败走 `ClearPending`(**含 `subCtx->CancelIO()`**,防孤儿子连接)。
> 注意:`followUp`(写命令)与「补发的 `COMMAND_PSLIST`/`COMMAND_WSLIST`」必须是**两次独立的
> `Send2Client` 调用**,不能拼成一个 buffer——客户端 `OnReceive` 按 `szBuffer[0]` 单命令分发,
> 拼包会把后续字节当成 pid/hwnd 解析。
接管函数(进程/窗口共用同一个 `OnSystemReady`——两者都在同一个 `CSystemManager` 子连接上,
客户端 `OnReceive` 统一处理 `COMMAND_PSLIST`/`COMMAND_KILLPROCESS`/`COMMAND_WSLIST`/
`CMD_WINDOW_CLOSE`/`CMD_WINDOW_TEST`):
| 工具 | 开子连接命令 | 就绪包(首包) | 接管后写命令 | 确认包(第二包) |
|---|---|---|---|---|
| `kill_process` | `COMMAND_SYSTEM` | `TOKEN_PSLIST` | `COMMAND_KILLPROCESS` + 补 `COMMAND_PSLIST` | `TOKEN_PSLIST` |
| `operate_window` | `COMMAND_WSLIST` | `TOKEN_WSLIST` | `CMD_WINDOW_CLOSE`(close 自动回);`CMD_WINDOW_TEST`(test 补 `COMMAND_WSLIST`) | `TOKEN_WSLIST` |
| `control_service` | `COMMAND_SERVICES` | `TOKEN_SERVERLIST` | `COMMAND_SERVICECONFIG`(客户端写后自动 `SendServicesList`) | `TOKEN_SERVERLIST` |
`MessageHandle` 现有分派点(均已核实):
- `TOKEN_PSLIST`/`TOKEN_WSLIST` 分支:`2015RemoteDlg.cpp:6540`(当前 `TakeMainResponse`+`CancelIO`)
- `TOKEN_SERVERLIST` 分支:`2015RemoteDlg.cpp:6585`(同上)
接入方式:在这两个分支的 `IsPending(devId)` 命中后,**先按 `pending.tool` 判断是否为
写工具(kill/operate/control_service)**;是则调 `OnSystemReady`/`OnServiceReady`(返回 true
= 已接管、不 `CancelIO`),否则沿用现有 `TakeMainResponse`+`CancelIO`。与 `TOKEN_REGEDIT`
分支调用 `OnRegeditReady`(`2015RemoteDlg.cpp:6561`)的写法一致,避免与人工界面抢子连接。
## 4. 工具 schema
### 4.1 `kill_process`
```jsonc
// inputSchema
{ "id": "string(必填,主机 id)",
"pids": ["integer 数组(必填,至少 1 个进程 PID)"] }
// outputSchema
{ "remaining": ["integer 数组(杀完后仍在运行的 PID)"] }
```
- payload:`[COMMAND_KILLPROCESS:1][pid:4]*N`(DWORD 小端,与 `SystemDlg::OnPlistKill` 一致)。
- 成功判定:返回 `remaining`(发 `COMMAND_PSLIST` 后进程列表里已不含目标 pid 即视为成功)。
- 描述需写明:`TerminateProcess` 强制结束、不保存进程状态,可能造成数据丢失。
### 4.2 `operate_window`
```jsonc
// inputSchema
{ "id": "string(必填)",
"hwnd": "integer(必填,窗口句柄,取 list_windows 返回的 hwnd)",
"action": "string(必填)close|show|hide|minimize|maximize|restore" }
// outputSchema
{ "ok": "boolean" }
```
- payload(与 `SystemDlg::OnWlistClose/OnWlistHide/OnWlistRecover/OnWlistMax/OnWlistMin` 一致,
**固定 20 字节零填充 `BYTE lpMsgBuf[20]`,hwnd 一律按 `DWORD`(4 字节)小端写入 offset 1**):
- `close` → `[CMD_WINDOW_CLOSE:1][hwnd:4]`(客户端 `PostMessage WM_CLOSE`)。
- 其余 → `[CMD_WINDOW_TEST:1][hwnd:4][dHow:4]`,dHow 映射(MFC 实测值):
`hide=SW_HIDE(0)` / `show=SW_NORMAL(1)` / `restore=SW_NORMAL(1)` /
`maximize=SW_MAXIMIZE(3)` / `minimize=SW_MINIMIZE(6)`。
- 缓冲器必须补零到 20 字节:客户端 `close` 分支读 `*((HWND*)(szBuffer+1))`,在 x64 是 8 字节,
而服务端只写了 4 字节 hwnd——正是 20 字节零填充补齐了高 4 字节,读出的 HWND 才是正确值;
若 MCP 发最小 5 字节包,会越界读。`test` 分支(`TestWindow`)读 `[hwnd:4][dHow:4]` 共 9 字节,
20 字节缓冲也足够。MFC 固定发 `lpMsgBuf[20]` 正是为此,MCP 照搬。
### 4.3 `control_service`
```jsonc
// inputSchema
{ "id": "string(必填)",
"service_name": "string(必填,服务名,取 list_services 返回的 service_name)",
"action": "string(必填)start|stop|set_auto|set_manual" }
// outputSchema
{ "ok": "boolean" }
```
- payload:`COMMAND_SERVICECONFIG [cmd:1][bCmd:1][serviceName\0]`,
bCmd:`start=1` / `stop=2` / `set_auto=3` / `set_manual=4`。
- 边界:客户端 `ServicesManager` 仅实现这 4 项;**无** pause / continue / delete
(它们属于 `MachineDlg` 机器管理的另一条链路,留待 P8 评估)。
### 4.4 `power_action`
```jsonc
// inputSchema
{ "id": "string(必填)",
"action": "string(必填)logout|shutdown|reboot" }
// outputSchema
{ "ok": "boolean" }
```
- payload:`TOKEN_MACHINE_MANAGE [token:1][type:1]`,type:`logout=0` / `shutdown=1` / `reboot=2`
(`enum MachineCommand` 顺序)。MFC 发的是 32 字节缓冲(`2015RemoteDlg::MachineManage`),
但客户端只校验 `ulLength>1` 并读 `szBuffer[1]`,故 2 字节即可。
- 走**主连接**、发完即走、无响应;主机将立即离线。描述需明确「不可撤销、主机下线」。
- 需客户端管理员权限(`EnableShutdownPrivilege`),无权限时客户端静默忽略(同 MFC 现状)。
- **仅 Windows 客户端**:Linux 侧文档写的是 `COMMAND_SESSION`(`linux/README.md`),但代码中
未发现对应 handler,即 Linux 实际未实现电源操作——`power_action` 对 LNX/MAC/APK 一律返回
`-32005`,不尝试发送。
## 5. 安全门(单一 `McpManage` 开关)
- 新增配置键 `settings/McpManage`(默认 `0`,关闭)。
- 生效条件:`McpManage=1` 且 `McpReadonly=0`;二者任一不满足即隐藏 4 个工具。
- 双保险:`tools/list` 隐藏 + 每个 `BuildXxx` 入口二次校验(与 `exec_command` 的
`IsReadonly()` 双保险一致)。
- `McpSettingsDlg` 增加「管理操作(进程/窗口/服务/电源)」复选框,写入 `THIS_CFG`。
- 审计:每次写操作写入服务端审计日志(`get_audit_log` 可见),记录 tool + host id + 参数摘要 + 结果。
## 6. 单飞行与并发约束
- 沿用现有约束:同 host 同一时刻只允许一个 MCP 挂起请求(`BeginPending` 保证)。
- 写工具与只读工具共用 `m_Pending`,天然互斥;与 MFC 对话框并存(各用独立子连接,互不干扰)。
- 超时沿用 `kMcpToolTimeoutMs = 20000`;`power_action` 因无响应、发完即返回成功。
## 7. 边界与风险
1. **杀进程/关机/重启不可逆**:描述与审计都需显著标注;不提供「确认对话框」,
确认责任在调用方(MCP 客户端/LLM agent)。
2. **kill 无客户端回执**:成功与否靠「新进程列表不含目标 pid」推断,`TerminateProcess`
失败(权限不足等)时进程仍在 `remaining`,由调用方判断。
3. **服务操作仅 4 项**:pause/continue/delete 缺省,如需需评估 `MachineDlg` 链路。
4. **窗口句柄时效**:hwnd 可能已失效(窗口已关闭),客户端 `PostMessage/ShowWindow`
对失效句柄静默无操作,`operate_window` 的 `ok=true` 只表示「已下发」。
5. **子连接竞态**:写工具接管子连接后若客户端断线,`OfflineProc` 调 `OnManageSubClosed`
清理挂起(置 done 并清 `subCtx`、唤醒等待线程;**不擦表**,由 `WaitPendingWrite` 自行擦除,
防迭代器失效)。清 `subCtx` 是关键——context 对象被连接池回收复用,若超时路径仍对旧指针
`CancelIO` 会误伤新连接(与 `OnTerminalClosed`/`OnScreenControlClosed` 同理)。
## 8. P8 / P9 路线图(后续)
- **P8 文件与信息面**:`delete_file/delete_directory`、`create_folder`、`rename_file`、
`open_file`;`list_network_connections`、`list_software`、`get/set_hosts`、
`list_scheduled_tasks`(+增删启停)、`get_keylog`。
- **P9 进阶与 NL 增强**:`set/delete_registry`、`screen_blank`/`lock_input`/`get_clipboard`、
`open_url`/`send_message`;**批量操作**(`ids[]`/`group` 维度);离线主机枚举
`list_all_hosts`。
## 9. 实现与验证记录(2026-10-01)
已实现并端到端验证(真实客户端 DESKTOP-A55C0OI / Windows 10 / VMware,id=3441387693643366974):
- `kill_process` → `remaining:[]`;`operate_window`(restore) → `ok:true`;
`control_service`(set_manual AJRouter) → `ok:true`;`power_action`(reboot) → `ok:true`(发完即走)。
三个写工具的确认包(第二个 `TOKEN_*LIST`)均正常到达,列表均 >1460 字节触发 HTTP 分片。
- 落地:`McpManage` 开关 + `McpReadonly=0` 双保险;`McpSettingsDlg` 增「管理操作」复选框;
主工具栏增 `MCP` 入口按钮(`ID_MCP_SETTINGS`);en/de/zh-TW 三语文案同步。
- 前置解析 bug(验证中暴露,已单独提交 `a72e6eb`):`HeaderParser::Parse` 的
`m_bShouldUnmask` 在 HTTP 分片续包上被误置 `FALSE`,导致写工具的确认包被
`PARSER_FAILED` 丢弃(「首包能到、后续包到不了」);修复为 `else if (m_bShouldUnmask != TRUE)`。
Binary file not shown.
+202 -17
View File
@@ -27,6 +27,7 @@
#include <filesystem>
#include "KeyBoardDlg.h"
#include "InputDlg.h"
#include "ShareHostDlg.h"
#include "CPasswordDlg.h"
#include "FeatureFlags.h"
#include "FeatureLimitsDlg.h"
@@ -43,6 +44,9 @@
#include <sstream>
#include "common/skCrypter.h"
#include "common/commands.h"
#include "QualityLevelsDlg.h"
#include "CPreviewLevelsDlg.h"
#include "CFileSpeedDlg.h"
#include "McpServer.h"
#include "McpSettingsDlg.h"
#include "common/md5.h"
@@ -1013,6 +1017,9 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_COMMAND(ID_TOOL_IMPORT_LICENSE, &CMy2015RemoteDlg::OnToolImportLicense)
ON_COMMAND(ID_TOOL_V2_PRIVATEKEY, &CMy2015RemoteDlg::OnToolV2PrivateKey)
ON_COMMAND(ID_MENU_NOTIFY_SETTINGS, &CMy2015RemoteDlg::OnMenuNotifySettings)
ON_COMMAND(ID_TOOL_QUALITY_LEVELS, &CMy2015RemoteDlg::OnQualityLevels)
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_GHOST, &CMy2015RemoteDlg::OnExecuteGhost)
ON_COMMAND(ID_MASTER_TRAIL, &CMy2015RemoteDlg::OnMasterTrail)
@@ -1409,6 +1416,8 @@ VOID CMy2015RemoteDlg::CreateToolBar()
m_ToolBar.SetButtonText(11, _TR("生成服务端"));
m_ToolBar.SetButtonText(12, _TR("搜索"));
m_ToolBar.SetButtonText(13, _TR("帮助"));
m_ToolBar.SetButtonText(14, _T("Lang/语言"));
m_ToolBar.SetButtonText(15, _T("MCP"));
// ============================================================
// UIBranding: 根据编译时配置或运行时标志隐藏工具栏按钮
@@ -1459,7 +1468,7 @@ VOID CMy2015RemoteDlg::CreateToolBar()
}
// 如果所有按钮都被隐藏,则隐藏整个工具栏
if (hiddenCount >= 14) {
if (hiddenCount >= 16) {
m_ToolBar.ShowWindow(SW_HIDE);
}
@@ -2203,6 +2212,8 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
McpServer().SetRemoteControlEnabled(THIS_CFG.GetInt("settings", "McpRemoteControl", 0) != 0);
// 文件传输开关:默认关;仅 download_file 门控(不要求只读关)。
McpServer().SetFileTransferEnabled(THIS_CFG.GetInt("settings", "McpFileTransfer", 0) != 0);
// P7 管理控制开关:默认关;要求只读关(McpReadonly=0)才生效(工具列表/分派双重门控)。
McpServer().SetManageEnabled(THIS_CFG.GetInt("settings", "McpManage", 0) != 0);
if (!McpServer().Start(mcpBind, mcpPort)) {
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
} else {
@@ -2211,7 +2222,9 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
}
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);
UPDATE_SPLASH(20, "正在加载IP数据库...");
@@ -2405,6 +2418,8 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
// 这里读完 INI 后再 Apply 一次纠正过来。
LoadThumbnailSettingsFromCfg();
ApplyThumbnailSettings();
LoadQualityProfilesFromCfg();
LoadPreviewProfilesFromCfg();
m_runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
@@ -4709,6 +4724,9 @@ VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
{
if (!ShouldRemoteControl())
return;
// 先下发当前文件传输速度档位,再开远程桌面:避免被控端 ScreenManager 先初始化
// 文件上传模块(进程级引用计数)把速度锁成旧值。
SendFileSpeedCommand();
int n = THIS_CFG.GetInt("settings", "DXGI");
BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE);
CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", ALGORITHM_NULL).c_str();
@@ -4718,6 +4736,9 @@ VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
VOID CMy2015RemoteDlg::OnOnlineFileManager()
{
// 先下发文件传输速度档位,再发 COMMAND_LIST_DRIVE 开文件管理:保证被控端
// CFileManager 构造时读到的 [transfer] 配置已是最新值。
SendFileSpeedCommand();
BYTE bToken = COMMAND_LIST_DRIVE;
SendSelectedCommand(&bToken, sizeof(BYTE));
}
@@ -5194,6 +5215,12 @@ BOOL CALLBACK CMy2015RemoteDlg::OfflineProc(CONTEXT_OBJECT* ContextObject)
McpServer().OnScreenControlClosed(ContextObject);
}
// MCP P7 管理写操作(kill/窗口/服务)接管的子连接断开:清挂起,避免超时路径对
// 已回收(连接池复用)的 context 误 CancelIO(与终端/屏幕子连接的清理同理)。
if (McpServer().IsRunning()) {
McpServer().OnManageSubClosed(ContextObject);
}
SOCKET nSocket = ContextObject->sClientSocket;
CDialogBase* p = (CDialogBase*)ContextObject->hDlg;
@@ -6533,8 +6560,13 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
case TOKEN_PSLIST: { // 进程管理【x】
// P2b:MCP 挂起时接管。进程/窗口列表无请求 id,按 device_id 匹配并同步拷贝
// (此时 IO 线程阻塞在 NotifyProc,缓冲尚未被覆盖,拷贝安全)。
// P7:写工具(kill_process/operate_window)的「就绪包」与「确认包」是同一 token,
// OnSystemReady 命中就绪包时接管子连接并下发写命令(返回 true、不 CancelIO),
// 命中确认包或只读工具时返回 false,走 TakeMainResponse + CancelIO 收尾。
uint64_t devId = ContextObject->GetClientID();
if (McpServer().IsPending(devId)) {
if (McpServer().OnSystemReady(devId, ContextObject))
break;
McpServer().TakeMainResponse(devId, ContextObject->GetBuffer(0), ContextObject->GetBufferLength());
ContextObject->CancelIO(); // 用完即关,避免子链接泄漏(仿 WebService 终端关闭)
break;
@@ -6577,8 +6609,12 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
case TOKEN_SERVERLIST: { // 服务管理【x】
// P3:MCP 挂起时接管。服务列表为一次性子链接回传(CServicesManager 构造即发),
// 取走即 CancelIO 关子链接(无 MFC 对话框续用该子链接)。
// P7:control_service 的就绪/确认是同一 token,OnServiceReady 命中就绪包时接管并下发
// COMMAND_SERVICECONFIG(返回 true、不 CancelIO),确认包走 TakeMainResponse 收尾。
uint64_t devId = ContextObject->GetClientID();
if (McpServer().IsPending(devId)) {
if (McpServer().OnServiceReady(devId, ContextObject))
break;
McpServer().TakeMainResponse(devId, szBuffer, len);
ContextObject->CancelIO();
break;
@@ -7896,7 +7932,7 @@ LRESULT CMy2015RemoteDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
void CMy2015RemoteDlg::OnOnlineShare()
{
CInputDialog dlg(this);
CShareHostDlg dlg(this, FALSE);
dlg.Init(_TR("分享主机"), _TR("输入<IP:PORT>地址:"));
dlg.SetHistoryKey("ShareAddress");
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty())
@@ -7905,9 +7941,10 @@ void CMy2015RemoteDlg::OnOnlineShare()
MessageBoxL("字符串长度超出[0, 250]范围限制!", "提示", MB_ICONINFORMATION);
return;
}
CharMsg* buf = new CharMsg(dlg.m_str.GetLength()+1);
memcpy(buf->data, dlg.m_str, dlg.m_str.GetLength());
buf->data[dlg.m_str.GetLength()] = 0;
CString payload = dlg.m_str + _T(":") + (dlg.m_bShine ? _T("0") : _T("1"));
CharMsg* buf = new CharMsg(payload.GetLength()+1);
memcpy(buf->data, payload, payload.GetLength());
buf->data[payload.GetLength()] = 0;
PostMessageA(WM_SHARE_CLIENT, (WPARAM)buf, NULL);
}
@@ -8691,8 +8728,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_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]);
std::string geo = res[RES_CLIENT_PUBIP].IsEmpty() ? "" : m_IPConverter->GetGeoLocation(res[RES_CLIENT_PUBIP].GetString());
if (m_HasLocDB) {
// 地理信息只在加载了本地 IP 库(QQWry/ip2region)时才查询并展示。
// 未装库时 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;
qqwryLoc.FormatL("\r\n地理信息: %s", geo.c_str());
strText += qqwryLoc;
@@ -8793,7 +8833,7 @@ void CMy2015RemoteDlg::OnListSingleClick(NMHDR* pNMHDR, LRESULT* pResult)
OpenLoopTip(ctx, anchor);
}
// 屏幕预览参数挑选:复用既有 GetTargetQualityLevel + GetScreenPreviewProfile
// 屏幕预览参数挑选:复用 GetTargetQualityLevel + GetPreviewProfileCfg(可配置的 6 档预览参数表)
// RTT 阈值表与屏幕共享共用(FRP 模式阈值更宽松);超清档(Ultra/High)+ 4K/超宽屏
// 时按 min(screenWidth/4, 1280) 自适应放大,避免高分屏被压成无信息缩略图
void CMy2015RemoteDlg::ChooseScreenPreviewParams(context* ctx, WORD& maxWidth, BYTE& jpegQuality) const
@@ -8807,7 +8847,7 @@ void CMy2015RemoteDlg::ChooseScreenPreviewParams(context* ctx, WORD& maxWidth, B
int rttForLevel = ping > 0 ? ping : 250;
int level = GetTargetQualityLevel(rttForLevel, m_settings.UsingFRPProxy ? 1 : 0);
const PreviewProfile& prof = GetScreenPreviewProfile(level);
const PreviewProfile& prof = GetPreviewProfileCfg(level);
int base = prof.maxWidth;
BYTE q = (BYTE)prof.jpegQuality;
@@ -9141,6 +9181,150 @@ void CMy2015RemoteDlg::SaveThumbnailSettingsToCfg() const
THIS_CFG.SetInt("thumbnail", "JpegQuality", m_ThumbnailCfg.JpegQuality);
}
// ===== 远程桌面质量等级配置([quality] 节) =====
// 6 档 QualityProfile 的读写与下发入口。缺省值回落到 common/commands.h 的
// GetQualityProfile(),与客户端默认表一致;用户改过的档位单独落盘到 INI。
void CMy2015RemoteDlg::LoadQualityProfilesFromCfg()
{
// 先整体回落到内置默认,再逐字段覆盖,避免 INI 缺项时读到 0
for (int i = 0; i < QUALITY_COUNT; i++) {
m_QualityProfiles[i] = GetQualityProfile(i);
}
for (int i = 0; i < QUALITY_COUNT; i++) {
std::string sec = "profile" + std::to_string(i);
QualityProfile& p = m_QualityProfiles[i];
p.maxFPS = THIS_CFG.GetInt("quality", sec + "_maxFPS", p.maxFPS);
p.maxWidth = THIS_CFG.GetInt("quality", sec + "_maxWidth", p.maxWidth);
p.algorithm = THIS_CFG.GetInt("quality", sec + "_algorithm", p.algorithm);
p.bitRate = THIS_CFG.GetInt("quality", sec + "_bitRate", p.bitRate);
// Clamp 到安全范围(防 INI 被手工编错)
if (p.maxFPS < 1) p.maxFPS = 1;
if (p.maxFPS > 60) p.maxFPS = 60;
if (p.maxWidth < 0) p.maxWidth = 0;
if (p.maxWidth > 8192) p.maxWidth = 8192;
if (p.algorithm < ALGORITHM_GRAY || p.algorithm > ALGORITHM_RGB565)
p.algorithm = ALGORITHM_H264;
if (p.bitRate < 0) p.bitRate = 0;
if (p.bitRate > 50000) p.bitRate = 50000;
}
}
void CMy2015RemoteDlg::SaveQualityProfilesToCfg() const
{
for (int i = 0; i < QUALITY_COUNT; i++) {
std::string sec = "profile" + std::to_string(i);
const QualityProfile& p = m_QualityProfiles[i];
THIS_CFG.SetInt("quality", sec + "_maxFPS", p.maxFPS);
THIS_CFG.SetInt("quality", sec + "_maxWidth", p.maxWidth);
THIS_CFG.SetInt("quality", sec + "_algorithm", p.algorithm);
THIS_CFG.SetInt("quality", sec + "_bitRate", p.bitRate);
}
}
const QualityProfile& CMy2015RemoteDlg::GetQualityProfileCfg(int level) const
{
if (level < 0 || level >= QUALITY_COUNT)
return GetQualityProfile(level); // 越界:与 common 默认表同源兜底
return m_QualityProfiles[level];
}
void CMy2015RemoteDlg::OnQualityLevels()
{
CQualityLevelsDlg dlg(m_QualityProfiles, this);
if (dlg.DoModal() == IDOK) {
SaveQualityProfilesToCfg();
MessageBoxL("质量等级已保存,下次打开远程桌面时生效。", "提示", MB_ICONINFORMATION);
}
}
// ===== 屏幕预览质量等级配置([preview] 节) =====
// 6 档 PreviewProfile 的读写入口。缺省值回落到 common/commands.h 的
// GetScreenPreviewProfile(),与内置预览参数表一致;用户改过的档位单独落盘到 INI。
void CMy2015RemoteDlg::LoadPreviewProfilesFromCfg()
{
// 先整体回落到内置默认,再逐字段覆盖,避免 INI 缺项时读到 0
for (int i = 0; i < QUALITY_COUNT; i++) {
m_PreviewProfiles[i] = GetScreenPreviewProfile(i);
}
for (int i = 0; i < QUALITY_COUNT; i++) {
std::string sec = "preview" + std::to_string(i);
PreviewProfile& p = m_PreviewProfiles[i];
p.maxWidth = THIS_CFG.GetInt("preview", sec + "_maxWidth", p.maxWidth);
p.jpegQuality = THIS_CFG.GetInt("preview", sec + "_jpegQuality", p.jpegQuality);
// Clamp 到安全范围(防 INI 被手工编错)
if (p.maxWidth < 64) p.maxWidth = 64;
if (p.maxWidth > 2560) p.maxWidth = 2560;
if (p.jpegQuality < 1) p.jpegQuality = 1;
if (p.jpegQuality > 100) p.jpegQuality = 100;
}
}
void CMy2015RemoteDlg::SavePreviewProfilesToCfg() const
{
for (int i = 0; i < QUALITY_COUNT; i++) {
std::string sec = "preview" + std::to_string(i);
const PreviewProfile& p = m_PreviewProfiles[i];
THIS_CFG.SetInt("preview", sec + "_maxWidth", p.maxWidth);
THIS_CFG.SetInt("preview", sec + "_jpegQuality", p.jpegQuality);
}
}
const PreviewProfile& CMy2015RemoteDlg::GetPreviewProfileCfg(int level) const
{
if (level < 0 || level >= QUALITY_COUNT)
return GetScreenPreviewProfile(level); // 越界:与 common 默认表同源兜底
return m_PreviewProfiles[level];
}
void CMy2015RemoteDlg::OnPreviewLevels()
{
CPreviewLevelsDlg dlg(m_PreviewProfiles, this);
if (dlg.DoModal() == IDOK) {
SavePreviewProfilesToCfg();
MessageBoxL("预览质量等级已保存,下次发送预览时生效。", "提示", MB_ICONINFORMATION);
}
}
// ===== 文件传输速度配置([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()
{
// 任何已缓存的 HBITMAP 都是按"旧 ThumbWidth"渲染的,尺寸不再可信 —— 一律清空。
@@ -9660,26 +9844,27 @@ void CMy2015RemoteDlg::OnShellcodeTestAesBin()
void CMy2015RemoteDlg::OnOnlineAssignTo()
{
CInputDialog dlg(this);
CShareHostDlg dlg(this, TRUE);
dlg.Init(_TR("转移主机(到期自动复原)"), _TR("输入<IP:PORT>地址:"));
dlg.SetHistoryKey("ShareAddress");
dlg.Init2(_TR("天数(支持浮点数):"), "30");
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty() || atof(dlg.m_sSecondInput.GetString())<=0)
dlg.InitDays(_TR("天数(支持浮点数):"), "30");
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty() || atof(dlg.m_sDays.GetString())<=0)
return;
if (dlg.m_str.GetLength() >= 250) {
MessageBoxL("字符串长度超出[0, 250]范围限制!", "提示", MB_ICONINFORMATION);
return;
}
if (dlg.m_sSecondInput.GetLength() >= 6) {
if (dlg.m_sDays.GetLength() >= 6) {
MessageBoxL("超出使用时间可输入的字符数限制!", "提示", MB_ICONINFORMATION);
return;
}
CString daysProto = dlg.m_sDays + _T(":") + (dlg.m_bShine ? _T("0") : _T("1"));
CharMsg* buf1 = new CharMsg(dlg.m_str.GetLength() + 1);
CharMsg* buf2 = new CharMsg(dlg.m_sSecondInput.GetLength() + 1);
CharMsg* buf2 = new CharMsg(daysProto.GetLength() + 1);
memcpy(buf1->data, dlg.m_str, dlg.m_str.GetLength());
memcpy(buf2->data, dlg.m_sSecondInput, dlg.m_sSecondInput.GetLength());
memcpy(buf2->data, daysProto, daysProto.GetLength());
buf1->data[dlg.m_str.GetLength()] = 0;
buf2->data[dlg.m_sSecondInput.GetLength()] = 0;
buf2->data[daysProto.GetLength()] = 0;
PostMessageA(WM_ASSIGN_CLIENT, (WPARAM)buf1, (LPARAM)buf2);
}
+25
View File
@@ -6,6 +6,7 @@
#include "TrueColorToolBar.h"
#include "IOCPServer.h"
#include <common/location.h>
#include "common/commands.h"
#include <map>
#include <unordered_map>
#include <mutex>
@@ -328,6 +329,30 @@ public:
void LoadThumbnailSettingsFromCfg(); // 从 THIS_CFG 读到 m_ThumbnailCfg
void SaveThumbnailSettingsToCfg() const; // m_ThumbnailCfg 落盘到 THIS_CFG
void TickThumbnailRefresh(); // TIMER_THUMBNAIL_REFRESH 主循环
// ===== 远程桌面质量等级配置(6 档 QualityProfile) =====
// 主控端 UI 偏好,持久化走 THIS_CFG [quality] 节。服务端权威,经 CMD_QUALITY_PROFILES
// 下发给客户端。仅 UI 线程访问;修改后需重开远程桌面会话才对新连接生效。
QualityProfile m_QualityProfiles[QUALITY_COUNT];
void LoadQualityProfilesFromCfg(); // 从 THIS_CFG 读到 m_QualityProfiles(缺省回落 GetQualityProfile)
void SaveQualityProfilesToCfg() const; // m_QualityProfiles 落盘到 THIS_CFG
const QualityProfile& GetQualityProfileCfg(int level) const; // 供 ScreenSpyDlg 下发用
void OnQualityLevels(); // 高级→远程桌面质量等级
// ===== 屏幕预览质量等级配置(6 档 PreviewProfile) =====
// 双击主机单发预览 / 循环快照浮窗所用。持久化走 THIS_CFG [preview] 节。
// 仅 UI 线程访问;修改后下次发预览请求即生效。
PreviewProfile m_PreviewProfiles[QUALITY_COUNT];
void LoadPreviewProfilesFromCfg(); // 从 THIS_CFG 读到 m_PreviewProfiles(缺省回落 GetScreenPreviewProfile)
void SavePreviewProfilesToCfg() const; // m_PreviewProfiles 落盘到 THIS_CFG
const PreviewProfile& GetPreviewProfileCfg(int level) const; // 供 ChooseScreenPreviewParams 用
void OnPreviewLevels(); // 高级→屏幕预览质量等级
// ===== 文件传输速度配置([transfer] 节,ChunkKb / DurationMs) =====
// 主控端 UI 偏好,持久化走 THIS_CFG。上传方向读 THIS_CFG(重启主控端生效);
// 下载方向经 CMD_FILE_SPEED 下发到被控端(下次打开文件管理/远程桌面会话生效)。
void OnFileTransferSpeed(); // 高级→文件传输速度
void SendFileSpeedCommand(); // 把当前档位经主连接下发给被控端
void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求
void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes);
void ClearThumbnailCacheEntry(uint64_t clientID);
@@ -266,6 +266,7 @@
<None Include="res\Cur\MutiDrag.cur" />
<None Include="res\My2015Remote.rc2" />
<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.min.js" />
<None Include="stub2\stub32.bin" />
@@ -334,6 +335,7 @@
<ClInclude Include="NotifySettingsDlg.h" />
<ClInclude Include="FeatureLimitsDlg.h" />
<ClInclude Include="FrpsForSubDlg.h" />
<ClInclude Include="ShareHostDlg.h" />
<ClInclude Include="PluginSettingsDlg.h" />
<ClInclude Include="PreviewTipWnd.h" />
<ClInclude Include="TriggerSettingsDlg.h" />
@@ -462,6 +464,7 @@
<ClCompile Include="NotifySettingsDlg.cpp" />
<ClCompile Include="FeatureLimitsDlg.cpp" />
<ClCompile Include="FrpsForSubDlg.cpp" />
<ClCompile Include="ShareHostDlg.cpp" />
<ClCompile Include="Loader.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
@@ -76,6 +76,7 @@
<ClCompile Include="NotifyManager.cpp" />
<ClCompile Include="NotifySettingsDlg.cpp" />
<ClCompile Include="FrpsForSubDlg.cpp" />
<ClCompile Include="ShareHostDlg.cpp" />
<ClCompile Include="NetworkDlg.cpp" />
<ClCompile Include="IPHistoryDlg.cpp" />
<ClCompile Include="FeatureLimitsDlg.cpp" />
@@ -181,6 +182,7 @@
<ClInclude Include="NotifyManager.h" />
<ClInclude Include="NotifySettingsDlg.h" />
<ClInclude Include="FrpsForSubDlg.h" />
<ClInclude Include="ShareHostDlg.h" />
<ClInclude Include="TerminalModuleLoader.h" />
<ClInclude Include="..\..\common\SafeString.h" />
<ClInclude Include="NetworkDlg.h" />
@@ -230,6 +232,7 @@
<None Include="res\web\xterm.min.js" />
<None Include="res\web\xterm.css" />
<None Include="res\web\fit.min.js" />
<None Include="res\web\opus-decoder.min.js" />
<None Include="..\web\index.html" />
<None Include="stub2\stub32.bin" />
<None Include="stub2\stub64.bin" />
+1 -1
View File
@@ -217,7 +217,7 @@ CBuildDlg::CBuildDlg(CWnd* pParent)
, m_strPort(_T(""))
, m_strFindden(FLAG_FINDEN)
, m_sGroupName(_T("default"))
, m_strEncryptIP(_T("是"))
, m_strEncryptIP(_L(_T("是")))
, m_sInstallDir(_T(""))
, m_sInstallName(_T(""))
, m_sDownloadUrl(_T(""))
+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()
+12
View File
@@ -551,6 +551,18 @@ void CIconButton::DrawIconLetterZ(CDC* pDC, const CRect& rc)
DrawLetterIcon(pDC, rc, _T("Z"));
}
// Letter C icon (获取剪贴板)
void CIconButton::DrawIconLetterC(CDC* pDC, const CRect& rc)
{
DrawLetterIcon(pDC, rc, _T("C"));
}
// Letter P icon (设置剪贴板)
void CIconButton::DrawIconLetterP(CDC* pDC, const CRect& rc)
{
DrawLetterIcon(pDC, rc, _T("P"));
}
// Audio On: speaker with sound waves
void CIconButton::DrawIconAudioOn(CDC* pDC, const CRect& rc)
{
+2
View File
@@ -46,6 +46,8 @@ public:
static void DrawIconLetterX(CDC* pDC, const CRect& rc); // 字母 X 图标
static void DrawIconLetterY(CDC* pDC, const CRect& rc); // 字母 Y 图标
static void DrawIconLetterZ(CDC* pDC, const CRect& rc); // 字母 Z 图标
static void DrawIconLetterC(CDC* pDC, const CRect& rc); // 字母 C 图标 (获取剪贴板)
static void DrawIconLetterP(CDC* pDC, const CRect& rc); // 字母 P 图标 (设置剪贴板)
static void DrawIconAudioOn(CDC* pDC, const CRect& rc); // 音频开启图标 (喇叭+声波)
static void DrawIconAudioOff(CDC* pDC, const CRect& rc); // 音频关闭图标 (喇叭+斜线)
+121
View File
@@ -0,0 +1,121 @@
// CPreviewLevelsDlg.h
// 屏幕预览质量等级配置对话框:编辑 6 档 PreviewProfile(编码宽度 / JPEG 质量)。
// 纯主控端 UI 偏好,持久化由 CMy2015RemoteDlg::SavePreviewProfilesToCfg() 负责。
// 仅在 2015RemoteDlg.cpp 中 include 一次(头文件内联实现,勿多处包含)。
#pragma once
#include "LangManager.h"
class CPreviewLevelsDlg : public CDialog
{
public:
enum { IDD = IDD_PREVIEW_LEVELS };
// profiles: 指向 6 元素 PreviewProfile 数组,确认后写回
CPreviewLevelsDlg(PreviewProfile* profiles, CWnd* pParent = NULL)
: CDialog(CPreviewLevelsDlg::IDD, pParent), m_profiles(profiles)
{
}
protected:
PreviewProfile* m_profiles;
PreviewProfile m_working[QUALITY_COUNT];
static const char* LevelName(int i)
{
static const char* names[QUALITY_COUNT] = {
"Ultra", "High", "Good", "Medium", "Low", "Minimal"
};
return (i >= 0 && i < QUALITY_COUNT) ? names[i] : "?";
}
void LoadToControls()
{
CString s;
for (int i = 0; i < QUALITY_COUNT; i++) {
s.Format(_T("%d"), m_working[i].maxWidth);
SetDlgItemText(IDC_PLEVEL_WIDTH + i, s);
s.Format(_T("%d"), m_working[i].jpegQuality);
SetDlgItemText(IDC_PLEVEL_QUALITY + i, s);
}
}
bool ReadInt(int id, int& out)
{
CString s;
GetDlgItemText(id, s);
s.Trim();
if (s.IsEmpty()) return false;
int v = 0;
for (int k = 0; k < s.GetLength(); k++) {
TCHAR ch = s[k];
if (ch < _T('0') || ch > _T('9')) return false;
v = v * 10 + (ch - _T('0'));
}
out = v;
return true;
}
bool ReadFromControls(CString& err)
{
for (int i = 0; i < QUALITY_COUNT; i++) {
int w, q;
if (!ReadInt(IDC_PLEVEL_WIDTH + i, w))
{ err.Format(_T("等级 %s 的最大宽度不是有效数字"), LevelName(i)); return false; }
if (!ReadInt(IDC_PLEVEL_QUALITY + i, q))
{ err.Format(_T("等级 %s 的 JPEG 质量不是有效数字"), LevelName(i)); return false; }
if (w < 64 || w > 2560)
{ err.Format(_T("等级 %s 的最大宽度需在 64~2560 之间"), LevelName(i)); return false; }
if (q < 1 || q > 100)
{ err.Format(_T("等级 %s 的 JPEG 质量需在 1~100 之间"), LevelName(i)); return false; }
m_working[i].maxWidth = w;
m_working[i].jpegQuality = q;
}
return true;
}
void ResetToDefaults()
{
for (int i = 0; i < QUALITY_COUNT; i++) {
m_working[i] = GetScreenPreviewProfile(i);
}
LoadToControls();
}
virtual BOOL OnInitDialog() override
{
CDialog::OnInitDialog();
for (int i = 0; i < QUALITY_COUNT; i++) {
m_working[i] = m_profiles[i];
}
LoadToControls();
TranslateDialog(this);
return TRUE;
}
virtual void OnOK() override
{
CString err;
if (!ReadFromControls(err)) {
MessageBox(err, _TR("提示"), MB_ICONWARNING);
return;
}
for (int i = 0; i < QUALITY_COUNT; i++) {
m_profiles[i] = m_working[i];
}
CDialog::OnOK();
}
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) override
{
if (LOWORD(wParam) == IDC_PLEVEL_RESET && HIWORD(wParam) == BN_CLICKED) {
ResetToDefaults();
return TRUE;
}
return CDialog::OnCommand(wParam, lParam);
}
};
+1 -1
View File
@@ -107,7 +107,7 @@ void CWalletDlg::OnOK()
if (a[i].IsEmpty()) continue;
if (WALLET_UNKNOWN == detectWalletType(a[i].GetString())) {
char tip[100];
sprintf(tip, "第 %d 个钱包地址不合法!", i + 1);
sprintf(tip, _TRF("第 %d 个钱包地址不合法!"), i + 1);
MessageBoxL(CString(tip), "提示", MB_ICONINFORMATION);
return;
}
+1 -1
View File
@@ -268,7 +268,7 @@ BOOL CHideScreenSpyDlg::OnInitDialog()
#ifdef _DEBUG
// ShowWindow(SW_MINIMIZE);
#endif
m_strTip = CString("请等待......");
m_strTip = _TR("请等待......");
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
+9 -8
View File
@@ -703,12 +703,13 @@ protected:
if (langCode == _T("zh_CN")) return _T("Simplified Chinese");
if (langCode == _T("zh_TW")) return _T("Traditional Chinese");
if (langCode == _T("en_US")) return _T("English");
if (langCode == _T("de_DE")) return _T("Deutsch");
return langCode;
}
LPCDLGTEMPLATE CreateDialogTemplate()
{
const WORD DLG_WIDTH = 200;
const WORD DLG_WIDTH = 260;
const WORD DLG_HEIGHT = 75;
m_templateBuffer.clear();
@@ -722,24 +723,24 @@ protected:
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
AppendWord(0); // 菜单
AppendWord(0); // 窗口类
AppendString(_T("Select Language"));
AppendString(_T("切换语言 / Switch Language"));
AlignToDword();
// 静态文本
AddControl(0x0082, 15, 15, 50, 12, (WORD)-1,
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("Language:"));
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
// ComboBox
AddControl(0x0085, 65, 13, 120, 150, 1001,
AddControl(0x0085, 65, 13, 180, 150, 1001,
CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, _T(""));
// OK 按钮
AddControl(0x0080, 45, 50, 50, 14, IDOK,
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("OK"));
AddControl(0x0080, 75, 50, 50, 14, IDOK,
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("确定"));
// Cancel 按钮
AddControl(0x0080, 105, 50, 50, 14, IDCANCEL,
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("Cancel"));
AddControl(0x0080, 135, 50, 50, 14, IDCANCEL,
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("取消"));
return (LPCDLGTEMPLATE)m_templateBuffer.data();
}
+610 -1
View File
@@ -840,6 +840,129 @@ Json::Value BuildServiceListOutputSchema() {
return schema;
}
// ========== P7 输入/输出 schema ==========
// 公共:为 props 追加必填的 id 字符串属性。
static void AddIdRequiredProp(Json::Value& props) {
Json::Value idProp(Json::objectValue);
idProp["type"] = "string";
idProp["description"] = u8"主机 id,取 list_online_hosts / search_hosts 返回的 id 字段";
props["id"] = idProp;
}
// 公共:组装 {id, ...额外必填} 的 object schema。
static Json::Value FinalizeIdSchema(Json::Value props, std::initializer_list<const char*> extraRequired) {
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
Json::Value required(Json::arrayValue);
required.append("id");
for (const char* r : extraRequired) required.append(r);
schema["required"] = required;
return schema;
}
// 公共:{ ok: boolean } 输出 schema(operate_window / control_service / power_action 共用)。
static Json::Value BuildOkOutputSchema() {
Json::Value props(Json::objectValue);
Json::Value ok(Json::objectValue);
ok["type"] = "boolean";
props["ok"] = ok;
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
return schema;
}
Json::Value BuildKillProcessInputSchema() {
Json::Value props(Json::objectValue);
AddIdRequiredProp(props);
Json::Value pidsProp(Json::objectValue);
pidsProp["type"] = "array";
Json::Value pidItems(Json::objectValue);
pidItems["type"] = "integer";
pidsProp["items"] = pidItems;
pidsProp["description"] = u8"要终止的进程 PID 列表(至少 1 个)";
props["pids"] = pidsProp;
return FinalizeIdSchema(props, { "pids" });
}
Json::Value BuildKillProcessOutputSchema() {
Json::Value props(Json::objectValue);
Json::Value remProp(Json::objectValue);
remProp["type"] = "array";
Json::Value remItems(Json::objectValue);
remItems["type"] = "integer";
remProp["items"] = remItems;
remProp["description"] = u8"杀完后仍在运行的 PID(含权限不足未能终止的)";
props["remaining"] = remProp;
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
return schema;
}
Json::Value BuildOperateWindowInputSchema() {
Json::Value props(Json::objectValue);
AddIdRequiredProp(props);
Json::Value hwndProp(Json::objectValue);
hwndProp["type"] = "integer";
hwndProp["description"] = u8"窗口句柄,取 list_windows 返回的 hwnd 字段";
props["hwnd"] = hwndProp;
Json::Value actionProp(Json::objectValue);
actionProp["type"] = "string";
actionProp["description"] = u8"操作:close(关闭) | show(显示) | hide(隐藏) | minimize(最小化) | maximize(最大化) | restore(还原)";
props["action"] = actionProp;
return FinalizeIdSchema(props, { "hwnd", "action" });
}
Json::Value BuildOperateWindowOutputSchema() {
return BuildOkOutputSchema();
}
Json::Value BuildControlServiceInputSchema() {
Json::Value props(Json::objectValue);
AddIdRequiredProp(props);
Json::Value nameProp(Json::objectValue);
nameProp["type"] = "string";
nameProp["description"] = u8"服务名,取 list_services 返回的 service_name 字段";
props["service_name"] = nameProp;
Json::Value actionProp(Json::objectValue);
actionProp["type"] = "string";
actionProp["description"] = u8"操作:start(启动) | stop(停止) | set_auto(设为自动) | set_manual(设为手动)";
props["action"] = actionProp;
return FinalizeIdSchema(props, { "service_name", "action" });
}
Json::Value BuildControlServiceOutputSchema() {
return BuildOkOutputSchema();
}
Json::Value BuildPowerActionInputSchema() {
Json::Value props(Json::objectValue);
AddIdRequiredProp(props);
Json::Value actionProp(Json::objectValue);
actionProp["type"] = "string";
actionProp["description"] = u8"操作:logout(注销) | shutdown(关机) | reboot(重启)";
props["action"] = actionProp;
return FinalizeIdSchema(props, { "action" });
}
Json::Value BuildPowerActionOutputSchema() {
return BuildOkOutputSchema();
}
// list_registry 的 inputSchema(id 必填,path 可选;path 缺省/空 = 列根键)
Json::Value BuildListRegistryInputSchema() {
Json::Value props(Json::objectValue);
@@ -1163,6 +1286,20 @@ Json::Value BuildUploadFileOutputSchema();
std::string BuildUploadFile(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
std::string BuildRemoteClipboard(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
// ===== P7 前置声明(定义见下方「tools/call 分派」前)=====
Json::Value BuildKillProcessInputSchema();
Json::Value BuildKillProcessOutputSchema();
Json::Value BuildOperateWindowInputSchema();
Json::Value BuildOperateWindowOutputSchema();
Json::Value BuildControlServiceInputSchema();
Json::Value BuildControlServiceOutputSchema();
Json::Value BuildPowerActionInputSchema();
Json::Value BuildPowerActionOutputSchema();
std::string BuildKillProcess(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
std::string BuildOperateWindow(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
std::string BuildControlService(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
std::string BuildPowerAction(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
// tools/list
std::string BuildToolsListResult(const Json::Value& id) {
Json::Value result(Json::objectValue);
@@ -1433,6 +1570,43 @@ std::string BuildToolsListResult(const Json::Value& id) {
tools.append(tool);
}
// 18) kill_process / operate_window / control_service / power_action(P7:设备管理控制,
// 仅 Windows,安全门:McpManage=1 且 McpReadonly=0)
if (CMcpServer::Instance().IsManageEnabled() && !CMcpServer::Instance().IsReadonly()) {
{
Json::Value tool(Json::objectValue);
tool["name"] = "kill_process";
tool["description"] = u8"终止指定在线 Windows 主机上的一个或多个进程(TerminateProcess 强制结束,不保存进程状态,可能造成数据丢失)。返回 remaining(杀完后仍在运行的 PID)。不可撤销,请谨慎确认 PID。";
tool["inputSchema"] = BuildKillProcessInputSchema();
tool["outputSchema"] = BuildKillProcessOutputSchema();
tools.append(tool);
}
{
Json::Value tool(Json::objectValue);
tool["name"] = "operate_window";
tool["description"] = u8"对指定在线 Windows 主机的窗口执行操作:close(关闭)/show(显示)/hide(隐藏)/minimize(最小化)/maximize(最大化)/restore(还原)。hwnd 取 list_windows 返回的 hwnd。ok 仅表示已下发(窗口句柄可能已失效)。";
tool["inputSchema"] = BuildOperateWindowInputSchema();
tool["outputSchema"] = BuildOperateWindowOutputSchema();
tools.append(tool);
}
{
Json::Value tool(Json::objectValue);
tool["name"] = "control_service";
tool["description"] = u8"控制指定在线 Windows 主机的服务:start(启动)/stop(停止)/set_auto(设为自动)/set_manual(设为手动)。service_name 取 list_services 返回的 service_name。ok 仅表示已下发(成功与否需再查 list_services 核对状态)。";
tool["inputSchema"] = BuildControlServiceInputSchema();
tool["outputSchema"] = BuildControlServiceOutputSchema();
tools.append(tool);
}
{
Json::Value tool(Json::objectValue);
tool["name"] = "power_action";
tool["description"] = u8"对指定在线 Windows 主机执行电源操作:logout(注销)/shutdown(关机)/reboot(重启)。不可撤销,主机将立即下线;需客户端管理员权限。";
tool["inputSchema"] = BuildPowerActionInputSchema();
tool["outputSchema"] = BuildPowerActionOutputSchema();
tools.append(tool);
}
}
result["tools"] = tools;
return BuildResult(id, result);
}
@@ -4175,6 +4349,332 @@ std::string BuildUploadFile(const Json::Value& id, const Json::Value& args, CMy2
return BuildError(id, -32001, msg);
}
// ========== P7:设备管理控制 ==========
// 读取 JSON number 或纯数字字符串为 uint64。
static bool ReadUInt64(const Json::Value& v, uint64_t& out) {
if (v.isNumeric()) { out = v.asUInt64(); return true; }
if (v.isString()) {
std::string s = v.asString();
if (IsDigits(s)) { out = strtoull(s.c_str(), nullptr, 10); return true; }
}
return false;
}
static bool ReadUInt64Arg(const Json::Value& args, const char* key, uint64_t& out) {
if (!args.isMember(key)) return false;
return ReadUInt64(args[key], out);
}
// 写审计(杀进程/窗口/服务/电源共用):PostMessageA 落到服务端消息/审计日志(不可关闭)。
static void AuditManageAction(CMy2015RemoteDlg* parent, uint64_t devId,
const std::string& tool, const std::string& detail) {
if (!parent) return;
std::string text = "host " + std::to_string(devId) + " " + tool + ": " + detail;
parent->PostMessageA(WM_SHOWERRORMSG,
(WPARAM)new CString(ToAnsi(text, 936).c_str()),
(LPARAM)new CString(_TR("MCP管理操作")));
}
// kill_process:主连接 COMMAND_SYSTEM → 系统子连接 TOKEN_PSLIST(就绪)→ COMMAND_KILLPROCESS
// → 补发 COMMAND_PSLIST → 第二个 TOKEN_PSLIST(确认)。payload 与 SystemDlg::OnPlistKill 一致。
std::string BuildKillProcess(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
uint64_t devId = 0;
std::string err;
if (!ParseHostIdArg(args, devId, err))
return BuildError(id, -32602, err);
context* ctx = FindMainContext(parent, devId);
if (!ctx)
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
if (clientType == "LNX" || clientType == "MAC" || clientType == "APK")
return BuildError(id, -32005, "kill_process is only supported on Windows hosts");
CMcpServer& mcp = CMcpServer::Instance();
if (mcp.IsReadonly())
return BuildError(id, -32006, "kill_process is disabled: MCP is in read-only mode (set McpReadonly=0 to enable)");
if (!mcp.IsManageEnabled())
return BuildError(id, -32006, "kill_process is disabled: management operations are off (set McpManage=1 to enable)");
const Json::Value& pids = args["pids"];
if (!pids.isArray() || pids.size() == 0)
return BuildError(id, -32602, "Missing or invalid parameter: pids (non-empty integer array)");
std::vector<uint64_t> pidList;
std::vector<BYTE> followUp;
followUp.push_back((BYTE)COMMAND_KILLPROCESS);
for (unsigned int i = 0; i < pids.size(); ++i) {
uint64_t pid = 0;
if (!ReadUInt64(pids[i], pid) || pid == 0)
return BuildError(id, -32602, "pids must be non-zero integers");
pidList.push_back(pid);
DWORD d = (DWORD)pid;
followUp.push_back((BYTE)(d & 0xFF));
followUp.push_back((BYTE)((d >> 8) & 0xFF));
followUp.push_back((BYTE)((d >> 16) & 0xFF));
followUp.push_back((BYTE)((d >> 24) & 0xFF));
}
if (!mcp.BeginPendingFollowUp(devId, "kill_process", followUp))
return BuildError(id, -32003, "Device busy: another request is pending for this host");
BYTE cmd = COMMAND_SYSTEM;
if (!ctx->Send2Client(&cmd, 1)) {
mcp.ClearPending(devId);
return BuildError(id, -32004, "Failed to send command to host");
}
std::vector<BYTE> data;
if (!mcp.WaitPendingWrite(devId, data, kMcpToolTimeoutMs))
return BuildError(id, -32001, "Timeout waiting for process list after kill");
UINT cp = (clientType == "LNX" || clientType == "MAC") ? CP_UTF8 : 936;
Json::Value procs = ParseProcessList(data, cp);
Json::Value remaining(Json::arrayValue);
std::string detail = "pids=";
for (size_t i = 0; i < pidList.size(); ++i) {
if (i) detail += ",";
detail += std::to_string(pidList[i]);
bool stillAlive = false;
for (unsigned int j = 0; j < procs.size(); ++j) {
if (procs[j]["pid"].asUInt64() == pidList[i]) { stillAlive = true; break; }
}
if (stillAlive) remaining.append((Json::UInt64)pidList[i]);
}
AuditManageAction(parent, devId, "kill_process", detail);
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
structuredContent["remaining"] = remaining;
result["structuredContent"] = structuredContent;
Json::Value content(Json::arrayValue);
Json::Value item(Json::objectValue);
item["type"] = "text";
item["text"] = remaining.empty()
? std::string(u8"目标进程已全部终止。")
: std::string(u8"仍有 ") + std::to_string((int)remaining.size()) + std::string(u8" 个进程存活(见 remaining)。");
content.append(item);
result["content"] = content;
result["isError"] = false;
return BuildResult(id, result);
}
// operate_window:主连接 COMMAND_WSLIST → 窗口子连接 TOKEN_WSLIST(就绪)→ CMD_WINDOW_CLOSE
// (close 客户端自动回 TOKEN_WSLIST)或 CMD_WINDOW_TEST(test 补发 COMMAND_WSLIST 索取)。
// 20 字节零填充 buffer 复刻 SystemDlg::OnWlistClose/OnWlistHide/Recover/Max/Min(hwnd 为 DWORD)。
std::string BuildOperateWindow(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
uint64_t devId = 0;
std::string err;
if (!ParseHostIdArg(args, devId, err))
return BuildError(id, -32602, err);
context* ctx = FindMainContext(parent, devId);
if (!ctx)
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
if (clientType == "LNX" || clientType == "MAC" || clientType == "APK")
return BuildError(id, -32005, "operate_window is only supported on Windows hosts");
CMcpServer& mcp = CMcpServer::Instance();
if (mcp.IsReadonly())
return BuildError(id, -32006, "operate_window is disabled: MCP is in read-only mode (set McpReadonly=0 to enable)");
if (!mcp.IsManageEnabled())
return BuildError(id, -32006, "operate_window is disabled: management operations are off (set McpManage=1 to enable)");
uint64_t hwnd = 0;
if (!ReadUInt64Arg(args, "hwnd", hwnd) || hwnd == 0)
return BuildError(id, -32602, "Missing or invalid parameter: hwnd (non-zero window handle)");
std::string action = Trim(GetStringArg(args, "action"));
BYTE lpMsgBuf[20] = { 0 };
DWORD h = (DWORD)hwnd;
if (action == "close") {
lpMsgBuf[0] = CMD_WINDOW_CLOSE;
memcpy(lpMsgBuf + 1, &h, sizeof(DWORD));
} else {
DWORD dHow;
if (action == "hide") dHow = SW_HIDE; // 0
else if (action == "show") dHow = SW_NORMAL; // 1
else if (action == "restore") dHow = SW_NORMAL; // 1
else if (action == "maximize") dHow = SW_MAXIMIZE; // 3
else if (action == "minimize") dHow = SW_MINIMIZE; // 6
else return BuildError(id, -32602,
"Invalid action: " + action + " (expect close|show|hide|minimize|maximize|restore)");
lpMsgBuf[0] = CMD_WINDOW_TEST;
memcpy(lpMsgBuf + 1, &h, sizeof(DWORD));
memcpy(lpMsgBuf + 1 + sizeof(DWORD), &dHow, sizeof(DWORD));
}
std::vector<BYTE> followUp(lpMsgBuf, lpMsgBuf + sizeof(lpMsgBuf));
if (!mcp.BeginPendingFollowUp(devId, "operate_window", followUp))
return BuildError(id, -32003, "Device busy: another request is pending for this host");
BYTE cmd = COMMAND_WSLIST;
if (!ctx->Send2Client(&cmd, 1)) {
mcp.ClearPending(devId);
return BuildError(id, -32004, "Failed to send command to host");
}
std::vector<BYTE> data;
if (!mcp.WaitPendingWrite(devId, data, kMcpToolTimeoutMs))
return BuildError(id, -32001, "Timeout waiting for window operation");
AuditManageAction(parent, devId, "operate_window",
"action=" + action + " hwnd=" + std::to_string(hwnd));
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
structuredContent["ok"] = true;
result["structuredContent"] = structuredContent;
Json::Value content(Json::arrayValue);
Json::Value item(Json::objectValue);
item["type"] = "text";
item["text"] = std::string(u8"已下发窗口操作:") + action;
content.append(item);
result["content"] = content;
result["isError"] = false;
return BuildResult(id, result);
}
// control_service:主连接 COMMAND_SERVICES → 服务子连接 TOKEN_SERVERLIST(就绪)→
// COMMAND_SERVICECONFIG → 客户端写后自动 SendServicesList(确认,无需补发)。
std::string BuildControlService(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
uint64_t devId = 0;
std::string err;
if (!ParseHostIdArg(args, devId, err))
return BuildError(id, -32602, err);
context* ctx = FindMainContext(parent, devId);
if (!ctx)
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
if (clientType == "LNX" || clientType == "MAC" || clientType == "APK")
return BuildError(id, -32005, "control_service is only supported on Windows hosts");
CMcpServer& mcp = CMcpServer::Instance();
if (mcp.IsReadonly())
return BuildError(id, -32006, "control_service is disabled: MCP is in read-only mode (set McpReadonly=0 to enable)");
if (!mcp.IsManageEnabled())
return BuildError(id, -32006, "control_service is disabled: management operations are off (set McpManage=1 to enable)");
std::string serviceName = Trim(GetStringArg(args, "service_name"));
if (serviceName.empty())
return BuildError(id, -32602, "Missing required parameter: service_name");
std::string action = Trim(GetStringArg(args, "action"));
BYTE bCmd;
if (action == "start") bCmd = 1;
else if (action == "stop") bCmd = 2;
else if (action == "set_auto") bCmd = 3;
else if (action == "set_manual") bCmd = 4;
else return BuildError(id, -32602,
"Invalid action: " + action + " (expect start|stop|set_auto|set_manual)");
// 服务名来自 Windows A 接口(GBK/936),下发前转 ANSI。payload 与 MachineDlg 一致:
// [COMMAND_SERVICECONFIG:1][bCmd:1][serviceName\0]。
std::string ansiName = ToAnsi(serviceName, 936);
std::vector<BYTE> followUp;
followUp.reserve(2 + ansiName.size() + 1);
followUp.push_back((BYTE)COMMAND_SERVICECONFIG);
followUp.push_back(bCmd);
followUp.insert(followUp.end(), ansiName.begin(), ansiName.end());
followUp.push_back(0);
if (!mcp.BeginPendingFollowUp(devId, "control_service", followUp))
return BuildError(id, -32003, "Device busy: another request is pending for this host");
BYTE cmd = COMMAND_SERVICES;
if (!ctx->Send2Client(&cmd, 1)) {
mcp.ClearPending(devId);
return BuildError(id, -32004, "Failed to send command to host");
}
std::vector<BYTE> data;
if (!mcp.WaitPendingWrite(devId, data, kMcpToolTimeoutMs))
return BuildError(id, -32001, "Timeout waiting for service operation");
AuditManageAction(parent, devId, "control_service",
"service=" + serviceName + " action=" + action);
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
structuredContent["ok"] = true;
result["structuredContent"] = structuredContent;
Json::Value content(Json::arrayValue);
Json::Value item(Json::objectValue);
item["type"] = "text";
item["text"] = std::string(u8"已下发服务操作:") + action + std::string(u8" ") + serviceName;
content.append(item);
result["content"] = content;
result["isError"] = false;
return BuildResult(id, result);
}
// power_action:主连接 TOKEN_MACHINE_MANAGE,发完即走(无响应);主机立即离线。
std::string BuildPowerAction(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
uint64_t devId = 0;
std::string err;
if (!ParseHostIdArg(args, devId, err))
return BuildError(id, -32602, err);
context* ctx = FindMainContext(parent, devId);
if (!ctx)
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
if (clientType == "LNX" || clientType == "MAC" || clientType == "APK")
return BuildError(id, -32005, "power_action is only supported on Windows hosts");
CMcpServer& mcp = CMcpServer::Instance();
if (mcp.IsReadonly())
return BuildError(id, -32006, "power_action is disabled: MCP is in read-only mode (set McpReadonly=0 to enable)");
if (!mcp.IsManageEnabled())
return BuildError(id, -32006, "power_action is disabled: management operations are off (set McpManage=1 to enable)");
std::string action = Trim(GetStringArg(args, "action"));
BYTE type;
if (action == "logout") type = MACHINE_LOGOUT;
else if (action == "shutdown") type = MACHINE_SHUTDOWN;
else if (action == "reboot") type = MACHINE_REBOOT;
else return BuildError(id, -32602,
"Invalid action: " + action + " (expect logout|shutdown|reboot)");
// 主连接、发完即走、无响应(客户端 KernelManager 只校验 ulLength>1 并读 szBuffer[1])。
BYTE token[2] = { (BYTE)TOKEN_MACHINE_MANAGE, type };
if (!ctx->Send2Client(token, 2))
return BuildError(id, -32004, "Failed to send command to host");
AuditManageAction(parent, devId, "power_action", "action=" + action);
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
structuredContent["ok"] = true;
result["structuredContent"] = structuredContent;
Json::Value content(Json::arrayValue);
Json::Value item(Json::objectValue);
item["type"] = "text";
item["text"] = std::string(u8"已下发电源操作:") + action;
content.append(item);
result["content"] = content;
result["isError"] = false;
return BuildResult(id, result);
}
// tools/call 分派
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
const Json::Value& id = root["id"];
@@ -4210,6 +4710,10 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
if (toolName == "remote_clipboard") return BuildRemoteClipboard(id, args, parent);
if (toolName == "download_file") return BuildDownloadFile(id, args, parent);
if (toolName == "upload_file") return BuildUploadFile(id, args, parent);
if (toolName == "kill_process") return BuildKillProcess(id, args, parent);
if (toolName == "operate_window") return BuildOperateWindow(id, args, parent);
if (toolName == "control_service") return BuildControlService(id, args, parent);
if (toolName == "power_action") return BuildPowerAction(id, args, parent);
return BuildError(id, -32602,
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));
@@ -4727,7 +5231,8 @@ bool CMcpServer::IsPending(uint64_t device_id) {
void CMcpServer::TakeMainResponse(uint64_t device_id, const BYTE* data, ULONG len) {
std::lock_guard<std::mutex> lk(m_PendingMutex);
auto it = m_Pending.find(device_id);
if (it == m_Pending.end()) return; // 已超时清理 → 迟到数据,丢弃
if (it == m_Pending.end()) { Mprintf("[MCP-P7] TakeMainResponse dev=%llu len=%d: no pending (late/dropped)\n", device_id, (int)len); return; } // 已超时清理 → 迟到数据,丢弃
Mprintf("[MCP-P7] TakeMainResponse dev=%llu len=%d done\n", device_id, (int)len);
it->second.data.assign(data, data + len);
it->second.done = true;
m_PendingCv.notify_one();
@@ -4902,6 +5407,110 @@ bool CMcpServer::WaitPendingRegistry(uint64_t device_id, std::vector<BYTE>& subk
return true;
}
// ===== P7:设备管理控制(写工具接管)=====
bool CMcpServer::BeginPendingFollowUp(uint64_t device_id, const std::string& tool,
const std::vector<BYTE>& followUp) {
if (IsFileTransferPending(device_id)) return false; // 该 host 有文件传输会话在飞(互斥,见 F2)
std::lock_guard<std::mutex> lk(m_PendingMutex);
if (m_Pending.find(device_id) != m_Pending.end()) return false; // 设备忙
PendingRequest r;
r.tool = tool;
r.followUp = followUp;
m_Pending[device_id] = std::move(r);
return true;
}
bool CMcpServer::OnSystemReady(uint64_t device_id, context* subCtx) {
std::string tool;
std::vector<BYTE> followUp;
{
std::lock_guard<std::mutex> lk(m_PendingMutex);
auto it = m_Pending.find(device_id);
if (it == m_Pending.end()) { Mprintf("[MCP-P7] OnSystemReady dev=%llu: no pending\n", device_id); return false; } // 无挂起 → 回落 MFC
tool = it->second.tool;
if (tool != "kill_process" && tool != "operate_window") { Mprintf("[MCP-P7] OnSystemReady dev=%llu tool=%s: not write tool\n", device_id, tool.c_str()); return false; }
if (it->second.followUpSent) { Mprintf("[MCP-P7] OnSystemReady dev=%llu tool=%s: CONFIRM token -> TakeMainResponse\n", device_id, tool.c_str()); return false; } // 确认包 → TakeMainResponse
followUp = it->second.followUp;
it->second.subCtx = subCtx;
it->second.followUpSent = true;
}
// 锁外下发写命令;kill/test 需补发刷新命令索取新列表(close/control_service 客户端自动回)。
BOOL b1 = subCtx->Send2Client(followUp.data(), (ULONG)followUp.size());
BYTE refresh = 0;
if (tool == "kill_process") {
refresh = (BYTE)COMMAND_PSLIST;
} else if (!followUp.empty() && followUp[0] == CMD_WINDOW_TEST) {
refresh = (BYTE)COMMAND_WSLIST;
}
if (refresh) {
// followUp 与 refresh 是两次独立 Send2Client,各自 PostQueuedCompletionStatus(IOWrite),
// worker 会对同一 OutCompressedBuffer 并发发起 WSASend,造成竞态/丢包。用 WaitSendIdle
// 确定性等首个包真正发完再发刷新包(比 MFC 的 Sleep(100) 盲等更稳)。
subCtx->WaitSendIdle(2000);
BOOL b2 = subCtx->Send2Client(&refresh, 1);
Mprintf("[MCP-P7] OnSystemReady dev=%llu tool=%s READY: followUpSend=%d refreshSend=%d refresh=%d\n",
device_id, tool.c_str(), b1 ? 1 : 0, b2 ? 1 : 0, (int)refresh);
}
return true; // 已接管,不 CancelIO
}
bool CMcpServer::OnServiceReady(uint64_t device_id, context* subCtx) {
std::vector<BYTE> followUp;
{
std::lock_guard<std::mutex> lk(m_PendingMutex);
auto it = m_Pending.find(device_id);
if (it == m_Pending.end()) return false;
if (it->second.tool != "control_service") return false;
if (it->second.followUpSent) return false; // 确认包 → 调用方 TakeMainResponse
followUp = it->second.followUp;
it->second.subCtx = subCtx;
it->second.followUpSent = true;
}
subCtx->Send2Client(followUp.data(), (ULONG)followUp.size());
return true; // 客户端写后自动回,无需补发
}
bool CMcpServer::WaitPendingWrite(uint64_t device_id, std::vector<BYTE>& out, int timeoutMs) {
context* subCtx = nullptr;
std::unique_lock<std::mutex> lk(m_PendingMutex);
auto it = m_Pending.find(device_id);
if (it == m_Pending.end()) return false;
bool signaled = m_PendingCv.wait_for(lk, std::chrono::milliseconds(timeoutMs),
[&] { return it->second.done; });
if (!signaled || it->second.data.empty()) {
subCtx = it->second.subCtx;
Mprintf("[MCP-P7] WaitPendingWrite dev=%llu TIMEOUT: signaled=%d dataEmpty=%d subCtx=%p\n",
device_id, signaled ? 1 : 0, it->second.data.empty() ? 1 : 0, (void*)subCtx);
m_Pending.erase(it); // 超时/空数据 → 清理
lk.unlock();
if (subCtx) subCtx->CancelIO(); // 锁外关子连接,防孤儿子连接
return false;
}
out = std::move(it->second.data);
Mprintf("[MCP-P7] WaitPendingWrite dev=%llu OK len=%d\n", device_id, (int)out.size());
m_Pending.erase(it);
return true;
}
void CMcpServer::OnManageSubClosed(context* subCtx) {
std::lock_guard<std::mutex> lk(m_PendingMutex);
// 只匹配「已接管、等确认包」的写工具子连接(read 工具从不存 subCtx,天然不命中)。
// 扫描而非按 device_id 查找,避免依赖断线后 GetClientID 是否仍有效。
for (auto& kv : m_Pending) {
if (kv.second.subCtx == subCtx) {
Mprintf("[MCP-P7] OnManageSubClosed subCtx=%p tool=%s: sub-conn closed before confirm\n",
(void*)subCtx, kv.second.tool.c_str());
kv.second.subCtx = nullptr; // 断线后子连接已死/将回收,超时路径不得再 CancelIO
kv.second.done = true; // 唤醒等待线程(WaitPendingWrite 见 data 空 → 返回失败)
m_PendingCv.notify_all();
break;
}
}
}
// rand_s:Windows CRT 加密安全随机源(基于系统 CSPRNG)。其声明需在 <stdlib.h> 前
// 定义 _CRT_RAND_S;为避免依赖 PCH 的包含顺序,这里手动声明其导出原型(errno_t == int)。
extern "C" int __cdecl rand_s(unsigned int* randomValue);
+33
View File
@@ -242,6 +242,34 @@ public:
// 收尾:擦会话 + 锁外 CancelIO 文件管理器/流式两条子链接(幂等)。
void ClearFileTransfer(uint64_t device_id);
// ===== P7:设备管理控制(kill_process / operate_window / control_service / power_action)=====
// 写工具复用 m_Pending 单飞行;followUp/followUpSent/subCtx 标记接管进度。
// 「就绪 token」与「确认 token」是同一 token(客户端子连接构造即回一包,写后再回一包),
// 故必须用 followUpSent 区分两次到达,不能靠 token 区分(与 list_registry 的 PATH/KEY 双 token 不同)。
void SetManageEnabled(bool enabled) { m_manageEnabled = enabled; }
bool IsManageEnabled() const { return m_manageEnabled; }
// 登记写挂起:与 BeginPending 同单飞行,另预填 followUp(写命令 payload)。
bool BeginPendingFollowUp(uint64_t device_id, const std::string& tool,
const std::vector<BYTE>& followUp);
// MessageHandle(TOKEN_PSLIST/TOKEN_WSLIST) 调用:命中且为写工具(kill_process/operate_window)
// 则接管子连接、下发 followUp(返回 true=已接管,调用方不 CancelIO);否则 false(回落 MFC
// 或按确认包走 TakeMainResponse)。
bool OnSystemReady(uint64_t device_id, context* subCtx);
// MessageHandle(TOKEN_SERVERLIST) 调用:命中且为 control_service 则接管、下发
// COMMAND_SERVICECONFIG(返回 true=已接管);否则 false。
bool OnServiceReady(uint64_t device_id, context* subCtx);
// 工具线程:等待写确认包(第二个同 token 包);超时/空数据清挂起并 CancelIO 子连接。
bool WaitPendingWrite(uint64_t device_id, std::vector<BYTE>& out, int timeoutMs);
// OfflineProc 调用:接管的子连接断开时清挂起(置 done 并清 subCtx,唤醒等待线程)。
// 不擦表(由 WaitPendingWrite 持有迭代器时自行擦除,防迭代器失效);清 subCtx 使
// 超时路径不会对已回收(连接池复用)的 context 误 CancelIO。
void OnManageSubClosed(context* subCtx);
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
void SetReadonly(bool readonly) { m_readonly = readonly; }
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
@@ -274,6 +302,10 @@ private:
bool pathDone = false; // list_registry:已收到 TOKEN_REG_PATH
bool keyDone = false; // list_registry:已收到 TOKEN_REG_KEY
bool done = false;
// ===== P7:写工具(kill_process / operate_window / control_service)接管进度 =====
std::vector<BYTE> followUp; // 接管后下发的写命令 payload(首字节 = 写命令)
bool followUpSent = false; // false=等子连接就绪(首包);true=已发写命令、等确认包
context* subCtx = nullptr; // 接管的子连接(超时/失败 CancelIO 用,防孤儿子连接)
};
std::mutex m_PendingMutex;
std::condition_variable m_PendingCv;
@@ -348,6 +380,7 @@ private:
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false)
bool m_remoteControlEnabled = false; // 远程控制开关(默认关;要求 m_readonly=false)
bool m_fileTransferEnabled = false; // 文件传输开关(默认关;download_file)
bool m_manageEnabled = false; // P7 管理控制开关(默认关;要求 m_readonly=false)
};
// 全局访问器(仿 WebService(),见 WebService.h 末尾)
+13 -2
View File
@@ -145,6 +145,9 @@ BOOL CMcpSettingsDlg::OnInitDialog()
m_btnFileTransfer.Create(_TR("启用文件传输(download_file)"),
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
r0, this, IDC_MCP_FILETRANSFER);
m_btnManage.Create(_TR("启用管理操作(进程/窗口/服务/电源)"),
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
r0, this, IDC_MCP_MANAGE);
m_lblWhitelist.Create(_TR("命令白名单"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
m_editWhitelist.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP |
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL,
@@ -168,6 +171,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
m_btnTerminal.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnRemoteControl.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnFileTransfer.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnManage.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_lblWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_editWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
@@ -184,6 +188,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
int terminal = THIS_CFG.GetInt("settings", "McpTerminal", 0);
int remoteControl = THIS_CFG.GetInt("settings", "McpRemoteControl", 0);
int fileTransfer = THIS_CFG.GetInt("settings", "McpFileTransfer", 0);
int manage = THIS_CFG.GetInt("settings", "McpManage", 0);
std::string whitelist = THIS_CFG.GetStr("settings", "McpCmdWhitelist", "");
m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED);
@@ -194,6 +199,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
m_btnTerminal.SetCheck(terminal ? BST_CHECKED : BST_UNCHECKED);
m_btnRemoteControl.SetCheck(remoteControl ? BST_CHECKED : BST_UNCHECKED);
m_btnFileTransfer.SetCheck(fileTransfer ? BST_CHECKED : BST_UNCHECKED);
m_btnManage.SetCheck(manage ? BST_CHECKED : BST_UNCHECKED);
// 白名单存储为逗号分隔,展示为每行一条。
m_editWhitelist.SetWindowText(CString(WhitelistForDisplay(whitelist).c_str()));
@@ -213,6 +219,7 @@ void CMcpSettingsDlg::OnOK()
bool terminal = (m_btnTerminal.GetCheck() == BST_CHECKED);
bool remoteControl = (m_btnRemoteControl.GetCheck() == BST_CHECKED);
bool fileTransfer = (m_btnFileTransfer.GetCheck() == BST_CHECKED);
bool manage = (m_btnManage.GetCheck() == BST_CHECKED);
// 端口校验:1-65535
int port = atoi(CT2A(sPort));
@@ -239,14 +246,15 @@ void CMcpSettingsDlg::OnOK()
THIS_CFG.SetInt("settings", "McpTerminal", terminal ? 1 : 0);
THIS_CFG.SetInt("settings", "McpRemoteControl", remoteControl ? 1 : 0);
THIS_CFG.SetInt("settings", "McpFileTransfer", fileTransfer ? 1 : 0);
THIS_CFG.SetInt("settings", "McpManage", manage ? 1 : 0);
std::string whitelist = CT2A(sWhitelist);
whitelist = NormalizeWhitelist(whitelist);
THIS_CFG.SetStr("settings", "McpCmdWhitelist", whitelist);
// 拆成两段可翻译的单行键,中间用 \r\n 连接(多行键无法在 INI 中表示)
MessageBox(_TR("MCP 设置已保存。") + _T("\r\n") +
_TR("启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。") + _T("\r\n") +
_TR("持久终端与远程控制仅在只读模式关闭时生效。"),
_TR("启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输/管理操作的改动需重启程序生效。") + _T("\r\n") +
_TR("持久终端、远程控制与管理操作仅在只读模式关闭时生效。"),
_TR("提示"), MB_ICONINFORMATION);
CDialog::OnOK();
@@ -289,6 +297,9 @@ void CMcpSettingsDlg::LayoutControls(int cx, int cy)
m_btnFileTransfer.MoveWindow(margin, y, cx - margin * 2, 22);
y += 30;
m_btnManage.MoveWindow(margin, y, cx - margin * 2, 22);
y += 30;
const int whitelistH = 90;
m_lblWhitelist.MoveWindow(margin, y, labelW, rowH);
m_editWhitelist.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, whitelistH);
+2
View File
@@ -31,6 +31,7 @@ private:
IDC_MCP_TERMINAL = 1007, // 「启用持久终端」复选框(全命令,无白名单,要求只读关)
IDC_MCP_REMOTECONTROL = 1008, // 「启用远程控制」复选框(AI 操控桌面,要求只读关)
IDC_MCP_FILETRANSFER = 1009, // 「启用文件传输」复选框(download_file,不要求只读关)
IDC_MCP_MANAGE = 1010, // 「启用管理操作」复选框(kill/窗口/服务/电源,要求只读关)
};
CButton m_btnEnable;
@@ -40,6 +41,7 @@ private:
CButton m_btnTerminal;
CButton m_btnRemoteControl;
CButton m_btnFileTransfer;
CButton m_btnManage;
CStatic m_lblWhitelist;
CEdit m_editWhitelist;
CButton m_btnOK, m_btnCancel;
+38 -3
View File
@@ -56,7 +56,7 @@ BOOL NotifySettingsDlg::OnInitDialog()
SetDlgItemText(IDC_STATIC_TRIGGER, _TR(_T("触发条件:")));
SetDlgItemText(IDC_STATIC_MATCH_COLUMN, _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(IDOK, _TR("确定"));
SetDlgItemText(IDCANCEL, _TR("取消"));
@@ -154,7 +154,24 @@ void NotifySettingsDlg::LoadSettings()
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()
@@ -189,8 +206,26 @@ void NotifySettingsDlg::SaveSettings()
sel = m_comboNotifyColumn.GetCurSel();
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);
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
GetNotifyManager().SetConfig(m_config);
+156
View File
@@ -0,0 +1,156 @@
// QualityLevelsDlg.h
// 远程桌面质量等级配置对话框:编辑 6 档 QualityProfile(帧率/宽度/算法/码率)。
// 纯主控端 UI 偏好,持久化由 CMy2015RemoteDlg::SaveQualityProfilesToCfg() 负责。
// 仅在 2015RemoteDlg.cpp 中 include 一次(头文件内联实现,勿多处包含)。
#pragma once
#include "LangManager.h"
class CQualityLevelsDlg : public CDialog
{
public:
enum { IDD = IDD_QUALITY_LEVELS };
// profiles: 指向 6 元素 QualityProfile 数组,确认后写回
CQualityLevelsDlg(QualityProfile* profiles, CWnd* pParent = NULL)
: CDialog(CQualityLevelsDlg::IDD, pParent), m_profiles(profiles)
{
}
protected:
QualityProfile* m_profiles;
QualityProfile m_working[QUALITY_COUNT];
static const char* AlgorithmName(int algo)
{
switch (algo) {
case ALGORITHM_GRAY: return "GRAY";
case ALGORITHM_DIFF: return "DIFF";
case ALGORITHM_H264: return "H264";
case ALGORITHM_RGB565: return "RGB565";
default: return "?";
}
}
static const char* LevelName(int i)
{
static const char* names[QUALITY_COUNT] = {
"Ultra", "High", "Good", "Medium", "Low", "Minimal"
};
return (i >= 0 && i < QUALITY_COUNT) ? names[i] : "?";
}
void FillAlgoCombo(int i)
{
CComboBox* cb = (CComboBox*)GetDlgItem(IDC_QLEVEL_ALGO + i);
if (!cb) return;
for (int a = 0; a < 4; a++) {
cb->AddString(CString(AlgorithmName(a)));
}
cb->SetCurSel(m_working[i].algorithm);
}
void LoadToControls()
{
CString s;
for (int i = 0; i < QUALITY_COUNT; i++) {
s.Format(_T("%d"), m_working[i].maxFPS);
SetDlgItemText(IDC_QLEVEL_FPS + i, s);
s.Format(_T("%d"), m_working[i].maxWidth);
SetDlgItemText(IDC_QLEVEL_WIDTH + i, s);
s.Format(_T("%d"), m_working[i].bitRate);
SetDlgItemText(IDC_QLEVEL_BITRATE + i, s);
FillAlgoCombo(i);
}
}
bool ReadInt(int id, int& out)
{
CString s;
GetDlgItemText(id, s);
s.Trim();
if (s.IsEmpty()) return false;
int v = 0;
for (int k = 0; k < s.GetLength(); k++) {
TCHAR ch = s[k];
if (ch < _T('0') || ch > _T('9')) return false;
v = v * 10 + (ch - _T('0'));
}
out = v;
return true;
}
bool ReadFromControls(CString& err)
{
for (int i = 0; i < QUALITY_COUNT; i++) {
int fps, w, br;
if (!ReadInt(IDC_QLEVEL_FPS + i, fps))
{ err.Format(_T("等级 %s 的帧率不是有效数字"), LevelName(i)); return false; }
if (!ReadInt(IDC_QLEVEL_WIDTH + i, w))
{ err.Format(_T("等级 %s 的最大宽度不是有效数字"), LevelName(i)); return false; }
if (!ReadInt(IDC_QLEVEL_BITRATE + i, br))
{ err.Format(_T("等级 %s 的码率不是有效数字"), LevelName(i)); return false; }
if (fps < 1 || fps > 60)
{ err.Format(_T("等级 %s 的帧率需在 1~60 之间"), LevelName(i)); return false; }
if (w < 0 || w > 8192)
{ err.Format(_T("等级 %s 的最大宽度需在 0~8192 之间(0=原始分辨率)"), LevelName(i)); return false; }
if (br < 0 || br > 50000)
{ err.Format(_T("等级 %s 的码率需在 0~50000 之间"), LevelName(i)); return false; }
CComboBox* cb = (CComboBox*)GetDlgItem(IDC_QLEVEL_ALGO + i);
int algo = cb ? cb->GetCurSel() : -1;
if (algo < 0 || algo > 3)
{ err.Format(_T("等级 %s 的编码算法未选择"), LevelName(i)); return false; }
m_working[i].maxFPS = fps;
m_working[i].maxWidth = w;
m_working[i].bitRate = br;
m_working[i].algorithm = algo;
}
return true;
}
void ResetToDefaults()
{
for (int i = 0; i < QUALITY_COUNT; i++) {
m_working[i] = GetQualityProfile(i);
}
LoadToControls();
}
virtual BOOL OnInitDialog() override
{
CDialog::OnInitDialog();
for (int i = 0; i < QUALITY_COUNT; i++) {
m_working[i] = m_profiles[i];
}
LoadToControls();
TranslateDialog(this);
return TRUE;
}
virtual void OnOK() override
{
CString err;
if (!ReadFromControls(err)) {
MessageBox(err, _TR("提示"), MB_ICONWARNING);
return;
}
for (int i = 0; i < QUALITY_COUNT; i++) {
m_profiles[i] = m_working[i];
}
CDialog::OnOK();
}
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) override
{
if (LOWORD(wParam) == IDC_QLEVEL_RESET && HIWORD(wParam) == BN_CLICKED) {
ResetToDefaults();
return TRUE;
}
return CDialog::OnCommand(wParam, lParam);
}
};
+6 -2
View File
@@ -889,13 +889,17 @@ BOOL CScreenSpyDlg::OnInitDialog()
// 启动传输速率更新定时器 (1秒)
SetTimer(4, 1000, NULL);
// 下发质量配置表(让客户端可以持久化保存)
// 下发质量配置表(让客户端可以持久化保存)。
// 优先使用主控端在「高级 → 远程桌面质量等级」里自定义的档位;无主控端时回落内置默认表。
{
BYTE profileCmd[1 + sizeof(QualityProfile) * QUALITY_COUNT];
profileCmd[0] = CMD_QUALITY_PROFILES;
for (int i = 0; i < QUALITY_COUNT; i++) {
const QualityProfile& prof = (m_pParent != nullptr)
? m_pParent->GetQualityProfileCfg(i)
: GetQualityProfile(i);
memcpy(profileCmd + 1 + i * sizeof(QualityProfile),
&GetQualityProfile(i), sizeof(QualityProfile));
&prof, sizeof(QualityProfile));
}
m_ContextObject->Send2Client(profileCmd, sizeof(profileCmd));
}
+20 -1
View File
@@ -112,7 +112,12 @@ protected:
m_bShouldUnmask = TRUE;
std::string clientIP = getXForwardedFor(str);
if (!clientIP.empty()) peer = clientIP;
} else {
} else if (m_bShouldUnmask != TRUE) {
// 仅当连接尚未被判定为 HTTP(初始 -1,或已确认非 HTTP)时才置为 FALSE。
// 否则会误伤 HTTP 连接的分片续包:续包到达时读位置已越过掩码头,
// TryUnMask 找不到 "POST "(maskType==None),若在这里把 m_bShouldUnmask
// 从 TRUE 改回 FALSE,下一个新包(仍是 HTTP 掩码)就不再解掩码,从而被 Parse
// 判为 PARSER_FAILED。这正是「kill 后进程列表确认包到不了 NotifyProc」的根因。
m_bShouldUnmask = FALSE;
}
if (nullptr == m_Masker) {
@@ -568,6 +573,20 @@ public:
}
return FALSE;
}
// 等待 OutCompressedBuffer 排空。OnClientPostSending 在最后一个 WSASend 完成后
// ClearBuffer 使有效长度归零,此处轮询该信号,避免背靠背 Send2Client 对同一缓冲
// 并发 WSASend(MCP OnSystemReady 发写命令后补发刷新命令时调用)。
bool WaitSendIdle(int timeoutMs) override
{
ULONGLONG deadline = (timeoutMs > 0) ? (GetTickCount64() + (ULONGLONG)timeoutMs) : 0;
while (OutCompressedBuffer.GetBufferLength() != 0) {
if (timeoutMs > 0 && GetTickCount64() >= deadline) {
return false;
}
Sleep(1);
}
return true;
}
VOID SetClientInfo(const CString(&s)[ONLINELIST_MAX], const std::vector<std::string>& a = {})
{
for (int i = 0; i < ONLINELIST_MAX; i++) {
+125
View File
@@ -0,0 +1,125 @@
// ShareHostDlg.cpp: 分享主机 / 转移主机 专用对话框实现
//
#include "stdafx.h"
#include "ShareHostDlg.h"
#include "2015Remote.h"
#include <algorithm>
CShareHostDlg::CShareHostDlg(CWnd* pParent /*=nullptr*/, BOOL bNeedDays /*=FALSE*/)
: CDialogLangEx(IDD_DIALOG_SHARE_HOST, pParent)
, m_bNeedDays(bNeedDays)
, m_bShine(TRUE)
{
}
CShareHostDlg::~CShareHostDlg()
{
}
void CShareHostDlg::Init(LPCTSTR caption, LPCTSTR prompt)
{
m_sCaption = caption;
m_sPrompt = prompt;
}
void CShareHostDlg::InitDays(LPCTSTR name, LPCTSTR defaultValue)
{
m_sDaysName = name;
m_sDays = defaultValue;
}
void CShareHostDlg::SetHistoryKey(LPCTSTR historyKey)
{
m_sHistoryKey = historyKey;
}
void CShareHostDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogLangEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_SHARE_ADDRESS, m_ComboInput);
DDX_Control(pDX, IDC_STATIC_SHARE_DAYS, m_StaticDays);
DDX_Control(pDX, IDC_EDIT_SHARE_DAYS, m_EditDays);
DDX_Control(pDX, IDC_CHECK_USE_SHINE, m_CheckShine);
}
BEGIN_MESSAGE_MAP(CShareHostDlg, CDialogLangEx)
END_MESSAGE_MAP()
BOOL CShareHostDlg::OnInitDialog()
{
CDialogLangEx::OnInitDialog();
SetWindowText(m_sCaption);
SetDlgItemText(IDC_STATIC_SHARE_ADDRESS, m_sPrompt);
SetDlgItemText(IDC_CHECK_USE_SHINE, _TR("使用 Shine 协议(兼容 Go 服务端)"));
SetDlgItemText(IDOK, _TR("确定"));
SetDlgItemText(IDCANCEL, _TR("取消"));
// 天数(仅转移主机显示)
m_StaticDays.SetWindowTextA(m_sDaysName);
m_StaticDays.ShowWindow(m_bNeedDays ? SW_SHOW : SW_HIDE);
m_EditDays.SetWindowTextA(m_sDays);
m_EditDays.ShowWindow(m_bNeedDays ? SW_SHOW : SW_HIDE);
// 默认勾选 Shine
m_CheckShine.SetCheck(m_bShine ? BST_CHECKED : BST_UNCHECKED);
LoadHistory();
m_ComboInput.SetWindowText(m_str);
return TRUE;
}
void CShareHostDlg::OnOK()
{
m_ComboInput.GetWindowText(m_str);
m_bShine = (m_CheckShine.GetCheck() == BST_CHECKED);
if (m_bNeedDays)
m_EditDays.GetWindowText(m_sDays);
SaveHistory();
CDialogLangEx::OnOK();
}
void CShareHostDlg::LoadHistory()
{
if (m_sHistoryKey.IsEmpty()) return;
std::string history = THIS_CFG.GetStr("history", m_sHistoryKey.GetString());
if (history.empty()) return;
auto items = StringToVector(history, '|');
for (size_t i = 0; i < items.size() && i < 16; i++) {
if (!items[i].empty()) {
m_ComboInput.AddString(items[i].c_str());
}
}
}
void CShareHostDlg::SaveHistory()
{
if (m_sHistoryKey.IsEmpty() || m_str.IsEmpty()) return;
std::string newValue = m_str.GetString();
if (newValue.find('|') != std::string::npos) return;
std::string history = THIS_CFG.GetStr("history", m_sHistoryKey.GetString());
auto items = StringToVector(history, '|');
items.erase(std::remove(items.begin(), items.end(), newValue), items.end());
items.erase(std::remove(items.begin(), items.end(), std::string("")), items.end());
items.insert(items.begin(), newValue);
if (items.size() > 16) {
items.resize(16);
}
std::string result;
for (size_t i = 0; i < items.size(); i++) {
if (i > 0) result += '|';
result += items[i];
}
THIS_CFG.SetStr("history", m_sHistoryKey.GetString(), result);
}
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "resource.h"
#include "LangManager.h"
// 分享主机 / 转移主机 专用对话框(带 Shine/HELL 协议勾选)
class CShareHostDlg : public CDialogLangEx
{
public:
CShareHostDlg(CWnd* pParent = nullptr, BOOL bNeedDays = FALSE);
virtual ~CShareHostDlg();
enum { IDD = IDD_DIALOG_SHARE_HOST };
void Init(LPCTSTR caption, LPCTSTR prompt);
void InitDays(LPCTSTR name, LPCTSTR defaultValue); // 仅转移主机显示
void SetHistoryKey(LPCTSTR historyKey);
// 结果
CString m_str; // 地址 IP:PORT
CString m_sDays; // 天数(转移主机)
BOOL m_bShine; // TRUE=Shine(0), FALSE=HELL(1)
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
virtual void OnOK();
DECLARE_MESSAGE_MAP()
CString m_sCaption;
CString m_sPrompt;
CString m_sDaysName;
CString m_sHistoryKey;
BOOL m_bNeedDays;
CComboBox m_ComboInput;
CStatic m_StaticDays;
CEdit m_EditDays;
CButton m_CheckShine;
void LoadHistory();
void SaveHistory();
};
+34 -11
View File
@@ -39,6 +39,8 @@ BEGIN_MESSAGE_MAP(CToolbarDlg, CDialogEx)
ON_BN_CLICKED(IDC_BTN_Y, &CToolbarDlg::OnBnClickedY)
ON_BN_CLICKED(IDC_BTN_Z, &CToolbarDlg::OnBnClickedZ)
ON_BN_CLICKED(IDC_BTN_SCREENSHOT, &CToolbarDlg::OnBnClickedScreenshot)
ON_BN_CLICKED(IDC_BTN_GET_CLIPBOARD, &CToolbarDlg::OnBnClickedGetClipboard)
ON_BN_CLICKED(IDC_BTN_SET_CLIPBOARD, &CToolbarDlg::OnBnClickedSetClipboard)
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
@@ -88,7 +90,7 @@ void CToolbarDlg::CheckMousePosition()
int monWidth = monRight - monLeft;
int monHeight = monBottom - monTop;
int btnSize = 32, btnSpacing = 8, btnCount = 16;
int btnSize = 32, btnSpacing = 8, btnCount = 18;
if (m_nPosition <= 1) {
// 水平模式 (上/下)
@@ -153,9 +155,9 @@ void CToolbarDlg::SlideIn()
int monWidth = monRight - monLeft;
int monHeight = monBottom - monTop;
int hw = 640; // 水平工具栏宽度 (与垂直高度一致)
int hw = 720; // 水平工具栏宽度 (与垂直高度一致)
int vw = 40;
int vh = 640;
int vh = 720;
// 从边缘展开(改变窗口大小),避免多显示器时跑到相邻屏幕
switch (m_nPosition) {
@@ -212,9 +214,9 @@ void CToolbarDlg::SlideOut()
int monWidth = monRight - monLeft;
int monHeight = monBottom - monTop;
int hw = 640;
int hw = 720;
int vw = 40;
int vh = 640;
int vh = 720;
CWnd* btns[] = {
&m_btnExit, &m_btnControl,
@@ -224,9 +226,10 @@ void CToolbarDlg::SlideOut()
&m_btnQuality, &m_btnRestoreConsole,
&m_btnX, &m_btnY, &m_btnZ,
&m_btnScreenshot,
&m_btnGetClipboard, &m_btnSetClipboard,
&m_btnMinimize, &m_btnClose,
};
const int N = 16;
const int N = 18;
CRect btnRects[N];
for (int i = 0; i < N; i++) {
btns[i]->GetWindowRect(&btnRects[i]);
@@ -331,6 +334,8 @@ BOOL CToolbarDlg::OnInitDialog()
m_btnY.SubclassDlgItem(IDC_BTN_Y, this);
m_btnZ.SubclassDlgItem(IDC_BTN_Z, this);
m_btnScreenshot.SubclassDlgItem(IDC_BTN_SCREENSHOT, this);
m_btnGetClipboard.SubclassDlgItem(IDC_BTN_GET_CLIPBOARD, this);
m_btnSetClipboard.SubclassDlgItem(IDC_BTN_SET_CLIPBOARD, this);
m_btnMinimize.SubclassDlgItem(IDC_BTN_MINIMIZE, this);
m_btnClose.SubclassDlgItem(IDC_BTN_CLOSE, this);
@@ -343,6 +348,8 @@ BOOL CToolbarDlg::OnInitDialog()
// Y按钮图标在 UpdateButtonIcons 中根据音频状态动态设置
m_btnZ.SetIconDrawFunc(CIconButton::DrawIconLetterZ); // 预留按钮 Z
m_btnScreenshot.SetIconDrawFunc(CIconButton::DrawIconScreenshot);
m_btnGetClipboard.SetIconDrawFunc(CIconButton::DrawIconLetterC);
m_btnSetClipboard.SetIconDrawFunc(CIconButton::DrawIconLetterP);
m_btnMinimize.SetIconDrawFunc(CIconButton::DrawIconMinimize);
m_btnClose.SetIconDrawFunc(CIconButton::DrawIconClose);
m_btnClose.SetIsCloseButton(true);
@@ -367,6 +374,8 @@ BOOL CToolbarDlg::OnInitDialog()
m_tooltip.AddTool(&m_btnY, pParent->m_Settings.AudioEnabled ? _TR("关闭系统音频") : _TR("打开系统音频"));
m_tooltip.AddTool(&m_btnZ, _TR("选择ROI"));
m_tooltip.AddTool(&m_btnScreenshot, _TR("截图"));
m_tooltip.AddTool(&m_btnGetClipboard, _TR("获取剪贴板"));
m_tooltip.AddTool(&m_btnSetClipboard, _TR("设置剪贴板"));
m_tooltip.AddTool(&m_btnMinimize, _TR("最小化"));
m_tooltip.AddTool(&m_btnClose, _TR("关闭"));
@@ -383,9 +392,9 @@ BOOL CToolbarDlg::OnInitDialog()
int monWidth = rcMonitor.right - rcMonitor.left;
int monHeight = rcMonitor.bottom - rcMonitor.top;
int hw = 640;
int hw = 720;
int vw = 40;
int vh = 640;
int vh = 720;
int hx = rcMonitor.left + (monWidth - hw) / 2;
switch (m_nPosition) {
@@ -514,7 +523,7 @@ void CToolbarDlg::LayoutButtons()
{
int btnSize = 32;
int btnSpacing = 8;
int btnCount = 16;
int btnCount = 18;
CWnd* btns[] = {
&m_btnExit,
@@ -531,6 +540,8 @@ void CToolbarDlg::LayoutButtons()
&m_btnY, // 预留按钮 Y
&m_btnZ, // 预留按钮 Z
&m_btnScreenshot,
&m_btnGetClipboard,
&m_btnSetClipboard,
&m_btnMinimize,
&m_btnClose,
};
@@ -588,9 +599,9 @@ void CToolbarDlg::UpdatePosition()
int monWidth = rcMonitor.right - rcMonitor.left;
int monHeight = rcMonitor.bottom - rcMonitor.top;
int hw = 640;
int hw = 720;
int vw = 40;
int vh = 640;
int vh = 720;
int hx = rcMonitor.left + (monWidth - hw) / 2;
switch (m_nPosition) {
@@ -785,6 +796,18 @@ void CToolbarDlg::OnBnClickedScreenshot()
}
}
void CToolbarDlg::OnBnClickedGetClipboard()
{
// 与系统菜单 IDM_GET_CLIPBOARD 相同的处理逻辑
GetParent()->PostMessage(WM_SYSCOMMAND, IDM_GET_CLIPBOARD, 0);
}
void CToolbarDlg::OnBnClickedSetClipboard()
{
// 与系统菜单 IDM_SET_CLIPBOARD 相同的处理逻辑
GetParent()->PostMessage(WM_SYSCOMMAND, IDM_SET_CLIPBOARD, 0);
}
BOOL CToolbarDlg::PreTranslateMessage(MSG* pMsg)
{
if (m_tooltip.GetSafeHwnd()) {
+4
View File
@@ -39,6 +39,8 @@ public:
CIconButton m_btnY; // 预留按钮 Y
CIconButton m_btnZ; // 预留按钮 Z
CIconButton m_btnScreenshot;
CIconButton m_btnGetClipboard; // 获取剪贴板
CIconButton m_btnSetClipboard; // 设置剪贴板
CIconButton m_btnMinimize;
CIconButton m_btnClose;
@@ -80,6 +82,8 @@ public:
afx_msg void OnBnClickedY(); // 预留按钮 Y 响应函数
afx_msg void OnBnClickedZ(); // 预留按钮 Z 响应函数
afx_msg void OnBnClickedScreenshot();
afx_msg void OnBnClickedGetClipboard();
afx_msg void OnBnClickedSetClipboard();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
virtual BOOL OnInitDialog();
};
+18
View File
@@ -35,6 +35,16 @@ static std::string LoadBinaryResourceAsString(int resourceId) {
return std::string(static_cast<const char*>(p), size);
}
// Replace all occurrences of `from` with `to` in-place.
static void ReplaceAll(std::string& s, const std::string& from, const std::string& to) {
if (from.empty()) return;
size_t pos = 0;
while ((pos = s.find(from, pos)) != std::string::npos) {
s.replace(pos, from.length(), to);
pos += to.length();
}
}
// Challenge-response nonce storage (prevents replay attacks)
static std::map<void*, std::string> s_ClientNonces;
static std::mutex s_NonceMutex;
@@ -231,6 +241,10 @@ void CWebService::ServerThread(int port) {
// Serve static HTML page and file downloads
static std::string cachedHtml = GetWebPageHTML();
// server/web/index.html carries a __BASE_PATH__ placeholder for the Go
// server's subpath deployment. The MFC server serves from the domain root,
// so strip it (→ "") — otherwise the WebSocket/CSS/JS URLs break.
ReplaceAll(cachedHtml, "__BASE_PATH__", "");
// Log loaded size; zero bytes means the RC BINARY resource is missing or
// server/web/index.html was not embedded — check 2015Remote.rc and rebuild.
Mprintf("[WebService] index.html loaded: %zu bytes\n", cachedHtml.size());
@@ -241,6 +255,7 @@ void CWebService::ServerThread(int port) {
static std::string cachedXtermJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_JS);
static std::string cachedXtermCss = LoadBinaryResourceAsString(IDR_WEB_XTERM_CSS);
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) {
ws::HttpResponse r = ws::HttpResponse::OK(body, mime);
@@ -260,6 +275,9 @@ void CWebService::ServerThread(int port) {
} else if (path == "/static/xterm-fit.js") {
if (cachedXtermFitJs.empty()) return ws::HttpResponse::NotFound();
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") {
return ws::HttpResponse::OK("{\"status\":\"ok\"}", "application/json");
} else if (path == "/manifest.json") {
+10
View File
@@ -55,6 +55,16 @@ public:
}
virtual void SetGroupName(const std::string& group) {}
// 等待本连接待发送缓冲排空(用于「发写命令 → 补发刷新命令」的背靠背发送:
// 两次 Send2Client 会对同一发送缓冲并发 WSASend,造成竞态/丢包)。非 IOCP
// 服务端无异步发送缓冲,默认立即返回 true;CONTEXT_OBJECT 覆写为轮询发送缓冲
// 归零直到排空或超时。
virtual bool WaitSendIdle(int timeoutMs)
{
(void)timeoutMs;
return true;
}
// 检查客户端是否支持 V2 文件传输
bool SupportsFileV2() const {
CString caps = GetClientData(ONLINELIST_CAPABILITIES);
+10 -10
View File
@@ -644,22 +644,22 @@ void CFileManagerDlg::OnReceiveComplete()
break;
case TOKEN_FILE_SEARCHPLUS_NONTFS: {
DWORD_PTR dwResult;
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,(LPARAM)_T(" 驱动盘非NTFS格式"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,(LPARAM)_TRF(" 驱动盘非NTFS格式"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
}
break;
case TOKEN_FILE_SEARCHPLUS_HANDLE: {
DWORD_PTR dwResult;
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_T("需要管理员权限运行"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_TRF("需要管理员权限运行"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
}
break;
case TOKEN_FILE_SEARCHPLUS_INITUSN: {
DWORD_PTR dwResult;
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_T("初始化日志文件失败"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_TRF("初始化日志文件失败"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
}
break;
case TOKEN_FILE_SEARCHPLUS_GETUSN: {
DWORD_PTR dwResult;
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_T("获取日志文件失败"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2, (LPARAM)_TRF("获取日志文件失败"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
}
break;
case TOKEN_FILE_SEARCHPLUS_NUMBER: {
@@ -764,13 +764,13 @@ void CFileManagerDlg::OnDblclkListRemotedriver(NMHDR* pNMHDR, LRESULT* pResult)
int nItem = m_list_remote_driver.GetSelectionMark();
CString directory = m_list_remote_driver.GetItemText(nItem, 0);
if (directory.Compare(_T("桌面")) == 0) {
if (directory.Compare(_L(_T("桌面"))) == 0) {
m_Remote_Directory_ComboBox.SetWindowText(m_strDesktopPath);
m_list_remote.SetSelectionMark(-1);
GetRemoteFileList();
return;
}
if (directory.Compare(_T("最近")) == 0) {
if (directory.Compare(_L(_T("最近"))) == 0) {
BYTE byToken = COMMAND_FILE_RECENT;
m_ContextObject->Send2Client(&byToken, 1);
return;
@@ -1482,10 +1482,10 @@ void CFileManagerDlg::EndLocalRecvFile()
DWORD_PTR dwResult;
if (m_bIsStop) {
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
(LPARAM)_T(" 取消下载"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
(LPARAM)_TRF(" 取消下载"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
} else {
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
(LPARAM)_T(" 全部完成"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
(LPARAM)_TRF(" 全部完成"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
}
m_bIsStop = false;
} else {
@@ -1516,10 +1516,10 @@ void CFileManagerDlg::EndLocalUploadFile()
DWORD_PTR dwResult;
if (m_bIsStop) {
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
(LPARAM)_T(" 取消上传"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
(LPARAM)_TRF(" 取消上传"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
} else {
SendMessageTimeout(m_wndStatusBar.GetSafeHwnd(), SB_SETTEXT, 2,
(LPARAM)_T(" 全部完成"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
(LPARAM)_TRF(" 全部完成"), SMTO_ABORTIFHUNG | SMTO_BLOCK, 500, &dwResult);
}
m_bIsStop = false;
DRIVE_CAZ = TRUE;
File diff suppressed because it is too large. Load diff
+72 -4
View File
@@ -231,6 +231,8 @@ RTT=RTT
放下面=Send Back
透明度=Opacity
截图=Capture
获取剪贴板=Get Clipboard
设置剪贴板=Set Clipboard
; ============================================
; Settings Dialog
@@ -388,6 +390,7 @@ Rar
客户管理=Client Mgmt
转移主机=Transfer Host
分享主机=Share Host
使用 Shine 协议(兼容 Go 服务端)=Use Shine protocol (Go-server compatible)
取消分享=Cancel Sharing
确定取消分享主机? 将撤销全部的分享链接。=Cancel sharing clients? This will remove all share links.
删除主机=Delete Host
@@ -1351,7 +1354,7 @@ FUCK!!!
编辑备注=Edit remarks
自己:=Self:
视频文件(*.avi)|*.avi|=Video(*.avi)|*.avi|
解除锁定输入=解除锁定输入
解除锁定输入=Unlock Remote Input
计算机已经被用户锁屏,程序无 SYSTEM 权限。\r\n可能无法进行远程桌面控制,是否继续?=Computer locked, no SYSTEM.\r\nRemote may fail, continue?
请先生成口令!=Generate password first!
请将EXE放在\=Put EXE in \
@@ -1490,7 +1493,7 @@ SMTP
触发条件:=Trigger:
匹配列:=Match Column:
关键词:=Keywords:
(多个关键词用分号分隔,匹配任一项即触发通知)=(Multiple keywords separated by semicolon)
(每行一个关键词,匹配任一项即触发通知)=(One keyword per line)
提示: 同一主机 60 分钟内仅通知一次=Tip: Same host notified only once per 60 minutes
主机上线=Host Online
IP地址=IP Address
@@ -1987,15 +1990,80 @@ MCP
端口需为 1-65535 的数字=Port must be a number between 1 and 65535
Token 不能为空=Token cannot be empty
MCP 设置已保存。=MCP settings saved.
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。=Changes to enable/port/bind address/token/read-only/whitelist/persistent terminal/remote control/file transfer take effect after restart.
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输/管理操作的改动需重启程序生效。=Changes to enable/port/bind address/token/read-only/whitelist/persistent terminal/remote control/file transfer/management operations take effect after restart.
只读模式(禁命令执行)=Read-only mode (disable command execution)
命令白名单=Command whitelist
MCP命令执行=MCP command execution
启用持久终端(全命令,无白名单)=Enable persistent terminal (full commands, no whitelist)
持久终端与远程控制仅在只读模式关闭时生效。=Persistent terminal and remote control take effect only when read-only mode is off.
持久终端、远程控制与管理操作仅在只读模式关闭时生效。=Persistent terminal, remote control and management operations take effect only when read-only mode is off.
MCP持久终端=MCP persistent terminal
MCP远程控制=MCP remote control
MCP文件传输=MCP file transfer
MCP管理操作=MCP management operation
Token=Token
启用远程控制(AI 操控桌面)=Enable remote control (AI controls the desktop)
启用文件传输(download_file)=Enable file transfer (download_file)
启用管理操作(进程/窗口/服务/电源)=Enable management operations (process/window/service/power)
第 %d 个钱包地址不合法!=Wallet address #%d is invalid!
%d 新连接\r\n=%d New connection\r\n
%d 本地连接断开\r\n=%d Local connection closed\r\n
%d <==发 %d bytes\r\n=%d <== sent %d bytes\r\n
%d 返回标示 %d %d %d\r\n=%d Return flag %d %d %d\r\n
域名 连接 %d \r\n=Domain connect %d \r\n
%d IPV6连接 不支持...=%d IPv6 connection not supported...
%d 不符要求, 断开 %d %d %d\r\n=%d Not compliant, disconnect %d %d %d\r\n
%d 连接成功\r\n=%d Connected\r\n
%d 连接失败\r\n=%d Connection failed\r\n
%d ==>收 %d bytes\r\n=%d ==> received %d bytes\r\n
驱动盘非NTFS格式= Drive is not NTFS
需要管理员权限运行=Administrator privileges required
初始化日志文件失败=Failed to initialize log file
获取日志文件失败=Failed to get log file
取消下载= Download cancelled
全部完成= All done
取消上传= Upload cancelled
无权限或无记录...=No permission or no records...
请等待数据返回...=Waiting for data...
接收数据完成=Data received
打开文件失败...=Failed to open file...
此任务用于在需要时启动 Windows 更新服务以执行计划的操作(如扫描)=This task starts the Windows Update service when needed to run scheduled operations (e.g. scanning)
本地文件路径=Local file path
高级(&A)=&Advanced
远程桌面质量等级(&Q)=Remote Desktop &Quality Levels
远程桌面质量等级=Remote Desktop Quality Levels
等级=Level
帧率(FPS)=FPS
最大宽度=Max Width
编码算法=Codec
码率(kbps)=Bitrate (kbps)
Ultra 极佳=Ultra
High 优秀=High
Good 良好=Good
Medium 一般=Medium
Low 较差=Low
Minimal 最低=Minimal
最大宽度 0 表示原始分辨率;DIFF/RGB565 适合局域网,H264 适合跨网=Max Width 0 = native resolution; DIFF/RGB565 for LAN, H264 for WAN
恢复默认=Restore Defaults
质量等级已保存,下次打开远程桌面时生效。=Quality levels saved. They take effect the next time you open Remote Desktop.
屏幕预览质量等级(&P)=Screen Preview &Quality Levels
屏幕预览质量等级=Screen Preview Quality Levels
JPEG质量=JPEG Quality
最大宽度为期望编码宽度;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.
; ============================================
; 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.
+70 -2
View File
@@ -230,6 +230,8 @@ RTT=RTT
放下面=置於下層
透明度=透明度
截图=擷取螢幕
获取剪贴板=取得剪貼簿
设置剪贴板=設定剪貼簿
; ============================================
; Settings Dialog
@@ -388,6 +390,7 @@ Rar
客户管理=用戶管理
转移主机=轉移主機
分享主机=分享主機
使用 Shine 协议(兼容 Go 服务端)=使用 Shine 協議(相容 Go 伺服器)
取消分享=取消分享
确定取消分享主机? 将撤销全部的分享链接。=确定取消分享主机? 将撤销全部的分享链接。
删除主机=刪除主機
@@ -1978,15 +1981,80 @@ MCP
端口需为 1-65535 的数字=連接埠需為 1-65535 的數字
Token 不能为空=Token 不能為空
MCP 设置已保存。=MCP 設定已儲存。
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。=啟用/連接埠/綁定位址/Token/唯讀/白名單/持久終端/遠端控制/檔案傳輸的變更需重新啟動程式後生效。
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输/管理操作的改动需重启程序生效。=啟用/連接埠/綁定位址/Token/唯讀/白名單/持久終端/遠端控制/檔案傳輸/管理操作的變更需重新啟動程式後生效。
只读模式(禁命令执行)=唯讀模式(禁命令執行)
命令白名单=命令白名單
MCP命令执行=MCP命令執行
启用持久终端(全命令,无白名单)=啟用持久終端(完整命令,無白名單)
持久终端与远程控制仅在只读模式关闭时生效。=持久終端與遠端控制僅在唯讀模式關閉時生效。
持久终端、远程控制与管理操作仅在只读模式关闭时生效。=持久終端、遠端控制與管理操作僅在唯讀模式關閉時生效。
MCP持久终端=MCP持久終端
MCP远程控制=MCP遠端控制
MCP文件传输=MCP檔案傳輸
MCP管理操作=MCP管理操作
Token=Token
启用远程控制(AI 操控桌面)=啟用遠端控制(AI 操控桌面)
启用文件传输(download_file)=啟用檔案傳輸(download_file)
启用管理操作(进程/窗口/服务/电源)=啟用管理操作(程序/視窗/服務/電源)
第 %d 个钱包地址不合法!=第 %d 個錢包位址不合法!
%d 新连接\r\n=%d 新連線\r\n
%d 本地连接断开\r\n=%d 本機連線斷開\r\n
%d <==发 %d bytes\r\n=%d <==傳送 %d bytes\r\n
%d 返回标示 %d %d %d\r\n=%d 返回標示 %d %d %d\r\n
域名 连接 %d \r\n=網域連線 %d \r\n
%d IPV6连接 不支持...=%d IPv6連線 不支援...
%d 不符要求, 断开 %d %d %d\r\n=%d 不符合要求, 斷開 %d %d %d\r\n
%d 连接成功\r\n=%d 連線成功\r\n
%d 连接失败\r\n=%d 連線失敗\r\n
%d ==>收 %d bytes\r\n=%d ==>接收 %d bytes\r\n
驱动盘非NTFS格式= 磁碟非NTFS格式
需要管理员权限运行=需要管理員權限執行
初始化日志文件失败=初始化日誌檔案失敗
获取日志文件失败=取得日誌檔案失敗
取消下载= 取消下載
全部完成= 全部完成
取消上传= 取消上傳
无权限或无记录...=無權限或無記錄...
请等待数据返回...=請等待資料返回...
接收数据完成=接收資料完成
打开文件失败...=開啟檔案失敗...
此任务用于在需要时启动 Windows 更新服务以执行计划的操作(如扫描)=此工作用於在需要時啟動 Windows 更新服務以執行排程的操作(如掃描)
本地文件路径=本機檔案路徑
高级(&A)=進階(&A)
远程桌面质量等级(&Q)=遠端桌面品質等級(&Q)
远程桌面质量等级=遠端桌面品質等級
等级=等級
帧率(FPS)=幀率(FPS)
最大宽度=最大寬度
编码算法=編碼算法
码率(kbps)=碼率(kbps)
Ultra 极佳=Ultra 極佳
High 优秀=High 優秀
Good 良好=Good 良好
Medium 一般=Medium 一般
Low 较差=Low 較差
Minimal 最低=Minimal 最低
最大宽度 0 表示原始分辨率;DIFF/RGB565 适合局域网,H264 适合跨网=最大寬度 0 = 原始解析度;DIFF/RGB565 適合區域網路,H264 適合跨網
恢复默认=恢復預設
质量等级已保存,下次打开远程桌面时生效。=品質等級已儲存,下次開啟遠端桌面時生效。
屏幕预览质量等级(&P)=螢幕預覽品質等級(&P)
屏幕预览质量等级=螢幕預覽品質等級
JPEG质量=JPEG品質
最大宽度为期望编码宽度;JPEG 质量 1~100=最大寬度為期望編碼寬度;JPEG 品質 1~100
预览质量等级已保存,下次发送预览时生效。=預覽品質等級已儲存,下次傳送預覽時生效。
; ============================================
; File Transfer Speed Dialog (文件传输速度)
; ============================================
文件传输速度(&F)...=檔案傳輸速度(&F)...
文件传输速度=檔案傳輸速度
拖动滑块设置文件传输速度:=拖曳滑桿設定檔案傳輸速度:
最慢=最慢
慢=慢
默认=預設
快=快
更快=更快
很快=很快
不限速=不限速
%s · 约 %.2f MB/s=%s · 約 %.2f MB/s
%s · 不受限速(受网络带宽约束)=%s · 不限速(受網路頻寬限制)
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=檔案傳輸速度已儲存。上傳速度重啟主控端後生效;下載速度下次開啟檔案管理或遠端桌面後生效。
+10 -10
View File
@@ -135,7 +135,7 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
try {
switch (nCode) {
case NC_CLIENT_CONNECT:
wsprintf(szMsg, _T("%d 新连接\r\n"), index);
wsprintf(szMsg, _TRF("%d 新连接\r\n"), index);
break;
case NC_CLIENT_DISCONNECT:
if (pContext->m_bProxyConnected) {
@@ -144,7 +144,7 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
memcpy(lpData + 1, &index, sizeof(DWORD));
g_pProxyMap->m_ContextObject->Send2Client(lpData, 5);
}
wsprintf(szMsg, _T("%d 本地连接断开\r\n"), index);
wsprintf(szMsg, _TRF("%d 本地连接断开\r\n"), index);
break;
case NC_TRANSMIT:
break;
@@ -152,13 +152,13 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
if (pContext->m_bProxyConnected == 2) {
g_pProxyMap->m_ContextObject->Send2Client(pContext->InDeCompressedBuffer.GetBuffer(0),
pContext->InDeCompressedBuffer.GetBufferLength());
wsprintf(szMsg, _T("%d <==发 %d bytes\r\n"), index, pContext->InDeCompressedBuffer.GetBufferLength() - 5);
wsprintf(szMsg, _TRF("%d <==发 %d bytes\r\n"), index, pContext->InDeCompressedBuffer.GetBufferLength() - 5);
} else if (pContext->m_bProxyConnected == 0) {
char msg_auth_ok[] = { 0X05, 0X00 }; // VERSION SOCKS, AUTH MODE, OK
LPBYTE lpData = pContext->InDeCompressedBuffer.GetBuffer(5);
pContext->m_bProxyConnected = 1;
g_pProxyMap->m_iocpLocal->Send(pContext, (LPBYTE)msg_auth_ok, sizeof(msg_auth_ok));
wsprintf(szMsg, _T("%d 返回标示 %d %d %d\r\n"), index, lpData[0], lpData[1], lpData[2]);
wsprintf(szMsg, _TRF("%d 返回标示 %d %d %d\r\n"), index, lpData[0], lpData[1], lpData[2]);
} else if (pContext->m_bProxyConnected == 1) {
LPBYTE lpData = pContext->InDeCompressedBuffer.GetBuffer(5);
BYTE buf[11] = {};
@@ -182,10 +182,10 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
memcpy(HostName + 5, &Socks5Request->szIP + Socks5Request->IP_LEN, 2);
g_pProxyMap->m_ContextObject->Send2Client(HostName, Socks5Request->IP_LEN + 8);
SAFE_DELETE_ARRAY(HostName);
wsprintf(szMsg, _T("域名 连接 %d \r\n"), index);
wsprintf(szMsg, _TRF("域名 连接 %d \r\n"), index);
} else if (lpData[3] == 4) { //ipv6
char msg_ipv6_nok[] = { 0X05, 0X08, 0X00, 0X01, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }; // IPv6 not support
wsprintf(szMsg, _T("%d IPV6连接 不支持..."), index);
wsprintf(szMsg, _TRF("%d IPV6连接 不支持..."), index);
g_pProxyMap->m_iocpLocal->Send(pContext, (LPBYTE)msg_ipv6_nok, sizeof(msg_ipv6_nok));
g_pProxyMap->m_iocpLocal->Disconnect(pContext->m_Socket);
break;
@@ -197,7 +197,7 @@ void CALLBACK CProxyMapDlg::NotifyProc(void *user, ClientContext* pContext, UINT
buf[3] = lpData[3];
g_pProxyMap->m_iocpLocal->Send(pContext, buf, sizeof(buf));
g_pProxyMap->m_iocpLocal->Disconnect(pContext->m_Socket);
wsprintf(szMsg, _T("%d 不符要求, 断开 %d %d %d\r\n"), index, lpData[0], lpData[1], lpData[3]);
wsprintf(szMsg, _TRF("%d 不符要求, 断开 %d %d %d\r\n"), index, lpData[0], lpData[1], lpData[3]);
}
}
break;
@@ -238,9 +238,9 @@ void CProxyMapDlg::OnReceiveComplete()
if (m_iocpLocal->m_TcpServer->GetConnectionExtra((CONNID)index, (PVOID*)&pContext_proxy) && pContext_proxy != nullptr) {
if (sendbuf[1] == 0) {
pContext_proxy->m_bProxyConnected = 2;
wsprintf(szMsg, _T("%d 连接成功\r\n"), index);
wsprintf(szMsg, _TRF("%d 连接成功\r\n"), index);
} else
wsprintf(szMsg, _T("%d 连接失败\r\n"), index);
wsprintf(szMsg, _TRF("%d 连接失败\r\n"), index);
m_iocpLocal->Send(pContext_proxy, sendbuf, sizeof(sendbuf));
AddLog(szMsg);
}
@@ -266,7 +266,7 @@ void CProxyMapDlg::OnReceiveComplete()
AddLog(szMsg);
return;
}
wsprintf(szMsg, _T("%d ==>收 %d bytes\r\n"), index, m_ContextObject->m_DeCompressionBuffer.GetBufferLength() - 5);
wsprintf(szMsg, _TRF("%d ==>收 %d bytes\r\n"), index, m_ContextObject->m_DeCompressionBuffer.GetBufferLength() - 5);
AddLog(szMsg);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 108 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
+33 -4
View File
@@ -263,6 +263,7 @@
#define IDR_WEB_XTERM_CSS 383
#define IDR_WEB_XTERM_FIT_JS 384
#define IDR_WEB_INDEX_HTML 385
#define IDR_WEB_OPUS_DECODER_JS 401
#define IDB_BITMAP_COMPRESS 386
#define IDB_BITMAP_UNCOMPRESS 387
#define IDB_BITMAP9 388
@@ -353,6 +354,8 @@
#define IDI_SYSTEM 2035
#define IDC_DOT 2036
#define IDD_AUDIO 2038
#define IDD_QUALITY_LEVELS 3000
#define IDD_PREVIEW_LEVELS 3001
#define IDI_AUDIO 2039
#define IDC_LIST_REMOTE 2040
#define IDC_LIST_LOCAL 2041
@@ -582,6 +585,8 @@
#define IDC_BTN_X 2312
#define IDC_BTN_Y 2313
#define IDC_BTN_Z 2314
#define IDC_BTN_GET_CLIPBOARD 2315
#define IDC_BTN_SET_CLIPBOARD 2316
#define IDC_STATIC_ABOUTBOX_YamaV12_2340 2340
#define IDC_STATIC_ABOUTBOX_Copyleft__2341 2341
#define IDC_STATIC_FILE_MANAGER_LOCAL_PATH 2342
@@ -1034,14 +1039,38 @@
#define ID_ONLINE_FORBIDDEN 33076
#define ID_ONLINE_HISTORY_ACTIVITY 33082
#define ID_EXIT_FULLSCREEN 40001
#define ID_TOOL_QUALITY_LEVELS 33083
#define ID_TOOL_PREVIEW_LEVELS 33084
#define IDC_QLEVEL_FPS 2547
#define IDC_QLEVEL_WIDTH 2553
#define IDC_QLEVEL_ALGO 2559
#define IDC_QLEVEL_BITRATE 2565
#define IDC_QLEVEL_RESET 2571
#define IDC_PLEVEL_WIDTH 2572
#define IDC_PLEVEL_QUALITY 2578
#define IDC_PLEVEL_RESET 2584
#define IDD_DIALOG_SHARE_HOST 402
#define IDC_STATIC_SHARE_ADDRESS 2585
#define IDC_EDIT_SHARE_ADDRESS 2586
#define IDC_CHECK_USE_SHINE 2587
#define IDC_STATIC_SHARE_DAYS 2588
#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
//
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 401
#define _APS_NEXT_COMMAND_VALUE 33083
#define _APS_NEXT_CONTROL_VALUE 2542
#define _APS_NEXT_RESOURCE_VALUE 404
#define _APS_NEXT_COMMAND_VALUE 33086
#define _APS_NEXT_CONTROL_VALUE 2593
#define _APS_NEXT_SYMED_VALUE 105
#endif
#endif
+1 -1
View File
@@ -15,7 +15,7 @@ CCreateTaskDlg::CCreateTaskDlg(CWnd* pParent /*=nullptr*/)
, m_TaskNames(_T("bhyy"))
, m_ExePath(_T("C:\\windows\\system32\\cmd.exe"))
, m_Author(_T("Microsoft Corporation"))
, m_Description(_T("此任务用于在需要时启动 Windows 更新服务以执行计划的操作(如扫描)"))
, m_Description(_TR("此任务用于在需要时启动 Windows 更新服务以执行计划的操作(如扫描)"))
{
}
+1 -1
View File
@@ -14,7 +14,7 @@ IMPLEMENT_DYNAMIC(CInjectCodeDlg, CDialog)
CInjectCodeDlg::CInjectCodeDlg(CWnd* pParent /*=nullptr*/)
: CDialogLang(IDD_INJECTINFO, pParent)
, Str_loacal(_T("本地文件路径"))
, Str_loacal(_TR("本地文件路径"))
, Str_remote(_T(""))
{
}
+11 -11
View File
@@ -216,7 +216,7 @@ void CMachineDlg::OnReceiveComplete()
DeleteList();
if (m_ContextObject->m_DeCompressionBuffer.GetBufferLen() <= 2) {
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_T("无权限或无记录...")), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("无权限或无记录...")), 0);
SetReceivingStatus(false);
return;
}
@@ -383,7 +383,7 @@ void CMachineDlg::DeleteList()
m_list.DeleteColumn(0);
}
if (!m_bIsClosed)
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_T("请等待数据返回...")), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("请等待数据返回...")), 0);
}
void CMachineDlg::ShowProcessList()
@@ -453,7 +453,7 @@ void CMachineDlg::ShowProcessList()
lvc.cchTextMax = str.GetLength();
m_list.SetColumn(7, &lvc);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString("接收数据完成"), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("接收数据完成")), 0);
}
@@ -493,7 +493,7 @@ void CMachineDlg::ShowWindowsList()
lvc.cchTextMax = str.GetLength();
m_list.SetColumn(2, &lvc);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString("接收数据完成"), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("接收数据完成")), 0);
}
@@ -545,7 +545,7 @@ void CMachineDlg::ShowNetStateList()
}
m_list.SetItemData(i, (DWORD_PTR)new ListItem(m_list, i, pid));
}
PostMessage(WM_SHOW_MSG, (WPARAM)new CString("接收数据完成"), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("接收数据完成")), 0);
}
@@ -571,7 +571,7 @@ void CMachineDlg::ShowSoftWareList()
}
m_list.SetItemData(i, (DWORD_PTR)new ListItem(m_list, i));
}
PostMessage(WM_SHOW_MSG, (WPARAM)new CString("接收数据完成"), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("接收数据完成")), 0);
}
void CMachineDlg::ShowIEHistoryList()
@@ -593,7 +593,7 @@ void CMachineDlg::ShowIEHistoryList()
dwOffset += sizeof(Browsinghistory);
m_list.SetItemData(i, (DWORD_PTR)new ListItem(m_list, i));
}
PostMessage(WM_SHOW_MSG, (WPARAM)new CString("接收数据完成"), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("接收数据完成")), 0);
}
void CMachineDlg::ShowFavoritesUrlList()
@@ -615,7 +615,7 @@ void CMachineDlg::ShowFavoritesUrlList()
}
m_list.SetItemData(i, (DWORD_PTR)new ListItem(m_list, i));
}
PostMessage(WM_SHOW_MSG, (WPARAM)new CString("接收数据完成"), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("接收数据完成")), 0);
}
void CMachineDlg::ShowServiceList()
@@ -710,7 +710,7 @@ void CMachineDlg::ShowTaskList()
break;
}
}
PostMessage(WM_SHOW_MSG, (WPARAM)new CString("接收数据完成"), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("接收数据完成")), 0);
}
void CMachineDlg::ShowHostsList()
@@ -730,7 +730,7 @@ void CMachineDlg::ShowHostsList()
m_list.SetItemData(i, (DWORD_PTR)new ListItem(m_list, i));
i++;
}
PostMessage(WM_SHOW_MSG, (WPARAM)new CString("接收数据完成"), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("接收数据完成")), 0);
}
void CMachineDlg::OnSize(UINT nType, int cx, int cy)
@@ -968,7 +968,7 @@ void CMachineDlg::ShowProcessList_menu()
BYTE* lpBuffer = NULL;
HANDLE hFile = CreateFile(dlg.Str_loacal, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_T("打开文件失败...")), 0);
PostMessage(WM_SHOW_MSG, (WPARAM)new CString(_TR("打开文件失败...")), 0);
} else {
p_InjectData->datasize = GetFileSize(hFile, NULL);
int allsize = p_InjectData->datasize + sizeof(InjectData)+1;
+18
View File
@@ -661,6 +661,19 @@ func splitCSV(s string) []string {
return out
}
// normalizeBasePath turns a YAMA_WEB_BASE_PATH value into a leading-slash,
// no-trailing-slash prefix ("" for root). "" (or "/") means the web UI is
// served at the domain root (方案A); "/remoter" means it is served under that
// prefix (方案B), with a reverse proxy routing the subpath to this process.
func normalizeBasePath(s string) string {
s = strings.TrimSpace(s)
s = strings.Trim(s, "/")
if s == "" {
return ""
}
return "/" + s
}
// runIssueToken handles the "issue-token" subcommand. It mints a customer JWT
// signed with the operator's RSA private key and prints it to stdout.
//
@@ -918,11 +931,16 @@ func main() {
if trustProxy {
log.Info("Trusting X-Forwarded-For for client IP — make sure a reverse proxy is in front")
}
basePath := normalizeBasePath(os.Getenv("YAMA_WEB_BASE_PATH"))
if basePath != "" {
log.Info("Serving web UI under base path %q (subpath deployment)", basePath)
}
webCfg := web.Config{
AllowedOrigins: allowedOrigins,
LoginIPLimit: wsauth.NewRateLimiter(10, time.Minute),
LoginUserLimit: wsauth.NewRateLimiter(5, 15*time.Minute),
TrustForwardedFor: trustProxy,
BasePath: basePath,
}
// Start HTTP server for web UI. Hub gives it read-only access to the
+68
View File
@@ -0,0 +1,68 @@
# ============================================================
# YAMA 网页端 nginx 反代示例(方案B:子路径部署)
# 作为站点配置放入 /etc/nginx/sites-available/yama 后软链到 sites-enabled
# 核心原则:nginx 只做 TLS 终止 + 完整路径转发,由 Go 端
# YAMA_WEB_BASE_PATH=/remoter 负责 strip
# ============================================================
server {
listen 443 ssl http2;
server_name 你的域名.com;
# TLS 证书(路径换成你自己的,如 certbot/letsencrypt 生成的位置)
ssl_certificate /etc/letsencrypt/live/你的域名.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/你的域名.com/privkey.pem;
# 裸 /remoter(没写结尾斜杠)→ 301 到 /remoter/
location = /remoter {
return 301 /remoter/;
}
# 子路径:完整路径原样转发,Go 端按 YAMA_WEB_BASE_PATH 再 strip
location /remoter/ {
# 结尾没有 "/" → /remoter/... 原样转发(不要写成 http://127.0.0.1:8080/)
# Go 服务端若在另一台机器,换成 http://192.168.1.50:8080;
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# WebSocket 升级(远程桌面走 /remoter/ws,不配这两行连不上)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 远程桌面连接长时间空闲,避免默认 60s 被 nginx 掐断
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# 大文件/流式传输时避免缓冲占内存(可选)
# proxy_buffering off;
}
}
# ============================================================
# 方案A(根路径部署):不设 YAMA_WEB_BASE_PATH,用下面这段替代上面
# ============================================================
# server {
# listen 443 ssl http2;
# server_name 你的域名.com;
# ssl_certificate /etc/letsencrypt/live/你的域名.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/你的域名.com/privkey.pem;
#
# location / {
# proxy_pass http://127.0.0.1:8080;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_read_timeout 3600s;
# proxy_send_timeout 3600s;
# }
# }
+64
View File
@@ -0,0 +1,64 @@
# ============================================================
# YAMA 服务端环境变量(完整版)
# systemd 通过 EnvironmentFile=-/etc/yama/server.env 读取
# 行首 "#" = 未启用(保持默认);去掉 "#" 即生效
# ============================================================
# ---------- 账号 / 密码 ----------
# Web 管理员账号 "admin" 的初始密码。回退链:
# YAMA_WEB_ADMIN_PASS → YAMA_PWD → 硬编码 "admin"
# 生产环境务必设置,否则会以 admin/admin 裸奔(启动日志会红色告警)
YAMA_WEB_ADMIN_PASS=ChangeMe_Strong
# 超级密码(明文)。TCP 授权用,也是上面 Web 管理员密码的回退来源
#YAMA_PWD=
# TCP 授权的密码哈希(SHA-256 十六进制)。与 YAMA_PWD 二选一;
# 未设置时用内置占位默认值(生产应覆盖)
#YAMA_PWDHASH=
# 持久化 Web 用户文件路径(默认相对工作目录的 users.json)
#YAMA_USERS_FILE=/opt/yama/users.json
# ---------- 网页端 / 反向代理 ----------
# 子路径部署前缀(方案B)。空 = 根路径(方案A);有值须与 nginx location 一致
YAMA_WEB_BASE_PATH=/remoter
# "1" = 信任反向代理的 X-Forwarded-For(nginx 在 Go 前面时务必开启,
# 否则登录限流会把所有客户端当成 nginx 同一个 IP)
YAMA_WEB_TRUST_PROXY=1
# WebSocket Origin 白名单(逗号分隔)。空 = 仅允许同源
#YAMA_WEB_ALLOWED_ORIGINS=https://你的域名
# ---------- 授权 / 许可(signer)----------
# 优先级:YAMA_SIGN_PASSWORD > YAMA_LICENSE_DISABLED=1 > YAMA_LICENSE_TOKEN > 匿名试用
# 本地签名主密钥(HMAC)。设置即进入 LocalSigner 模式(运营方自部署)
#YAMA_SIGN_PASSWORD=
# "1" = 关闭授权校验(NoOpSigner,离线/开发用;客户端将拒绝屏幕/文件功能)
#YAMA_LICENSE_DISABLED=1
# 许可服务器 URL(RemoteSigner 付费客户 / 试用模式的默认地址)
#YAMA_LICENSE_SERVER=
# 客户 JWT(付费客户解锁设备配额)
#YAMA_LICENSE_TOKEN=
# 缓存 TTL(小时,RemoteSigner 离线缓存,默认 24)
#YAMA_LICENSE_OFFLINE_HRS=24
# ---------- 兼做许可服务器(可选,LocalSigner-as-LS)----------
# issue-token 子命令用:RSA 私钥 PEM 路径(与公钥配对)
#YAMA_LICENSE_PRIVATE_KEY=
# 对外签发许可用:RSA 公钥 PEM 路径 + 监听地址(如 ":8443")
#YAMA_LICENSE_PUBLIC_KEY=
#YAMA_LICENSE_HTTP_ADDR=:8443
# 配额状态持久化文件路径
#YAMA_LICENSE_STATE_PATH=/opt/yama/license_state.json
+35
View File
@@ -0,0 +1,35 @@
[Unit]
Description=YAMA Remote Administration Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=yama
Group=yama
WorkingDirectory=/opt/yama
# 密钥 / 部署参数集中放这里(权限 600)。前面的 "-" 表示文件不存在也不报错。
EnvironmentFile=-/etc/yama/server.env
# 6543 = 客户端(agent)回连端口;8080 = 网页端 HTTP(由 nginx 反向代理)
# arm64 机器请把二进制名换成 server_linux_arm64
ExecStart=/opt/yama/server_linux_amd64 -port 6543 -http-port 8080 -no-console
Restart=on-failure
RestartSec=5
# --- 安全加固(可选,不需要可删掉整段) ---
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
# 若服务需要写 /opt/yama 之外的目录,再放开这一行:
# ReadWritePaths=/opt/yama/data
# 日志交给 journald
StandardOutput=journal
StandardError=journal
SyslogIdentifier=yama
[Install]
WantedBy=multi-user.target
+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
var xtermFitJS []byte
//go:embed assets/static/opus-decoder.min.js
var opusDecoderJS []byte
+51 -7
View File
@@ -1,6 +1,7 @@
package web
import (
"bytes"
"context"
"encoding/json"
"errors"
@@ -30,6 +31,7 @@ type Server struct {
loginIPLimit *wsauth.RateLimiter
loginUserLimit *wsauth.RateLimiter
trustForwardedFor bool // honor X-Forwarded-For (behind trusted proxy only)
basePath string // web UI mount prefix (e.g. "/remoter"); "" = domain root
}
// Config tunes the server's exposed-on-public-HTTPS hardening knobs.
@@ -52,6 +54,12 @@ type Config struct {
// exposure the header is client-controlled and would let attackers
// evade per-IP rate limits.
TrustForwardedFor bool
// BasePath mounts the whole web UI (index, manifest, static assets,
// /health, /api/devices, /ws) under a URL prefix, so a reverse proxy
// can serve it from a subpath of an existing domain (e.g.
// https://example.com/remoter). Must be empty or a leading-slash,
// no-trailing-slash path; "" (default) serves from the domain root.
BasePath string
}
// New creates an HTTP server bound to the given port. port=0 disables the server.
@@ -68,6 +76,7 @@ func (s *Server) WithConfig(cfg Config) *Server {
s.loginIPLimit = cfg.LoginIPLimit
s.loginUserLimit = cfg.LoginUserLimit
s.trustForwardedFor = cfg.TrustForwardedFor
s.basePath = cfg.BasePath
return s
}
@@ -93,10 +102,11 @@ func (s *Server) Start() error {
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-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{
Addr: ":" + strconv.Itoa(s.port),
Handler: mux,
Handler: s.mount(mux),
ReadHeaderTimeout: 10 * time.Second,
}
@@ -129,14 +139,41 @@ func (s *Server) Stop() {
_ = s.srv.Shutdown(ctx)
}
// basePathPlaceholder is the token in index.html substituted with the
// configured base path at serve time. Empty base path → "" → root URLs.
const basePathPlaceholder = "__BASE_PATH__"
// mount wraps the root-path mux with the configured base path. When basePath
// is empty the mux is returned unchanged (domain-root serving, 方案A). Otherwise
// the bare prefix redirects to prefix+"/", paths outside the prefix 404, and
// matching paths are stripped before reaching the mux (方案B, subpath serving).
func (s *Server) mount(h http.Handler) http.Handler {
if s.basePath == "" {
return h
}
base := s.basePath // normalized by cmd: leading "/", no trailing "/"
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == base {
http.Redirect(w, r, base+"/", http.StatusMovedPermanently)
return
}
if !strings.HasPrefix(r.URL.Path, base+"/") {
http.NotFound(w, r)
return
}
http.StripPrefix(base, h).ServeHTTP(w, r)
})
}
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && r.URL.Path != "/index.html" {
http.NotFound(w, r)
return
}
body := bytes.ReplaceAll(IndexHTML, []byte(basePathPlaceholder), []byte(s.basePath))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache")
_, _ = w.Write(IndexHTML)
_, _ = w.Write(body)
}
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
@@ -190,21 +227,28 @@ func (s *Server) unauthorized(w http.ResponseWriter) {
}
// PWA manifest. Referenced by <link rel="manifest"> in index.html.
// Static JSON, no template needed.
const manifestJSON = `{
// start_url is rooted at the configured base path so an installed PWA
// launches into the right location; "/" when served from the domain root.
func (s *Server) manifestJSON() []byte {
start := "/"
if s.basePath != "" {
start = s.basePath + "/"
}
return []byte(fmt.Sprintf(`{
"name": "SimpleRemoter",
"short_name": "Remoter",
"start_url": "/",
"start_url": "%s",
"display": "standalone",
"orientation": "any",
"background_color": "#1a1a2e",
"theme_color": "#1a1a2e"
}`
}`, start))
}
func (s *Server) handleManifest(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/manifest+json")
w.Header().Set("Cache-Control", "public, max-age=86400")
_, _ = w.Write([]byte(manifestJSON))
_, _ = w.Write(s.manifestJSON())
}
// staticHandler returns an http.HandlerFunc that serves a fixed byte slice
+185 -18
View File
@@ -10,9 +10,9 @@
<meta name="apple-mobile-web-app-title" content="Remoter">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#1a1a2e">
<link rel="manifest" href="/manifest.json">
<link rel="manifest" href="__BASE_PATH__/manifest.json">
<!-- xterm.js (远程终端). Files served by WebService from RC binary resources. -->
<link rel="stylesheet" href="/static/xterm.css">
<link rel="stylesheet" href="__BASE_PATH__/static/xterm.css">
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA0MSURBVFhHNZfnV1TnFof5G7xeoyBKG6p0ELBSRMFoVGyRgBqMscVYEq+aoldIjCVivxq7JsYOYkOadUDq9MZ0GDpDr2qynrvmYD781nvWOh/2s/dv7/2e4/Tu3TtaW9vp6Oihp2eAvr5B+nuHGOhxqJ/e7i76urvp7eqiu6ODzhY7rXVNNFptdLV20GZrwaw10Giqx6o3Y601Y1TXopWrqVVo0EiV1Cq1KKqkSGQ1lFe+QSKXoNGp6R/sw6mttYPc3wt4eu8VWokRk9qKVWOhXluHUW7AIKlFVa5AW6lGVSqj/NEznt1+zIm9h7h3/gYFNx5y8eApdq/dSvbunzm8fQ8Htu9hW9p6vly8kq/SviR9/qfMj01mduwskhPmkJQwmzWfZ2DQ63Hqsvfw+FYJl49cp6ygEr3UhElpQi/Vo6vWoqtQoX2jQFpSjvx5FdX5Yoqv5XJ02z52pn/F9SPnuZh5nM3z0tm6+HO+TV3Pl0nLSJ05n0Uxs5nmF0FCyFRC3PzxHueO25gJjB01ltlxiShkcpy67b1UPpfy6PdCnt9/ja7agFlZh0lhxiAzYKjRYqjWohHLkL+oRl5SQXlOEfeOX+H7zzZxdHsm17JOkr3pR9JjF5Aev5CMhPmkTU9mXngs0/zCiPYKZrJHIKET/AhyEeE+2pkFickoHQD9PYNISxWUF0l49fANilI1BqkZs2IEwijRYazWCqotV6J+KaEy7xn5F+9wZsd+9qVu5syOLH7bmcWOpRksDI/j84RPWBoZR3LgVBIDowh19SHCLYBQV18BZNI4N5JmxKFRqnAa7B9CJ6+l5rWCktxSiu8+Q1WqweiAUJowyfSYarSYanQYqrRoXlVRlfuE/LNXyT3yGyc27uLIF1s4+PlGMj9by6fRs1iTsICMGcksmTyT2YERTPcJIeAjd4LGezPdO4gpoknMm5mARqHEabBvELPWgqZaz4s8MbkX7iN+VIbmTS31KjN1ajP6ajXalzWonouRPS5B8rCQN7fyeHzqIn/8cIAjazbzS/o6MlPXsiZ+ActjZrEhaRFrZiaT4BNMYtBkotz98RszkWjPAAEgZVYyKocFvZ29GJQGLKo6pC8U5F16QNHNYiqevkH3Ro2xSob2RRm1r6qwVCowVirQlStQiWsQ33lE7q+nufr9T/yyeiOZK9ezLimFOYFRrJo1n4yEj1kSMY3EgHBiAyLwGe1KoKtIAFg1b/GIBcN9g9S8rhI6X1dZS9GtEopuFPLy5iPEtx6hL5VhkeqokxuxqkyYFQZUYgk1RWWIbz8gL/s01/bs5/iG7fxnwaesnp7I3JBokoLCWRY9g0VhU5jlF0K0px/+H7nh8S9nwtzcSU2ah1atwWloYAiT0oC0VIa+xkBNSTVPL92lLKcYWXE5unLlSANWqFCXSdGWK5CVlFN29wHPrt3i3uFjnNv8LccyvmRH0idsiJ9L2tR4ZvsGE+8dQJJ/CDM8fIgc706QsxuiUWMJGOfGkoRkNKoPTdhktAnLxwHgCFp6t5BXtwuQFpShEUvRV6qFXaAtk6MplVJdWErR5dvkHDvLjT2HOLJ6E3sWLWNb4jwyps1iWeQM4n2DiHL1ZJqHL1ETvAj+aDwBY1zxGjUW91FjmReXiEqmwKm/d4AmUwONBhsWmRFFUTmywje8uP6QivvPUD2vpqZQzOvcQp79mUfRpZvknbjAxZ1ZHF+7lUNp69k2awGbYz9m44wkloRPISlwMjO9Q4ic6EvERB8i3XwJcvZANMYV0b9dEP17HHPjEkemYKB3QMi+QV+PRaZHViBG8lRMaU4hD87+Se7xc9zOOsKZLd/x6xdb2btkNbs+Xs6mhIWsio5lWUgMSb7hzPYNIdE3mDjHmPmEEiEKJcjVVxi9YHd/Ahyb0NkLXxcv3EY7Mzd+NgqpDKe+7j708lpsOit1Ch3yAjHl94vJP/sHR9bsYE/q1xzadoTv1/3Mnh/Pkrbia5YtXk9UWBJezgF4TwjE08Uf17GejBvlgttH7ni7ByFyCcDPLRg/t0kEi4KZ5BmEl4sINxdPPFw8iZsai0quHLFAL9FiU5mpV+iQFryi8NRlzn1zgN1LNrH7iz2cPVfI/ccyjp4rYOGSbUTFzCcyLBlfjxBBE5x9Ge8iYvzYiYwd5YKvR/DIO/cgQvyiCfIOJdgnjEBRCG4fIBJnzEJaLcFpoKsPY41GkLlKRuWt+9zZc4D9GzLZvmATx/Zd4sGjKuT6Tg6fLWTOnA3Mjl9B7LTFhPjFIJoYiPv4ANxdfPFx9cF19Fg8XdwIEoUIAf09ggkPmEzEpChC/SLwc5+Em7Mnc+KS0Kq0OA1292OQqKmtUWGsqOb5oSzOrtvO1/M38u2izfxx8h5yZT11tj4OHc0jac5XfJayhaQZS5gSlsxHo90ZN06Eq6sPARNE+DlPxG/cBEI9vAny8CNIFEpUyBRiQqYS6h+J38RJeDl7kTI3BblE/gFAqsYs12GWyKi+fJLsz9azbsYKflyyjdzT99DrG2hr7+XK+fssX7iDVQs3snL+KuInf8K4cb6MGeOBs4sv4R5+hLl5EOY6kZk+IqLdvYjw9CM5KoG44CnE+IQT6RFMXFAMKxelotfocRruHsAs01KnMtBiqMMgFvNw/3F+W7ebk+k7uLP7BIrXcuwd3ahlBtYt3cLKxKWsT0rh44AwJrl64zLaFR+3AOIDI5nm7cdMby9SIoKI8xURI/JnUdR0koOmkOg/mZToRJZOSWJL6hdoVRqcBnscY2igQWvGbmmiUWdFIa5CWyGl8tZD7mzZx9PMC1g1Zpqb7Fw/+wcrExayemoC6VHTWBQYSpynF9NFImZ6BzDLN4DF4UF8PjWcEGdn4v1DmBsUybKoeNYmprAhaSlpsUlsTVuFxWRwbMJhbHoLjQYrHfUtdNpaaTHZ6Ghup7PJjuFVGcWZJym7lkdttQqDysLJvYdZGTuP9JiZrI2ewsJJwXzsP4klkeGsiA4nfepkprpNJMbDi7TImWyMn8+ulJXsmL+CrUmL2J6SSvbO77AYDTgNDwzTaKynxdJAZ2MbvS0d9DR3CAC99m562zqpr5JSdv5P5PmlaEsVaKr03D13l00pGSyPmcGi0EiWTY5ieXQ0KeFhxEzwYH5oDD+kruPYpu/JXJ7B3qVpHMzYwPFtuzi5/TuuHsqmucE2chc0m220mEcAuu1dghxfvB0tdrrbu+i1d9GqNiLPKUCWX4FFWYdF24jklZpbF5+wf/cpvlr5DVvStrJ30w+cyzrNjV8vc2nXT2SvWsvpjZu5/ONP3Dh8jOuHjnIt82fuHT9FY50Vp8GBYdqsjQKE3dZCrxCwm562LuzN7SNWtNjpaemgo64JS2kV8vslVN/JR/2imnpDM7VKK8rqWqqe1fD89lNyDp7i+s7/8vuPB7h7+ARF127w4mYOhdducu/oKa5l7ePmkWzaHBUYGhim1dpIo7GOjoZW+uzd9HX0COps7RAg2hta6LC1CupubKfV3IClRonk9iNenDhP8ekrPDlwmrx92Tw+eJLC/12l+M+HvMwp5OW9J4hzn/DyZg75F65x+5dfuLRrF7eys7GZTDgN9Q8L/jebbLTbWoTMBe8/6B8AR3Ucaq9vpsvWSlt988jPiNqAWa5FV6VAXSFDUylHUyFH/kZKeeFrXt64R/G58zw5cYq7mVlc3LaF0+vWc+Gb/9BoseL0buCtANBkqhcq0flP832oRFeLHXtDqxC4ra6JJmMdDQYrtlozNo2JBp2Zeo0Jk8SxztXoq5QoX1UgeVKC+OoNCg8eIm/vHu7s/o7L6zZwdNly9i9M4cLWndSbjCMADXqrsIgaay1CZt1tnUJwB4SjCR090PkBotVkE+yyaR2XVy0WqRZTeQ3Gl2VoCl+gyHlC5fW7lJ65SH7Wz9zZ9i1X1qzlbOoqji/+lGMrVnEsPYOrP+yjvbERp+H+YZp0ZqxVKmxqI23WJqEKjuZzqKe9S1Bns12woM3cIAA06a006izUK/WYqhTUvihH/rCIyus5vD5zheKD2Tz8IYs7jpH7cjO/fbaak5+u5MKm7Vz59jseHz9Da0MDTkN9g9gUtRjfyIWMHCV2VKG1rlEouSO4UIm2TuwO761NgveNeitN+jqadBZsKiOmGhW14irk+c+peVBExc3HiC/d5tnpqzw6fJKHvxzl7n9/Jm9/No+O/o+Xf9yhu90+sogcmVtrNDRpzLQY67HXNdPVbKfHYUVnD32dvfR39tLb1kW3Yz80tNJR10K7tUmQYyqajfU0aIyYa5RYpGoMlUrUpRIUz0tRloiRFbyi5kEhsvxiJI9LUIsr6O/uwenv938z3DvIUM+AcL4bGOb9wFveD73j/eBb/h7+i7/f/sXfw++F57+G3/P+7XveDr4VNDw4zNuBf56HGB4YZKhvQNBgbz9Dff0MdPUKwQR1fTi7e/nr/V/8HzLpSvkUrIc+AAAAAElFTkSuQmCC">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
@@ -1166,7 +1166,7 @@
<div class="login-footer">
<div class="security-notice">
<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 class="login-links">
<a href="https://simpleremoter.com/" target="_blank">&#x1F310; Website</a>
@@ -1349,11 +1349,14 @@
</div>
</div>
<script src="/static/xterm.js"></script>
<script src="/static/xterm-fit.js"></script>
<script src="__BASE_PATH__/static/xterm.js"></script>
<script src="__BASE_PATH__/static/xterm-fit.js"></script>
<!-- Opus codec for audio decompression -->
<script src="https://cdn.jsdelivr.net/npm/opus.js@0.5.0/dist/opus.js"></script>
<!-- Opus WASM decoder (WebAudio fallback for browsers without MSE+WebM/Opus,
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>
let ws = null, token = null, decoder = null, devices = [], currentDevice = null;
@@ -1387,11 +1390,11 @@
// channels:2 sampleRate:4 bitsPerSample:2 blockAlign:2
// compression:1 (0=PCM unsupported by web, 1=Opus) reserved:1
// MSE + WebM/Opus playback. Raw Opus packets arrive over WS; we wrap
// each one in a minimal WebM container in JS and feed it to a
// SourceBuffer attached to a hidden <audio> element. The browser
// decodes Opus natively. Tested on desktop Chrome; mobile playback
// is a known follow-up (see commit notes).
// MSE + WebM/Opus playback (desktop Chrome/Firefox/Android). Raw Opus
// packets arrive over WS; we wrap each one in a minimal WebM container
// and feed it to a SourceBuffer attached to a hidden <audio> element.
// Browsers without MSE+WebM/Opus (iOS Safari/Chrome — WebKit has no
// WebM/Opus in MediaSource) fall back to the WebAudio + WASM path below.
let audioFormat = null; // { compression, channels, sampleRate, bitsPerSample, blockAlign }
let audioEnabled = true; // Audio on/off flag (set by UI)
let syncDrift = 0; // A/V sync monitoring (milliseconds)
@@ -1405,13 +1408,35 @@
const OPUS_FRAME_MS = 20; // 960 samples @ 48k — matches client encoder
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
// and .play()'d synchronously inside a user-gesture event handler.
// We hook the first click/keydown to spin up the element + MediaSource.
// Subsequent activity (e.g. tab regaining focus) re-issues play().
function installAudioGestureUnlock() {
const onGesture = () => {
if (!_audioElement) {
if (_useWebAudioPath) {
_setupWebAudioContext();
} else if (!_audioElement) {
try {
_setupAudioElementAndMediaSource();
console.log('[MSE] <audio> + MediaSource set up by gesture');
@@ -1423,11 +1448,137 @@
}
};
const opts = { passive: true, capture: true };
window.addEventListener('click', onGesture, opts);
window.addEventListener('keydown', onGesture, opts);
// click/keydown cover desktop; touchend/pointerdown are the most
// 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();
// ============================================================
// 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
let currentPage = 1;
let viewMode = 'grid'; // 'grid' or 'list'
@@ -1524,7 +1675,7 @@
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
updateWsStatus('connecting');
try {
ws = new WebSocket(protocol + '//' + location.host + '/ws');
ws = new WebSocket(protocol + '//' + location.host + '__BASE_PATH__/ws');
} catch (e) {
updateWsStatus('disconnected');
scheduleReconnect();
@@ -2003,6 +2154,10 @@
function pushOpusPacket(opusBytes) {
if (!audioFormat || audioFormat.compression !== 1) return;
if (_useWebAudioPath) {
_pushOpusWebAudio(opusBytes);
return;
}
if (_sourceBuffer && _initSegmentSent) {
_enqueueAppend(WebMMuxer.buildCluster(opusBytes, _opusTimestampMs));
_opusTimestampMs += OPUS_FRAME_MS;
@@ -2030,6 +2185,12 @@
_initSegmentSent = false;
_opusTimestampMs = 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) {
@@ -2073,10 +2234,12 @@
return;
}
stopAllAudio();
if (_mediaSource && _mediaSource.readyState === 'open') {
if (_useWebAudioPath) {
_initWebAudioDecoder(fmt);
} else if (_mediaSource && _mediaSource.readyState === 'open') {
_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 +
' sr=' + fmt.sampleRate + ' compression=' + fmt.compression);
}
@@ -3079,7 +3242,11 @@
// authoritative — toggleAudio() does not flip state locally; it only
// sends the request and waits for the audio_state broadcast.
function applyAudioState(enabled) {
const prev = audioEnabled;
audioEnabled = !!enabled;
if (prev !== audioEnabled) {
console.log('[Audio] ' + (audioEnabled ? 'enabled' : 'disabled'));
}
const btn = document.getElementById('btn-audio-bar');
if (btn) {
// 0x1F50A speaker / 0x1F507 muted speaker