#include "AdbController.h" #include "Assistant.h" #include "Controller.h" #include "MaaUtils/NoWarningCV.hpp" #include #include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4068) #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif #include "Common/AsstTypes.h" #include "Config/GeneralConfig.h" #include "Utils/Logger.hpp" #include "Utils/Platform.hpp" #include "Utils/StringMisc.hpp" #include asst::AdbController::AdbController(const AsstCallback& callback, Assistant* inst, PlatformType type) : InstHelper(inst), m_callback(callback) { LogTraceFunction; m_platform_io = PlatformFactory::create_platform(inst, type); if (!m_platform_io) { Log.error("platform not supported"); throw std::runtime_error("platform not supported"); } m_support_socket = m_platform_io->m_support_socket; if (!m_support_socket) { Log.error("socket not supported"); } } asst::AdbController::~AdbController() { LogTraceFunction; m_inited = false; release(); } std::optional asst::AdbController::reconnect(const std::string& cmd, int64_t timeout, bool recv_by_socket) { LogTraceFunction; json::value reconnect_info = json::object { { "uuid", m_uuid }, { "what", "Reconnecting" }, { "why", "" }, { "details", json::object { { "reconnect", m_adb.connect }, { "cmd", cmd }, } }, }; static constexpr int ReconnectTimes = 5; for (int i = 0; i < ReconnectTimes; ++i) { if (need_exit()) { break; } reconnect_info["details"]["times"] = i; callback(AsstMsg::ConnectionInfo, reconnect_info); sleep(10 * 1000); if (need_exit()) { break; } auto reconnect_ret = call_command(m_adb.connect, 60LL * 1000, false /* 禁止重连避免无限递归 */); if (need_exit()) { break; } bool is_reconnect_success = false; if (reconnect_ret) { auto& reconnect_str = reconnect_ret.value(); is_reconnect_success = reconnect_str.find("error") == std::string::npos; } if (is_reconnect_success) { auto recall_ret = call_command(cmd, timeout, false /* 禁止重连避免无限递归 */, recv_by_socket); if (recall_ret) { // 重连并成功执行了 reconnect_info["what"] = "Reconnected"; callback(AsstMsg::ConnectionInfo, reconnect_info); return recall_ret; } } } json::value info = json::object { { "uuid", m_uuid }, { "what", "Disconnect" }, { "why", "Reconnect failed" }, { "details", json::object { { "cmd", m_adb.connect }, } }, }; m_inited = false; // 重连失败,释放 release(); callback(AsstMsg::ConnectionInfo, info); return std::nullopt; } std::optional asst::AdbController::call_command( const std::string& cmd, int64_t timeout, bool allow_reconnect, bool recv_by_socket) { using namespace std::chrono_literals; using namespace std::chrono; // LogTraceScope(std::string(__FUNCTION__) + " | `" + cmd + "`"); std::string pipe_data; std::string sock_data; asst::platform::single_page_buffer pipe_buffer; asst::platform::single_page_buffer sock_buffer; auto start_time = steady_clock::now(); std::unique_lock callcmd_lock(m_callcmd_mutex); std::optional exit_res; exit_res = m_platform_io->call_command(cmd, recv_by_socket, pipe_data, sock_data, timeout, start_time); if (!exit_res) { Log.warn("Call `", cmd, "` failed"); return std::nullopt; } const int exit_ret = exit_res.value(); callcmd_lock.unlock(); m_last_command_duration = duration_cast(steady_clock::now() - start_time).count(); Log.info( "Call `", cmd, "` ret", exit_ret, ", cost", m_last_command_duration, "ms , stdout size:", pipe_data.size(), ", socket size:", sock_data.size()); if (!pipe_data.empty() && pipe_data.size() < 4096) { m_pipe_data_size = pipe_data.size(); Log.trace("stdout output:", Logger::separator::newline, pipe_data); } if (recv_by_socket && !sock_data.empty() && sock_data.size() < 4096) { Log.trace("socket output:", Logger::separator::newline, sock_data); } // 直接 return,避免走到下面的 else if 里的 m_inited = false) 关闭 adb 连接, // 导致停止后再开始任务还需要重连一次 if (need_exit()) { return std::nullopt; } if (!exit_ret) { return recv_by_socket ? sock_data : pipe_data; } else if (inited() && allow_reconnect) { // 之前可以运行,突然运行不了了,这种情况多半是 adb 炸了。所以重新连接一下 return reconnect(cmd, timeout, recv_by_socket); } return std::nullopt; } size_t asst::AdbController::get_pipe_data_size() const noexcept { return m_pipe_data_size; } size_t asst::AdbController::get_version() const noexcept { return m_version; } void asst::AdbController::callback(AsstMsg msg, const json::value& details) { if (m_callback) { m_callback(msg, details, m_inst); } } int asst::AdbController::get_mumu_index(const std::string& address) { LogTrace << VAR(address); auto pos = address.find(":"); if (pos == std::string::npos) { Log.error("address is invalid", address); return 0; } std::string port_str = address.substr(pos + 1); if (port_str.empty() || !std::ranges::all_of(port_str, [](const char& c) -> bool { return std::isdigit(c); })) { Log.error("port is invalid", port_str); return 0; } int port = std::stoi(port_str); int mumu_index = 0; if (port >= 16384) { mumu_index = (port - 16384) / 32; } else if (port == 7555) { mumu_index = 0; LogWarn << "Port 7555 is deprecated for MuMu6, please use 16384 or above."; } else if (port >= 5555) { mumu_index = (port - 5555) / 2; } LogInfo << VAR(port_str) << VAR(port) << VAR(mumu_index); return mumu_index; } void asst::AdbController::init_mumu_extras(const AdbCfg& adb_cfg, const std::string& address) { #if !ASST_WITH_EMULATOR_EXTRAS std::ignore = adb_cfg; std::ignore = address; Log.error("MaaCore is not compiled with ASST_WITH_EMULATOR_EXTRAS"); #else if (adb_cfg.extras.empty()) { LogWarn << "adb_cfg.extras is empty"; return; } set_mumu_package(adb_cfg.extras.get("client_type", "")); std::filesystem::path mumu_path = utils::path(adb_cfg.extras.get("path", "")); if (adb_cfg.extras.contains("index")) { // MuMu index is provided directly m_mumu_extras.init(mumu_path, adb_cfg.extras.get("index", 0)); } else { m_mumu_extras.init(mumu_path, get_mumu_index(address)); } #endif } void asst::AdbController::set_mumu_package(const std::string& client_type) { #if !ASST_WITH_EMULATOR_EXTRAS std::ignore = client_type; Log.error("MaaCore is not compiled with ASST_WITH_EMULATOR_EXTRAS"); #else std::string package_name = Config.get_package_name(client_type).value_or(""); m_mumu_extras.set_package_name(package_name); #endif } int asst::AdbController::get_ld_index(const std::string& address) { LogTrace << VAR(address); // emulator-5554 if (address.starts_with("emulator-")) { constexpr int base_emulator_port = 5554; std::string port_str = address.substr(9); // after "emulator-" if (port_str.empty() || !std::ranges::all_of(port_str, [](char c) { return std::isdigit(c); })) { Log.error("emulator port is invalid", port_str); return 0; } int port = std::stoi(port_str); int index = (port - base_emulator_port) / 2; LogInfo << VAR(port_str) << VAR(port) << VAR(index); return index; } // 127.0.0.1:5555 auto pos = address.find(':'); if (pos != std::string::npos && address.substr(0, pos) == "127.0.0.1") { constexpr int base_adb_port = 5555; std::string port_str = address.substr(pos + 1); if (port_str.empty() || !std::ranges::all_of(port_str, [](char c) { return std::isdigit(c); })) { Log.error("adb port is invalid", port_str); return 0; } int port = std::stoi(port_str); int index = (port - base_adb_port) / 2; LogInfo << VAR(port_str) << VAR(port) << VAR(index); return index; } Log.error("address is invalid or unsupported", address); return 0; } void asst::AdbController::init_ld_extras(const AdbCfg& adb_cfg, const std::string& address) { #if !ASST_WITH_EMULATOR_EXTRAS std::ignore = adb_cfg; std::ignore = address; Log.error("MaaCore is not compiled with ASST_WITH_EMULATOR_EXTRAS"); #else if (adb_cfg.extras.empty()) { LogWarn << "adb_cfg.extras is empty"; return; } std::filesystem::path ld_path = utils::path(adb_cfg.extras.get("path", "")); int ld_index; if (adb_cfg.extras.contains("index")) { ld_index = adb_cfg.extras.get("index", 0); } else { ld_index = get_ld_index(address); } int ld_pid = adb_cfg.extras.get("pid", 0); m_ld_extras.init(ld_path, ld_index, ld_pid, m_width, m_height); #endif } void asst::AdbController::close_socket() noexcept { m_platform_io->close_socket(); } std::optional asst::AdbController::init_socket(const std::string& local_address) { return m_platform_io->init_socket(local_address); } void asst::AdbController::clear_info() noexcept { m_inited = false; m_adb = decltype(m_adb)(); m_uuid.clear(); m_width = 0; m_height = 0; m_screen_size = { 0, 0 }; } bool asst::AdbController::inited() const noexcept { return m_inited; } bool asst::AdbController::start_game(const std::string& client_type) { if (client_type.empty()) { return false; } auto package_name = Config.get_package_name(client_type); if (!package_name) { return false; } std::string cur_cmd = utils::string_replace_all(m_adb.start, "[PackageName]", package_name.value()); bool ret = call_command(cur_cmd).has_value(); return ret; } bool asst::AdbController::stop_game(const std::string& client_type) { if (client_type.empty()) { return false; } auto package_name = Config.get_package_name(client_type); if (!package_name) { return false; } std::string cur_cmd = utils::string_replace_all(m_adb.stop, "[PackageName]", package_name.value()); bool ret = call_command(cur_cmd).has_value(); return ret; } bool asst::AdbController::click(const Point& p) { if (p.x < 0 || p.x >= m_width || p.y < 0 || p.y >= m_height) { Log.error("click point out of range"); } std::string cur_cmd = utils::string_replace_all(m_adb.click, { { "[x]", std::to_string(p.x) }, { "[y]", std::to_string(p.y) } }); return call_command(cur_cmd).has_value(); } bool asst::AdbController::input(const std::string& text) { if (text == "") { Log.error("empty text"); } std::string cur_cmd = utils::string_replace_all(m_adb.input, { { "[text]", text } }); return call_command(cur_cmd).has_value(); } bool asst::AdbController::swipe( const Point& p1, const Point& p2, int duration, bool extra_swipe, [[maybe_unused]] double slope_in, [[maybe_unused]] double slope_out, [[maybe_unused]] bool with_pause) { int x1 = p1.x, y1 = p1.y; int x2 = p2.x, y2 = p2.y; // 起点不能在屏幕外,但是终点可以 if (x1 < 0 || x1 >= m_width || y1 < 0 || y1 >= m_height) { Log.warn("swipe point1 is out of range", x1, y1); x1 = std::clamp(x1, 0, m_width - 1); y1 = std::clamp(y1, 0, m_height - 1); } const auto& opt = Config.get_options(); std::string duration_str = duration <= 0 ? "" : std::to_string(static_cast(duration * opt.adb_swipe_duration_multiplier)); std::string cur_cmd = utils::string_replace_all( m_adb.swipe, { { "[x1]", std::to_string(x1) }, { "[y1]", std::to_string(y1) }, { "[x2]", std::to_string(x2) }, { "[y2]", std::to_string(y2) }, { "[duration]", duration_str }, }); bool ret = call_command(cur_cmd).has_value(); // 额外的滑动:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 if (extra_swipe && opt.adb_extra_swipe_duration > 0) { std::string extra_cmd = utils::string_replace_all( m_adb.swipe, { { "[x1]", std::to_string(x2) }, { "[y1]", std::to_string(y2) }, { "[x2]", std::to_string(x2) }, { "[y2]", std::to_string(y2 - opt.adb_extra_swipe_dist /* * m_control_scale*/) }, { "[duration]", std::to_string(opt.adb_extra_swipe_duration) }, }); ret &= call_command(extra_cmd).has_value(); } return ret; } bool asst::AdbController::press_esc() { LogTraceFunction; return call_command(m_adb.press_esc).has_value(); } std::pair asst::AdbController::get_screen_res() const noexcept { return m_screen_size; } void asst::AdbController::release() { close_socket(); if (m_kill_adb_on_exit && !m_adb.release.empty()) { m_platform_io->release_adb(m_adb.release, 20000); } } const std::string& asst::AdbController::get_uuid() const { return m_uuid; } bool asst::AdbController::convert_lf(std::string& data) { if (data.empty() || data.size() < 2) { return false; } auto pred = [](const std::string::iterator& cur) -> bool { return *cur == '\r' && *(cur + 1) == '\n'; }; // find the first of "\r\n" auto first_iter = data.end(); for (auto iter = data.begin(); iter != data.end() - 1; ++iter) { if (pred(iter)) { first_iter = iter; break; } } if (first_iter == data.end()) { return false; } // move forward all non-crlf elements auto end_r1_iter = data.end() - 1; auto next_iter = first_iter; while (++first_iter != end_r1_iter) { if (!pred(first_iter)) { *next_iter = *first_iter; ++next_iter; } } *next_iter = *end_r1_iter; ++next_iter; data.erase(next_iter, data.end()); return true; } bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect) { using namespace std::chrono; DecodeFunc decode_raw = [&](const std::string& data) -> bool { if (data.size() < 8) { return false; } // assuming little endian uint32_t w = static_cast(static_cast(data[0])) << 0 | static_cast(static_cast(data[1])) << 8 | static_cast(static_cast(data[2])) << 16 | static_cast(static_cast(data[3])) << 24; uint32_t h = static_cast(static_cast(data[4])) << 0 | static_cast(static_cast(data[5])) << 8 | static_cast(static_cast(data[6])) << 16 | static_cast(static_cast(data[7])) << 24; if (int(w) != m_width || int(h) != m_height) { Log.error("Size from image header", w, h, "does not match the size of screen", m_width, m_height); return false; } size_t std_size = 4ULL * m_width * m_height; if (data.size() < std_size) { return false; } const size_t header_size = data.size() - std_size; // 12 or 16. ref: // https://android.googlesource.com/platform/frameworks/base/+/26a2b97dbe48ee45e9ae70110714048f2f360f97%5E%21/cmds/screencap/screencap.cpp auto img_data_beg = data.cbegin() + header_size; cv::Mat temp(m_height, m_width, CV_8UC4, const_cast(&*img_data_beg)); if (temp.empty()) { return false; } const auto& br = *(temp.end() - 1); if (br[3] != 255) { // only check alpha return false; } cv::cvtColor(temp, temp, cv::COLOR_RGBA2BGR); image_payload = temp; return true; }; DecodeFunc decode_raw_with_gzip = [&](const std::string& data) -> bool { const std::string raw_data = gzip::decompress(data.data(), data.size()); return decode_raw(raw_data); }; DecodeFunc decode_encode = [&](const std::string& data) -> bool { cv::Mat temp = cv::imdecode({ data.data(), int(data.size()) }, cv::IMREAD_COLOR); if (temp.empty()) { return false; } image_payload = temp; return true; }; image_payload = cv::Mat(); // 清空缓存 if (m_adb.screencap_method == AdbProperty::ScreencapMethod::UnknownYet) { std::vector> all_methods_cost; Log.info("Try to find the fastest way to screencap"); auto min_cost = milliseconds(LLONG_MAX); clear_lf_info(); auto start_time = steady_clock::now(); if (m_support_socket && m_server_started && screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true, 5000)) { auto duration = duration_cast(steady_clock::now() - start_time); if (duration < min_cost) { m_adb.screencap_method = AdbProperty::ScreencapMethod::RawByNc; m_inited = true; min_cost = duration; } Log.info("RawByNc cost", duration.count(), "ms"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::RawByNc, std::to_string(duration.count())); } else { Log.info("RawByNc is not supported"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::RawByNc, "???"); } clear_lf_info(); start_time = steady_clock::now(); if (screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect)) { auto duration = duration_cast(steady_clock::now() - start_time); if (duration < min_cost) { m_adb.screencap_method = AdbProperty::ScreencapMethod::RawWithGzip; m_inited = true; min_cost = duration; } Log.info("RawWithGzip cost", duration.count(), "ms"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::RawWithGzip, std::to_string(duration.count())); } else { Log.info("RawWithGzip is not supported"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::RawWithGzip, "???"); } clear_lf_info(); start_time = steady_clock::now(); if (screencap(m_adb.screencap_encode, decode_encode, allow_reconnect)) { auto duration = duration_cast(steady_clock::now() - start_time); if (duration < min_cost) { m_adb.screencap_method = AdbProperty::ScreencapMethod::Encode; m_inited = true; min_cost = duration; } Log.info("Encode cost", duration.count(), "ms"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::Encode, std::to_string(duration.count())); } else { Log.info("Encode is not supported"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::Encode, "???"); } #if ASST_WITH_EMULATOR_EXTRAS if (m_mumu_extras.inited()) { start_time = steady_clock::now(); if (m_mumu_extras.screencap()) { auto duration = duration_cast(steady_clock::now() - start_time); if (duration < min_cost) { m_adb.screencap_method = AdbProperty::ScreencapMethod::MumuExtras; m_inited = true; min_cost = duration; } Log.info("MumuExtras cost", duration.count(), "ms"); all_methods_cost.emplace_back( AdbProperty::ScreencapMethod::MumuExtras, std::to_string(duration.count())); } else { Log.info("MumuExtras is not supported"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::MumuExtras, "???"); } } if (m_ld_extras.inited()) { start_time = steady_clock::now(); if (m_ld_extras.screencap()) { auto duration = duration_cast(steady_clock::now() - start_time); if (duration < min_cost) { m_adb.screencap_method = AdbProperty::ScreencapMethod::LDExtras; m_inited = true; min_cost = duration; } Log.info("LDExtras cost", duration.count(), "ms"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::LDExtras, std::to_string(duration.count())); } else { Log.info("LDExtras is not supported"); all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::LDExtras, "???"); } } #endif static const std::unordered_map MethodName = { { AdbProperty::ScreencapMethod::UnknownYet, "UnknownYet" }, { AdbProperty::ScreencapMethod::RawByNc, "RawByNc" }, { AdbProperty::ScreencapMethod::RawWithGzip, "RawWithGzip" }, { AdbProperty::ScreencapMethod::Encode, "Encode" }, #if ASST_WITH_EMULATOR_EXTRAS { AdbProperty::ScreencapMethod::MumuExtras, "MumuExtras" }, { AdbProperty::ScreencapMethod::LDExtras, "LDExtras" }, #endif }; Log.info("The fastest way is", MethodName.at(m_adb.screencap_method), ", cost:", min_cost.count(), "ms"); if (m_adb.screencap_method != AdbProperty::ScreencapMethod::UnknownYet) { json::value info = json::object { { "uuid", m_uuid }, { "what", "FastestWayToScreencap" }, { "details", json::object { { "method", MethodName.at(m_adb.screencap_method) }, { "cost", min_cost.count() }, } }, }; json::array alt; for (auto& [method, cost] : all_methods_cost) { alt.push_back(json::object { { "method", MethodName.at(method) }, { "cost", cost } }); } auto details_obj = info.at("details").as_object(); details_obj["alternatives"] = std::move(alt); info.as_object()["details"] = std::move(details_obj); callback(AsstMsg::ConnectionInfo, info); } clear_lf_info(); return m_adb.screencap_method != AdbProperty::ScreencapMethod::UnknownYet; } else { auto start_time = high_resolution_clock::now(); bool screencap_ret = false; switch (m_adb.screencap_method) { case AdbProperty::ScreencapMethod::RawByNc: screencap_ret = screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true); break; case AdbProperty::ScreencapMethod::RawWithGzip: screencap_ret = screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect); break; case AdbProperty::ScreencapMethod::Encode: screencap_ret = screencap(m_adb.screencap_encode, decode_encode, allow_reconnect); break; #if ASST_WITH_EMULATOR_EXTRAS case AdbProperty::ScreencapMethod::MumuExtras: { auto img_opt = m_mumu_extras.screencap(); screencap_ret = img_opt.has_value(); if (!screencap_ret && allow_reconnect) { m_mumu_extras.reload(); img_opt = m_mumu_extras.screencap(); screencap_ret = img_opt.has_value(); } if (screencap_ret) { image_payload = img_opt.value(); } } break; case AdbProperty::ScreencapMethod::LDExtras: { auto img_opt = m_ld_extras.screencap(); screencap_ret = img_opt.has_value(); if (!screencap_ret && allow_reconnect) { m_ld_extras.reload(); img_opt = m_ld_extras.screencap(); screencap_ret = img_opt.has_value(); } if (screencap_ret) { image_payload = img_opt.value(); } } break; #endif default: break; } auto duration = duration_cast(high_resolution_clock::now() - start_time); // 记录截图耗时,每10次截图回传一次最值+平均值 m_screencap_cost.emplace_back(screencap_ret ? duration.count() : -1); // 记录截图耗时 ++m_screencap_times; if (m_screencap_cost.size() > 30) { m_screencap_cost.pop_front(); } if (m_screencap_times > 9) { // 每 10 次截图计算一次平均耗时 m_screencap_times = 0; auto filtered_cost = m_screencap_cost | std::views::filter([](auto num) { return num > 0; }); if (filtered_cost.empty()) { return screencap_ret; } // 过滤后的有效截图用时次数 auto filtered_count = m_screencap_cost.size() - std::ranges::count(m_screencap_cost, -1); auto [screencap_cost_min, screencap_cost_max] = std::ranges::minmax(filtered_cost); json::value info = json::object { { "uuid", m_uuid }, { "what", "ScreencapCost" }, { "details", json::object { { "min", screencap_cost_min }, { "max", screencap_cost_max }, { "avg", filtered_count > 0 ? std::accumulate(filtered_cost.begin(), filtered_cost.end(), 0ll) / filtered_count : -1 }, } }, }; if (m_screencap_cost.size() > filtered_count) { info["details"]["fault_times"] = m_screencap_cost.size() - filtered_count; } callback(AsstMsg::ConnectionInfo, info); } return screencap_ret; } } bool asst::AdbController::screencap( const std::string& cmd, const DecodeFunc& decode_func, bool allow_reconnect, bool by_socket, int timeout) { if ((!m_support_socket || !m_server_started) && by_socket) [[unlikely]] { return false; } auto ret = call_command(cmd, timeout, allow_reconnect, by_socket); if (!ret || ret.value().empty()) [[unlikely]] { Log.warn("data is empty!"); return false; } auto& data = ret.value(); bool tried_conversion = false; if (m_adb.screencap_end_of_line == AdbProperty::ScreencapEndOfLine::CRLF) { tried_conversion = true; if (!convert_lf(data)) [[unlikely]] { // 没找到 "\r\n" Log.info("screencap_end_of_line is set to CRLF but no `\\r\\n` found, set it to LF"); m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::LF; } } if (decode_func(data)) [[likely]] { if (m_adb.screencap_end_of_line == AdbProperty::ScreencapEndOfLine::UnknownYet) [[unlikely]] { Log.info("screencap_end_of_line is LF"); m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::LF; } } else { Log.info("data is not empty, but image is empty"); if (tried_conversion) { // 已经转换过行尾,再次转换 data 不会变化,不必重试 Log.error("skip retry decoding and decode failed!"); return false; } Log.info("try to cvt lf"); if (!convert_lf(data)) { // 没找到 "\r\n",data 没有变化,不必重试 Log.error("no `\\r\\n` found, skip retry decode"); return false; } if (!decode_func(data)) { Log.error("convert lf and retry decode failed!"); return false; } if (m_adb.screencap_end_of_line == AdbProperty::ScreencapEndOfLine::UnknownYet) { Log.info("screencap_end_of_line is CRLF"); } else { Log.info("screencap_end_of_line is changed to CRLF"); } m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::CRLF; } return true; } bool asst::AdbController::connect(const std::string& adb_path, const std::string& address, const std::string& config) { LogTraceFunction; clear_info(); #ifdef ASST_DEBUG if (config == "DEBUG") { m_inited = true; return true; } #endif auto get_info_json = [&]() -> json::value { return json::object { { "uuid", m_uuid }, { "details", json::object { { "adb", adb_path }, { "address", address }, { "config", config }, } }, }; }; std::string client_type = ctrler() ? ctrler()->get_client_type() : ""; m_conn_ctx.reset(adb_path, address, client_type); auto adb_ret = Config.get_adb_cfg(config); if (!adb_ret) { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "ConfigNotFound" }, }; callback(AsstMsg::ConnectionInfo, info); #ifdef ASST_DEBUG return false; #else Log.error("config ", config, "not found"); adb_ret = Config.get_adb_cfg("General"); #endif } m_conn_ctx.adb_cfg = adb_ret.value(); const auto& adb_cfg = m_conn_ctx.adb_cfg; if (need_exit()) { return false; } /* connect */ { // 先用 devices 读取输出 m_adb.devices = m_conn_ctx.replace_cmd(adb_cfg.devices); m_adb.address_regex = m_conn_ctx.replace_cmd(adb_cfg.address_regex); auto devices_ret = call_command(m_adb.devices, 60LL * 1000, false); bool need_connect = true; if (devices_ret) { const auto& devices_str = devices_ret.value(); const boost::regex address_regex(m_adb.address_regex); for (boost::sregex_iterator iter(devices_str.begin(), devices_str.end(), address_regex), end; iter != end; ++iter) { if (iter->size() > 1 && iter->str(1) == address) { need_connect = false; break; } } } // 设置配置 connect、release 命令,即使这里不连接,后续也会需要用到 m_adb.connect = m_conn_ctx.replace_cmd(adb_cfg.connect); m_adb.release = m_conn_ctx.replace_cmd(adb_cfg.release); m_platform_io->set_adb_serial(address); if (need_connect) { // 如果不包含 `:` 且需要连接,connect 命令也不会成功 if (address.find(':') == std::string::npos) { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "Cannot connect: address appears to be serial number but device not found" }, }; callback(AsstMsg::ConnectionInfo, info); return false; } auto connect_ret = call_command(m_adb.connect, 60LL * 1000, false /* adb 连接时不允许重试 */); if (connect_ret) { auto& connect_str = connect_ret.value(); // 检查连接字符串是否包含 "connected" if (connect_str.find("connected") == std::string::npos) { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "Connection command did not report \"connected\"" }, }; callback(AsstMsg::ConnectionInfo, info); return false; } } else { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "Connection command failed to exec" }, }; callback(AsstMsg::ConnectionInfo, info); return false; } } } if (need_exit()) { return false; } /* get uuid (imei) */ { auto uuid_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.uuid), 20000, false /* adb 连接时不允许重试 */); if (!uuid_ret) { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "Uuid command failed to exec" }, }; callback(AsstMsg::ConnectionInfo, info); return false; } std::string raw_output = std::move(uuid_ret.value()); std::string uuid_str = raw_output; std::erase_if(uuid_str, [](char c) { return !std::isdigit(c) && !std::isalpha(c); }); boost::regex hex_regex("^[0-9a-fA-F]{8,}$"); if (uuid_str.empty() || !boost::regex_match(uuid_str, hex_regex)) { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "Uuid invalid or shell command not available" }, }; info["details"]["raw_output"] = std::move(raw_output); callback(AsstMsg::ConnectionInfo, info); return false; } m_uuid = std::move(uuid_str); json::value info = get_info_json() | json::object { { "what", "UuidGot" }, { "why", "" }, }; info["details"]["uuid"] = m_uuid; callback(AsstMsg::ConnectionInfo, info); } /* get android version */ { auto version_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.version)); if (!version_ret) { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "Android version command failed to exec" }, }; callback(AsstMsg::ConnectionInfo, info); return false; } std::string raw_output = version_ret.value(); convert_lf(raw_output); std::erase_if(raw_output, [](char c) { return !std::isalnum(static_cast(c)); }); bool all_digit = !raw_output.empty() && std::ranges::all_of(raw_output, [](char c) { return std::isdigit(static_cast(c)); }); if (!all_digit) { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "Android version invalid or shell command not available" }, }; info["details"]["raw_output"] = std::move(version_ret.value()); callback(AsstMsg::ConnectionInfo, info); return false; } m_version = std::stoul(raw_output); } if (need_exit()) { return false; } // 按需获取display ID 信息 if (!adb_cfg.display_id.empty()) { // [PackageName] 不参与 replace_cmd 的统一替换(保留为运行时参数)。 // 只有 connect 阶段命令确实依赖包名的配置,才需要在连接前调用 // AsstSetInstanceOption(ClientType, ...)。当前内置配置仅 Androws / WSA 的 // displayId 查询走这里,因此其他连接配置不应被迫传入 client type。 auto display_id_cmd = utils::string_replace_all( m_conn_ctx.replace_cmd(adb_cfg.display_id), "[PackageName]", m_conn_ctx.package_name); auto display_id_ret = call_command(display_id_cmd); if (!display_id_ret) { return false; } auto& display_id_pipe_str = display_id_ret.value(); convert_lf(display_id_pipe_str); auto last = display_id_pipe_str.rfind(':'); if (last == std::string::npos) { return false; } m_conn_ctx.display_id = display_id_pipe_str.substr(last + 1); // 去掉换行 m_conn_ctx.display_id.pop_back(); } if (need_exit()) { return false; } // 按需获取 event ID 信息(用于 minitouch 的 /dev/input/eventN) if (!adb_cfg.event_id.empty()) { auto event_id_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.event_id)); if (!event_id_ret) { Log.warn("Failed to get event_id, skip"); } else { auto& event_id_pipe_str = event_id_ret.value(); convert_lf(event_id_pipe_str); // 去掉空白字符 std::erase_if(event_id_pipe_str, [](char c) { return !std::isdigit(c); }); if (event_id_pipe_str.empty()) { Log.warn("event_id is empty, skip"); } else { m_conn_ctx.event_id = event_id_pipe_str; Log.info("event_id:", m_conn_ctx.event_id); } } } if (need_exit()) { return false; } /* display */ { auto display_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.display)); if (!display_ret) { json::value info = get_info_json() | json::object { { "what", "ConnectFailed" }, { "why", "Display command failed to exec" }, }; callback(AsstMsg::ConnectionInfo, info); return false; } std::stringstream display_ss(display_ret.value()); int size_value1 = 0; int size_value2 = 0; display_ss >> size_value1 >> size_value2; m_width = (std::max)(size_value1, size_value2); m_height = (std::min)(size_value1, size_value2); json::value info = get_info_json() | json::object { { "what", "ResolutionGot" }, { "why", "" }, }; info["details"] |= json::object { { "width", m_width }, { "height", m_height }, }; callback(AsstMsg::ConnectionInfo, info); if (m_width == 0 || m_height == 0) { info["what"] = "ResolutionError"; info["why"] = "Get resolution failed"; callback(AsstMsg::ConnectionInfo, info); return false; } m_screen_size = { m_width, m_height }; } if (need_exit()) { return false; } { json::value info = get_info_json() | json::object { { "what", "Connected" }, { "why", "" }, }; callback(AsstMsg::ConnectionInfo, info); } m_adb.click = m_conn_ctx.replace_cmd(adb_cfg.click); m_adb.input = m_conn_ctx.replace_cmd(adb_cfg.input); m_adb.swipe = m_conn_ctx.replace_cmd(adb_cfg.swipe); m_adb.press_esc = m_conn_ctx.replace_cmd(adb_cfg.press_esc); m_adb.screencap_raw_with_gzip = m_conn_ctx.replace_cmd(adb_cfg.screencap_raw_with_gzip); m_adb.screencap_encode = m_conn_ctx.replace_cmd(adb_cfg.screencap_encode); m_adb.start = m_conn_ctx.replace_cmd(adb_cfg.start); m_adb.stop = m_conn_ctx.replace_cmd(adb_cfg.stop); m_adb.back_to_home = m_conn_ctx.replace_cmd(adb_cfg.back_to_home); if (m_support_socket && !m_server_started) { std::string bind_address; if (size_t pos = address.rfind(':'); pos != std::string::npos) { bind_address = address.substr(0, pos); } else { bind_address = "127.0.0.1"; } // reference from // https://github.com/ArknightsAutoHelper/ArknightsAutoHelper/blob/master/automator/connector/ADBConnector.py#L436 auto nc_address_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.nc_address)); if (nc_address_ret && !m_server_started) { auto& nc_result_str = nc_address_ret.value(); if (auto pos = nc_result_str.find(' '); pos != std::string::npos) { m_conn_ctx.nc_address = nc_result_str.substr(0, pos); } } auto socket_opt = init_socket(bind_address); if (socket_opt) { m_conn_ctx.nc_port = socket_opt.value(); m_adb.screencap_raw_by_nc = m_conn_ctx.replace_cmd(adb_cfg.screencap_raw_by_nc); m_server_started = true; } else { m_server_started = false; } } if (need_exit()) { return false; } if (config == "MuMuEmulator12") { init_mumu_extras(adb_cfg, address); } else if (config == "LDPlayer") { init_ld_extras(adb_cfg, address); } if (need_exit()) { return false; } return true; } void asst::AdbController::set_kill_adb_on_exit(bool enable) noexcept { m_kill_adb_on_exit = enable; } void asst::AdbController::clear_lf_info() { m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::UnknownYet; } void asst::AdbController::back_to_home() noexcept { call_command(m_adb.back_to_home); return; }