diff --git a/src/MaaCore/Assistant.cpp b/src/MaaCore/Assistant.cpp index 5a3e3f7eaa..df7043f31a 100644 --- a/src/MaaCore/Assistant.cpp +++ b/src/MaaCore/Assistant.cpp @@ -75,15 +75,15 @@ bool asst::Assistant::set_instance_option(InstanceOptionKey key, const std::stri switch (key) { case InstanceOptionKey::TouchMode: if (constexpr std::string_view Adb = "adb"; value == Adb) { - m_ctrler->set_minitouch_enabled(false); + m_ctrler->set_touch_mode(TouchMode::Adb); return true; } else if (constexpr std::string_view Minitouch = "minitouch"; value == Minitouch) { - m_ctrler->set_minitouch_enabled(true, false); + m_ctrler->set_touch_mode(TouchMode::Minitouch); return true; } else if (constexpr std::string_view MaaTouch = "maatouch"; value == MaaTouch) { - m_ctrler->set_minitouch_enabled(true, true); + m_ctrler->set_touch_mode(TouchMode::Maatouch); return true; } break; diff --git a/src/MaaCore/Common/AsstTypes.h b/src/MaaCore/Common/AsstTypes.h index 3076ab3145..481b7c14c9 100644 --- a/src/MaaCore/Common/AsstTypes.h +++ b/src/MaaCore/Common/AsstTypes.h @@ -43,6 +43,26 @@ namespace asst AdbLiteEnabled = 4, // 是否使用 AdbLite, "0" | "1" }; + enum class TouchMode + { + Adb = 0, + Minitouch = 1, + Maatouch = 2, + }; + + namespace ControlFeat + { + using Feat = int64_t; + constexpr Feat NONE = 0; + constexpr Feat SWIPE_WITH_PAUSE = 1 << 0; + constexpr Feat PRECISE_SWIPE = 1 << 1; + + inline bool support(Feat feat, Feat target) noexcept + { + return (feat & target) == target; + } + } + struct Point { Point() = default; diff --git a/src/MaaCore/Controller/AdbController.cpp b/src/MaaCore/Controller/AdbController.cpp new file mode 100644 index 0000000000..e731ad9c64 --- /dev/null +++ b/src/MaaCore/Controller/AdbController.cpp @@ -0,0 +1,752 @@ +#include "AdbController.h" + +#include "Assistant.h" +#include "Common/AsstConf.h" +#include "Utils/NoWarningCV.h" + +#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/StringMisc.hpp" + +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; + + make_instance_inited(false); + kill_adb_daemon(); +} + +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) { + return std::nullopt; + } + const int exit_ret = exit_res.value(); + + callcmd_lock.unlock(); + + auto duration = duration_cast(steady_clock::now() - start_time).count(); + Log.info("Call `", cmd, "` ret", exit_ret, ", cost", duration, "ms , stdout size:", pipe_data.size(), + ", socket size:", sock_data.size()); + if (!pipe_data.empty() && pipe_data.size() < 4096) { + 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 里的 make_instance_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 炸了。所以重新连接一下 + 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 }, + } }, + }; + make_instance_inited(false); // 重连失败,释放 + callback(AsstMsg::ConnectionInfo, info); + } + + return std::nullopt; +} + +void asst::AdbController::callback(AsstMsg msg, const json::value& details) +{ + if (m_callback) { + m_callback(msg, details, m_inst); + } +} + +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 +{ + make_instance_inited(false); + m_adb = decltype(m_adb)(); + m_uuid.clear(); + m_width = 0; + m_height = 0; + m_screen_size = { 0, 0 }; + m_screencap_data_general_size = 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 intent_name = Config.get_intent_name(client_type); + if (!intent_name) { + return false; + } + std::string cur_cmd = utils::string_replace_all(m_adb.start, "[Intent]", intent_name.value()); + return call_command(cur_cmd).has_value(); +} + +bool asst::AdbController::stop_game() +{ + return call_command(m_adb.stop).has_value(); +} + +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::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_adb.release.empty()) { + m_adb_release.clear(); + m_platform_io->release_adb(m_adb.release, 20000); + } +} + +void asst::AdbController::kill_adb_daemon() +{ + if (m_instance_count) return; + if (!m_adb_release.empty()) { + m_platform_io->release_adb(m_adb_release, 20000); + m_adb_release.clear(); + } +} + +void asst::AdbController::make_instance_inited(bool inited) +{ + Log.trace(__FUNCTION__, "|", inited, ", pre m_inited =", m_inited, ", pre m_instance_count =", m_instance_count); + + if (inited == m_inited) { + return; + } + m_inited = inited; + + if (inited) { + ++m_instance_count; + } + else { + // 所有实例全部释放,执行最终的 release 函数 + if (!--m_instance_count) { + release(); + } + } +} + +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) +{ + DecodeFunc decode_raw = [&](const std::string& data) -> bool { + if (data.empty()) { + return false; + } + size_t std_size = 4ULL * m_width * m_height; + if (data.size() < std_size) { + return false; + } + size_t header_size = data.size() - std_size; + auto img_data_beg = data.cbegin() + header_size; + if (std::all_of(data.cbegin(), img_data_beg, std::logical_not {})) { + return false; + } + cv::Mat temp(m_height, m_width, CV_8UC4, const_cast(&*img_data_beg)); + if (temp.empty()) { + return false; + } + cv::cvtColor(temp, temp, cv::COLOR_RGB2BGR); + 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; + }; + + switch (m_adb.screencap_method) { + case AdbProperty::ScreencapMethod::UnknownYet: { + using namespace std::chrono; + Log.info("Try to find the fastest way to screencap"); + auto min_cost = milliseconds(LLONG_MAX); + clear_lf_info(); + + auto start_time = high_resolution_clock::now(); + if (m_support_socket && m_server_started && + screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true)) { + auto duration = duration_cast(high_resolution_clock::now() - start_time); + if (duration < min_cost) { + m_adb.screencap_method = AdbProperty::ScreencapMethod::RawByNc; + make_instance_inited(true); + min_cost = duration; + } + Log.info("RawByNc cost", duration.count(), "ms"); + } + else { + Log.info("RawByNc is not supported"); + } + clear_lf_info(); + + start_time = high_resolution_clock::now(); + if (screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect)) { + auto duration = duration_cast(high_resolution_clock::now() - start_time); + if (duration < min_cost) { + m_adb.screencap_method = AdbProperty::ScreencapMethod::RawWithGzip; + make_instance_inited(true); + min_cost = duration; + } + Log.info("RawWithGzip cost", duration.count(), "ms"); + } + else { + Log.info("RawWithGzip is not supported"); + } + clear_lf_info(); + + start_time = high_resolution_clock::now(); + if (screencap(m_adb.screencap_encode, decode_encode, allow_reconnect)) { + auto duration = duration_cast(high_resolution_clock::now() - start_time); + if (duration < min_cost) { + m_adb.screencap_method = AdbProperty::ScreencapMethod::Encode; + make_instance_inited(true); + min_cost = duration; + } + Log.info("Encode cost", duration.count(), "ms"); + } + else { + Log.info("Encode is not supported"); + } + static const std::unordered_map MethodName = { + { AdbProperty::ScreencapMethod::UnknownYet, "UnknownYet" }, + { AdbProperty::ScreencapMethod::RawByNc, "RawByNc" }, + { AdbProperty::ScreencapMethod::RawWithGzip, "RawWithGzip" }, + { AdbProperty::ScreencapMethod::Encode, "Encode" }, + }; + Log.info("The fastest way is", MethodName.at(m_adb.screencap_method), ", cost:", min_cost.count(), "ms"); + clear_lf_info(); + return m_adb.screencap_method != AdbProperty::ScreencapMethod::UnknownYet; + } break; + case AdbProperty::ScreencapMethod::RawByNc: { + return screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true); + } break; + case AdbProperty::ScreencapMethod::RawWithGzip: { + return screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect); + } break; + case AdbProperty::ScreencapMethod::Encode: { + return screencap(m_adb.screencap_encode, decode_encode, allow_reconnect); + } break; + } + + return false; +} + +bool asst::AdbController::screencap(const std::string& cmd, const DecodeFunc& decode_func, bool allow_reconnect, + bool by_socket) +{ + if ((!m_support_socket || !m_server_started) && by_socket) [[unlikely]] { + return false; + } + + auto ret = call_command(cmd, 20000, allow_reconnect, by_socket); + + if (!ret || ret.value().empty()) [[unlikely]] { + Log.warn("data is too small!"); + } + auto& data = ret.value(); + if (m_screencap_data_general_size && data.size() < m_screencap_data_general_size * 0.1) { + Log.error("data is too small!"); + return false; + } + + 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; + } + m_screencap_data_general_size = data.size(); + 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") { + make_instance_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 }, + } }, + }; + }; + + 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); + return false; + } + + const auto& adb_cfg = adb_ret.value(); + std::string display_id; + std::string nc_address = "10.0.2.2"; + uint16_t nc_port = 0; + + // 里面的值每次执行命令后可能更新,所以要用 lambda 拿最新的 + auto cmd_replace = [&](const std::string& cfg_cmd) -> std::string { + return utils::string_replace_all(cfg_cmd, { + { "[Adb]", adb_path }, + { "[AdbSerial]", address }, + { "[DisplayId]", display_id }, + { "[NcPort]", std::to_string(nc_port) }, + { "[NcAddress]", nc_address }, + }); + }; + + if (need_exit()) { + return false; + } + + /* connect */ + { + m_adb.connect = cmd_replace(adb_cfg.connect); + auto connect_ret = call_command(m_adb.connect, 60LL * 1000, false /* adb 连接时不允许重试 */); + bool is_connect_success = false; + if (connect_ret) { + auto& connect_str = connect_ret.value(); + is_connect_success = connect_str.find("error") == std::string::npos; + if (connect_str.find("daemon started successfully") != std::string::npos && + connect_str.find("daemon still not running") == std::string::npos) { + m_adb_release = cmd_replace(adb_cfg.release); + } + } + if (!is_connect_success) { + 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(cmd_replace(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; + } + + auto& uuid_str = uuid_ret.value(); + std::erase_if(uuid_str, [](char c) { return !std::isdigit(c) && !std::isalpha(c); }); + 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); + } + + if (need_exit()) { + return false; + } + + // 按需获取display ID 信息 + if (!adb_cfg.display_id.empty()) { + auto display_id_ret = call_command(cmd_replace(adb_cfg.display_id)); + 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; + } + + display_id = display_id_pipe_str.substr(last + 1); + // 去掉换行 + display_id.pop_back(); + } + + if (need_exit()) { + return false; + } + + /* display */ + { + auto display_ret = call_command(cmd_replace(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 = cmd_replace(adb_cfg.click); + m_adb.swipe = cmd_replace(adb_cfg.swipe); + m_adb.press_esc = cmd_replace(adb_cfg.press_esc); + m_adb.screencap_raw_with_gzip = cmd_replace(adb_cfg.screencap_raw_with_gzip); + m_adb.screencap_encode = cmd_replace(adb_cfg.screencap_encode); + m_adb_release = m_adb.release = cmd_replace(adb_cfg.release); + m_adb.start = cmd_replace(adb_cfg.start); + m_adb.stop = cmd_replace(adb_cfg.stop); + + 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(cmd_replace(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) { + nc_address = nc_result_str.substr(0, pos); + } + } + + auto socket_opt = init_socket(bind_address); + if (socket_opt) { + nc_port = socket_opt.value(); + m_adb.screencap_raw_by_nc = cmd_replace(adb_cfg.screencap_raw_by_nc); + m_server_started = true; + } + else { + m_server_started = false; + } + } + + if (need_exit()) { + return false; + } + return true; +} + +void asst::AdbController::clear_lf_info() +{ + m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::UnknownYet; +} diff --git a/src/MaaCore/Controller/AdbController.h b/src/MaaCore/Controller/AdbController.h new file mode 100644 index 0000000000..0d543da2d1 --- /dev/null +++ b/src/MaaCore/Controller/AdbController.h @@ -0,0 +1,131 @@ +#pragma once + +#include "ControllerAPI.h" + +#include + +#include "Platform/PlatformFactory.h" + +#include "Common/AsstMsg.h" +#include "InstHelper.h" + +namespace asst +{ + class AdbController : public ControllerAPI, protected InstHelper + { + public: + AdbController(const AsstCallback& callback, Assistant* inst, PlatformType type); + AdbController(const AdbController&) = delete; + AdbController(AdbController&&) = delete; + virtual ~AdbController(); + + virtual bool connect(const std::string& adb_path, const std::string& address, + const std::string& config) override; + virtual bool inited() const noexcept override; + + virtual void set_swipe_with_pause([[maybe_unused]] bool enable) noexcept override {} + + virtual const std::string& get_uuid() const override; + + virtual bool screencap(cv::Mat& image_payload, bool allow_reconnect = false) override; + + virtual bool start_game(const std::string& client_type) override; + virtual bool stop_game() override; + + virtual bool click(const Point& p) override; + + virtual bool swipe(const Point& p1, const Point& p2, int duration = 0, bool extra_swipe = false, + double slope_in = 1, double slope_out = 1, bool with_pause = false) override; + + virtual bool inject_input_event([[maybe_unused]] const InputEvent& event) override { return false; } + + virtual bool press_esc() override; + virtual ControlFeat::Feat support_features() const noexcept override { return ControlFeat::NONE; } + + virtual std::pair get_screen_res() const noexcept override; + + AdbController& operator=(const AdbController&) = delete; + AdbController& operator=(AdbController&&) = delete; + + protected: + std::optional call_command(const std::string& cmd, int64_t timeout = 20000, + bool allow_reconnect = true, bool recv_by_socket = false); + + void release(); + void kill_adb_daemon(); + void make_instance_inited(bool inited); + + void close_socket() noexcept; + std::optional init_socket(const std::string& local_address); + + using DecodeFunc = std::function; + bool screencap(const std::string& cmd, const DecodeFunc& decode_func, bool allow_reconnect = false, + bool by_socket = false); + void clear_lf_info(); + + virtual void clear_info() noexcept; + void callback(AsstMsg msg, const json::value& details); + + // 转换 data 中的 CRLF 为 LF:有些模拟器自带的 adb,exec-out 输出的 \n 会被替换成 \r\n, + // 导致解码错误,所以这里转一下回来(点名批评 mumu 和雷电) + static bool convert_lf(std::string& data); + + AsstCallback m_callback; + + std::minstd_rand m_rand_engine; + + std::mutex m_callcmd_mutex; + + std::shared_ptr m_platform_io = nullptr; + + struct AdbProperty + { + /* command */ + std::string connect; + std::string call_minitouch; + std::string call_maatouch; + std::string click; + std::string swipe; + std::string press_esc; + + std::string screencap_raw_by_nc; + std::string screencap_raw_with_gzip; + std::string screencap_encode; + std::string release; + + std::string start; + std::string stop; + + /* properties */ + enum class ScreencapEndOfLine + { + UnknownYet, + CRLF, + LF, + CR + } screencap_end_of_line = ScreencapEndOfLine::UnknownYet; + + enum class ScreencapMethod + { + UnknownYet, + // Default, + RawByNc, + RawWithGzip, + Encode + } screencap_method = ScreencapMethod::UnknownYet; + } m_adb; + + std::string m_uuid; + inline static std::string m_adb_release; // 开了 adb daemon,但是没连上模拟器的时候, + // m_adb 并不会存下 release 的命令,但最后仍然需要一次释放。 + std::pair m_screen_size = { 0, 0 }; + int m_width = 0; + int m_height = 0; + bool m_support_socket = false; + bool m_server_started = false; + bool m_inited = false; + size_t m_screencap_data_general_size = 0; + + inline static int m_instance_count = 0; + }; +} // namespace asst diff --git a/src/MaaCore/Controller/ControlScaleProxy.cpp b/src/MaaCore/Controller/ControlScaleProxy.cpp new file mode 100644 index 0000000000..c134fbf374 --- /dev/null +++ b/src/MaaCore/Controller/ControlScaleProxy.cpp @@ -0,0 +1,132 @@ +#include "ControlScaleProxy.h" + +asst::ControlScaleProxy::ControlScaleProxy(std::shared_ptr controller, ProxyCallback proxy_callback) + : m_controller(controller), m_callback(proxy_callback), m_rand_engine(std::random_device {}()) +{ + auto screen_res = m_controller->get_screen_res(); + + int width = screen_res.first; + int height = screen_res.second; + + auto info = json::object { { + "details", + json::object { + { "width", width }, + { "height", height }, + }, + } }; + + if (width < WindowWidthDefault || height < WindowHeightDefault) { + info["what"] = "UnsupportedResolution"; + info["why"] = "Low screen resolution"; + callback(info); + throw std::runtime_error("Unsupported resolution"); + } + else if (std::fabs(static_cast(WindowWidthDefault) / static_cast(WindowHeightDefault) - + static_cast(width) / static_cast(height)) > 1e-7) { + info["what"] = "UnsupportedResolution"; + info["why"] = "Not 16:9"; + callback(info); + throw std::runtime_error("Unsupported resolution"); + } + + /* calc ratio */ + { + constexpr double DefaultRatio = + static_cast(WindowWidthDefault) / static_cast(WindowHeightDefault); + double cur_ratio = static_cast(width) / static_cast(height); + + if (cur_ratio >= DefaultRatio // 说明是宽屏或默认16:9,按照高度计算缩放 + || std::fabs(cur_ratio - DefaultRatio) < DoubleDiff) { + int scale_width = static_cast(cur_ratio * WindowHeightDefault); + m_scale_size = std::make_pair(scale_width, WindowHeightDefault); + m_control_scale = static_cast(height) / static_cast(WindowHeightDefault); + } + else { // 否则可能是偏正方形的屏幕,按宽度计算 + int scale_height = static_cast(WindowWidthDefault / cur_ratio); + m_scale_size = std::make_pair(WindowWidthDefault, scale_height); + m_control_scale = static_cast(width) / static_cast(WindowWidthDefault); + } + } +} + +bool asst::ControlScaleProxy::click(const Point& p) +{ + int x = static_cast(p.x * m_control_scale); + int y = static_cast(p.y * m_control_scale); + + return m_controller->click(Point(x, y)); +} + +bool asst::ControlScaleProxy::click(const Rect& rect) +{ + return click(rand_point_in_rect(rect)); +} + +bool asst::ControlScaleProxy::swipe(const Point& p1, const Point& p2, int duration, bool extra_swipe, double slope_in, + double slope_out, bool with_pause) +{ + int x1 = static_cast(p1.x * m_control_scale); + int y1 = static_cast(p1.y * m_control_scale); + int x2 = static_cast(p2.x * m_control_scale); + int y2 = static_cast(p2.y * m_control_scale); + + return m_controller->swipe(Point(x1, y1), Point(x2, y2), duration, extra_swipe, slope_in, slope_out, with_pause); +} + +bool asst::ControlScaleProxy::swipe(const Rect& r1, const Rect& r2, int duration, bool extra_swipe, double slope_in, + double slope_out, bool with_pause) +{ + return swipe(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, extra_swipe, slope_in, slope_out, + with_pause); +} + +bool asst::ControlScaleProxy::inject_input_event(InputEvent event) +{ + switch (event.type) { + case InputEvent::Type::TOUCH_DOWN: + case InputEvent::Type::TOUCH_MOVE: + event.point.x = static_cast(event.point.x * m_control_scale); + event.point.y = static_cast(event.point.y * m_control_scale); + break; + default: + break; + } + + return m_controller->inject_input_event(event); +} + +std::pair asst::ControlScaleProxy::get_scale_size() const noexcept +{ + return m_scale_size; +} + +asst::Point asst::ControlScaleProxy::rand_point_in_rect(const Rect& rect) +{ + int x = 0, y = 0; + if (rect.width == 0) { + x = rect.x; + } + else { + int x_rand = std::poisson_distribution(rect.width / 2.)(m_rand_engine); + + x = x_rand + rect.x; + } + + if (rect.height == 0) { + y = rect.y; + } + else { + int y_rand = std::poisson_distribution(rect.height / 2.)(m_rand_engine); + y = y_rand + rect.y; + } + + return { x, y }; +} + +void asst::ControlScaleProxy::callback(const json::object& details) +{ + if (m_callback) { + m_callback(details); + } +} diff --git a/src/MaaCore/Controller/ControlScaleProxy.h b/src/MaaCore/Controller/ControlScaleProxy.h new file mode 100644 index 0000000000..dd2f1e9281 --- /dev/null +++ b/src/MaaCore/Controller/ControlScaleProxy.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include "ControllerAPI.h" + +#include "Common/AsstMsg.h" + +namespace asst +{ + class ControlScaleProxy + { + public: + using ProxyCallback = std::function; + + public: + ControlScaleProxy(std::shared_ptr controller, ProxyCallback proxy_callback); + ~ControlScaleProxy() = default; + + ControlScaleProxy(const ControlScaleProxy&) = delete; + ControlScaleProxy(ControlScaleProxy&&) = delete; + + bool click(const Point& p); + bool click(const Rect& rect); + + bool swipe(const Point& p1, const Point& p2, int duration = 0, bool extra_swipe = false, double slope_in = 1, + double slope_out = 1, bool with_pause = false); + bool swipe(const Rect& r1, const Rect& r2, int duration = 0, bool extra_swipe = false, double slope_in = 1, + double slope_out = 1, bool with_pause = false); + + bool inject_input_event(InputEvent event); + + std::pair get_scale_size() const noexcept; + + private: + Point rand_point_in_rect(const Rect& rect); + + void callback(const json::object& details); + + std::shared_ptr m_controller; + ProxyCallback m_callback; + + std::minstd_rand m_rand_engine; + + std::pair m_scale_size = { WindowWidthDefault, WindowHeightDefault }; + double m_control_scale = 1.0; + }; +} diff --git a/src/MaaCore/Controller/Controller.cpp b/src/MaaCore/Controller/Controller.cpp index 0d16f0a485..6e900a9b05 100644 --- a/src/MaaCore/Controller/Controller.cpp +++ b/src/MaaCore/Controller/Controller.cpp @@ -2,20 +2,6 @@ #include "Utils/Platform.hpp" -#ifdef _WIN32 -#include -#else -#include -#include -#include -#include -#ifndef __APPLE__ -#include -#endif -#include -#include -#endif - #include #include #include @@ -37,6 +23,10 @@ #pragma warning(pop) #endif +#include "AdbController.h" +#include "MaatouchController.h" +#include "MinitouchController.h" + #include "Common/AsstTypes.h" #include "Config/GeneralConfig.h" #include "Utils/Logger.hpp" @@ -48,38 +38,12 @@ asst::Controller::Controller(const AsstCallback& callback, Assistant* inst) { LogTraceFunction; -#ifdef _WIN32 - m_support_socket = WsaHelper::get_instance()(); -#else - int pipe_in_ret = ::pipe(m_pipe_in); - int pipe_out_ret = ::pipe(m_pipe_out); - ::fcntl(m_pipe_out[PIPE_READ], F_SETFL, O_NONBLOCK); - - if (pipe_in_ret < 0 || pipe_out_ret < 0) { - Log.error(__FUNCTION__, "controller pipe created failed", pipe_in_ret, pipe_out_ret); - } - m_support_socket = true; -#endif - - if (!m_support_socket) { - Log.error("socket not supported"); - } + m_controller_factory = std::make_unique(callback, inst); } asst::Controller::~Controller() { LogTraceFunction; - - release_minitouch(); - make_instance_inited(false); - kill_adb_daemon(); - -#ifndef _WIN32 - ::close(m_pipe_in[PIPE_READ]); - ::close(m_pipe_in[PIPE_WRITE]); - ::close(m_pipe_out[PIPE_READ]); - ::close(m_pipe_out[PIPE_WRITE]); -#endif } std::pair asst::Controller::get_scale_size() const noexcept @@ -87,122 +51,10 @@ std::pair asst::Controller::get_scale_size() const noexcept return m_scale_size; } -std::optional asst::Controller::call_command(const std::string& cmd, int64_t timeout, bool allow_reconnect, - bool recv_by_socket) +void asst::Controller::clear_info() noexcept { - 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; - if (m_use_adb_lite) { - exit_res = call_command_tcpip(cmd, recv_by_socket, pipe_data, sock_data, timeout, start_time); - } - if (!exit_res) { -#ifdef _WIN32 - exit_res = call_command_win32(cmd, recv_by_socket, pipe_data, sock_data, timeout, start_time); -#else - exit_res = call_command_posix(cmd, recv_by_socket, pipe_data, sock_data, timeout, start_time); -#endif - } - if (!exit_res) { - return std::nullopt; - } - const int exit_ret = exit_res.value(); - - callcmd_lock.unlock(); - - auto duration = duration_cast(steady_clock::now() - start_time).count(); - Log.info("Call `", cmd, "` ret", exit_ret, ", cost", duration, "ms , stdout size:", pipe_data.size(), - ", socket size:", sock_data.size()); - if (!pipe_data.empty() && pipe_data.size() < 4096) { - 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 里的 make_instance_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 炸了。所以重新连接一下 - 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) { - if (m_minitouch_enabled && call_and_hup_minitouch()) { - Log.error("reconnected with minitouch"); - m_minitouch_available = true; - } - else { - Log.error("reconnected without minitouch"); - m_minitouch_available = false; - } - 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 }, - } }, - }; - make_instance_inited(false); // 重连失败,释放 - callback(AsstMsg::ConnectionInfo, info); - } - - return std::nullopt; + m_uuid.clear(); + m_scale_size = { WindowWidthDefault, WindowHeightDefault }; } void asst::Controller::callback(AsstMsg msg, const json::value& details) @@ -212,486 +64,6 @@ void asst::Controller::callback(AsstMsg msg, const json::value& details) } } -bool asst::Controller::call_and_hup_minitouch() -{ - LogTraceFunction; - release_minitouch(true); - - std::string cmd = m_use_maa_touch ? m_adb.call_maatouch : m_adb.call_minitouch; - Log.info(cmd); - - std::string pipe_str; - - auto check_timeout = [&](const auto& start_time) -> bool { - using namespace std::chrono_literals; - return std::chrono::steady_clock::now() - start_time < 3s; - }; - - bool call_minitouch_success = false; - if (m_use_adb_lite) { - call_minitouch_success = call_and_hup_minitouch_tcpip(cmd, 3, pipe_str); - } - else { -#ifdef _WIN32 - call_minitouch_success = call_and_hup_minitouch_win32(cmd, check_timeout, pipe_str); -#else // !_WIN32 - call_minitouch_success = call_and_hup_minitouch_posix(cmd, check_timeout, pipe_str); -#endif // _WIN32 - } - if (!call_minitouch_success) { - return false; - } - - Log.info("pipe str", Logger::separator::newline, pipe_str); - - convert_lf(pipe_str); - size_t s_pos = pipe_str.find('^'); - size_t e_pos = pipe_str.find('\n', s_pos); - if (s_pos == std::string::npos || e_pos == std::string::npos) { - Log.error("Failed to find ^ in minitouch pipe"); - release_minitouch(true); - return false; - } - std::string key_info = pipe_str.substr(s_pos + 1, e_pos - s_pos - 1); - Log.info("minitouch key props", key_info); - int size_1 = 0, size_2 = 0; - std::stringstream ss; - ss << key_info; - ss >> m_minitouch_props.max_contacts; - ss >> size_1; - ss >> size_2; - ss >> m_minitouch_props.max_pressure; - - // 有些模拟器在竖屏分辨率时,这里的输出是反过来的 - // 考虑到应该没人竖屏玩明日方舟,所以取较大值为 x,较小值为 y - m_minitouch_props.max_x = std::max(size_1, size_2); - m_minitouch_props.max_y = std::min(size_1, size_2); - - m_minitouch_props.x_scaling = static_cast(m_minitouch_props.max_x) / m_width; - m_minitouch_props.y_scaling = static_cast(m_minitouch_props.max_y) / m_height; - - return true; -} - -bool asst::Controller::input_to_minitouch(const std::string& cmd) -{ - // Log.debug("Input to minitouch", Logger::separator::newline, cmd); - if (m_use_adb_lite) { - return input_to_minitouch_adb(cmd); - } - -#ifdef _WIN32 - if (m_minitouch_parent_write == INVALID_HANDLE_VALUE) { - Log.error("minitouch write handle invalid", m_minitouch_parent_write); - return false; - } - DWORD written = 0; - if (!WriteFile(m_minitouch_parent_write, cmd.c_str(), - static_cast(cmd.size() * sizeof(std::string::value_type)), &written, NULL)) { - auto err = GetLastError(); - Log.error("Failed to write to minitouch, err", err); - return false; - } - - return cmd.size() == written; -#else - if (m_minitouch_process < 0 || m_write_to_minitouch_fd < 0) return false; - if (::write(m_write_to_minitouch_fd, cmd.c_str(), cmd.length()) >= 0) return true; - Log.error("Failed to write to minitouch, err", errno); - return false; -#endif -} - -void asst::Controller::release_minitouch(bool force) -{ - LogTraceFunction; - - if (!m_minitouch_available && !force) { - return; - } - m_minitouch_available = false; - - if (m_use_adb_lite) { - m_minitouch_handle = nullptr; - } - -#ifdef _WIN32 - - if (m_minitouch_process_info.hProcess != INVALID_HANDLE_VALUE) { - CloseHandle(m_minitouch_process_info.hProcess); - m_minitouch_process_info.hProcess = INVALID_HANDLE_VALUE; - } - if (m_minitouch_process_info.hThread != INVALID_HANDLE_VALUE) { - CloseHandle(m_minitouch_process_info.hThread); - m_minitouch_process_info.hThread = INVALID_HANDLE_VALUE; - } - if (m_minitouch_parent_write != INVALID_HANDLE_VALUE) { - CloseHandle(m_minitouch_parent_write); - m_minitouch_parent_write = INVALID_HANDLE_VALUE; - } -#else - if (m_write_to_minitouch_fd != -1) { - ::close(m_write_to_minitouch_fd); - m_write_to_minitouch_fd = -1; - } - if (m_minitouch_process > 0) { - ::kill(m_minitouch_process, SIGTERM); - if (force) ::kill(m_minitouch_process, SIGKILL); - m_minitouch_process = -1; - } -#endif // _WIN32 -} - -// 返回值代表是否找到 "\r\n",函数本身会将所有 "\r\n" 替换为 "\n" -bool asst::Controller::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; -} - -asst::Point asst::Controller::rand_point_in_rect(const Rect& rect) -{ - int x = 0, y = 0; - if (rect.width == 0) { - x = rect.x; - } - else { - int x_rand = std::poisson_distribution(rect.width / 2.)(m_rand_engine); - - x = x_rand + rect.x; - } - - if (rect.height == 0) { - y = rect.y; - } - else { - int y_rand = std::poisson_distribution(rect.height / 2.)(m_rand_engine); - y = y_rand + rect.y; - } - - return { x, y }; -} - -void asst::Controller::random_delay() const -{ - auto& opt = Config.get_options(); - if (opt.control_delay_upper != 0) { - static std::default_random_engine rand_engine(std::random_device {}()); - static std::uniform_int_distribution rand_uni(opt.control_delay_lower, opt.control_delay_upper); - - sleep(rand_uni(rand_engine)); - } -} - -void asst::Controller::clear_info() noexcept -{ - make_instance_inited(false); - m_adb = decltype(m_adb)(); - m_uuid.clear(); - m_width = 0; - m_height = 0; - m_control_scale = 1.0; - m_minitouch_available = false; - m_scale_size = { WindowWidthDefault, WindowHeightDefault }; - m_minitouch_props = decltype(m_minitouch_props)(); - m_screencap_data_general_size = 0; -} - -void asst::Controller::close_socket() noexcept -{ -#ifdef _WIN32 - if (m_server_sock != INVALID_SOCKET) { - ::closesocket(m_server_sock); - m_server_sock = INVALID_SOCKET; - } -#else - if (m_server_sock >= 0) { - ::close(m_server_sock); - m_server_sock = -1; - } -#endif - m_server_started = false; -} - -std::optional asst::Controller::init_socket(const std::string& local_address) -{ - LogTraceFunction; - -#ifdef _WIN32 - if (m_server_sock == INVALID_SOCKET) { - m_server_sock = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - if (m_server_sock == INVALID_SOCKET) { - return std::nullopt; - } - } - - DWORD dummy = 0; - GUID guid_accept_ex = WSAID_ACCEPTEX; - int err = WSAIoctl(m_server_sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid_accept_ex, sizeof(guid_accept_ex), - &m_server_accept_ex, sizeof(m_server_accept_ex), &dummy, NULL, NULL); - if (err == SOCKET_ERROR) { - err = WSAGetLastError(); - Log.error("failed to resolve AcceptEx, err:", err); - ::closesocket(m_server_sock); - return std::nullopt; - } - m_server_sock_addr.sin_family = PF_INET; - ::inet_pton(AF_INET, local_address.c_str(), &m_server_sock_addr.sin_addr); -#else - m_server_sock = ::socket(AF_INET, SOCK_STREAM, 0); - if (m_server_sock < 0) { - return std::nullopt; - } - - m_server_sock_addr.sin_family = AF_INET; - m_server_sock_addr.sin_addr.s_addr = INADDR_ANY; -#endif - - bool server_start = false; - uint16_t port_result = 0; - -#ifdef _WIN32 - m_server_sock_addr.sin_port = ::htons(0); - int bind_ret = ::bind(m_server_sock, reinterpret_cast(&m_server_sock_addr), sizeof(SOCKADDR)); - int addrlen = sizeof(m_server_sock_addr); - int getname_ret = ::getsockname(m_server_sock, reinterpret_cast(&m_server_sock_addr), &addrlen); - int listen_ret = ::listen(m_server_sock, 3); - server_start = bind_ret == 0 && getname_ret == 0 && listen_ret == 0; -#else - m_server_sock_addr.sin_port = htons(0); - int bind_ret = ::bind(m_server_sock, reinterpret_cast(&m_server_sock_addr), sizeof(::sockaddr_in)); - socklen_t addrlen = sizeof(m_server_sock_addr); - int getname_ret = ::getsockname(m_server_sock, reinterpret_cast(&m_server_sock_addr), &addrlen); - int listen_ret = ::listen(m_server_sock, 3); - struct timeval timeout = { 6, 0 }; - int timeout_ret = ::setsockopt(m_server_sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(struct timeval)); - server_start = bind_ret == 0 && getname_ret == 0 && listen_ret == 0 && timeout_ret == 0; -#endif - - if (!server_start) { - Log.info("not supports socket"); - return std::nullopt; - } - -#ifdef _WIN32 - port_result = ::ntohs(m_server_sock_addr.sin_port); -#else - port_result = ntohs(m_server_sock_addr.sin_port); -#endif - - Log.info("command server start", local_address, port_result); - return port_result; -} - -bool asst::Controller::screencap(bool allow_reconnect) -{ - DecodeFunc decode_raw = [&](const std::string& data) -> bool { - if (data.empty()) { - return false; - } - size_t std_size = 4ULL * m_width * m_height; - if (data.size() < std_size) { - return false; - } - size_t header_size = data.size() - std_size; - auto img_data_beg = data.cbegin() + header_size; - if (std::all_of(data.cbegin(), img_data_beg, std::logical_not {})) { - return false; - } - cv::Mat temp(m_height, m_width, CV_8UC4, const_cast(&*img_data_beg)); - if (temp.empty()) { - return false; - } - cv::cvtColor(temp, temp, cv::COLOR_RGB2BGR); - std::unique_lock image_lock(m_image_mutex); - m_cache_image = 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; - } - std::unique_lock image_lock(m_image_mutex); - m_cache_image = temp; - return true; - }; - - switch (m_adb.screencap_method) { - case AdbProperty::ScreencapMethod::UnknownYet: { - using namespace std::chrono; - Log.info("Try to find the fastest way to screencap"); - auto min_cost = milliseconds(LLONG_MAX); - clear_lf_info(); - - auto start_time = high_resolution_clock::now(); - if (m_support_socket && m_server_started && - screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true)) { - auto duration = duration_cast(high_resolution_clock::now() - start_time); - if (duration < min_cost) { - m_adb.screencap_method = AdbProperty::ScreencapMethod::RawByNc; - make_instance_inited(true); - min_cost = duration; - } - Log.info("RawByNc cost", duration.count(), "ms"); - } - else { - Log.info("RawByNc is not supported"); - } - clear_lf_info(); - - start_time = high_resolution_clock::now(); - if (screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect)) { - auto duration = duration_cast(high_resolution_clock::now() - start_time); - if (duration < min_cost) { - m_adb.screencap_method = AdbProperty::ScreencapMethod::RawWithGzip; - make_instance_inited(true); - min_cost = duration; - } - Log.info("RawWithGzip cost", duration.count(), "ms"); - } - else { - Log.info("RawWithGzip is not supported"); - } - clear_lf_info(); - - start_time = high_resolution_clock::now(); - if (screencap(m_adb.screencap_encode, decode_encode, allow_reconnect)) { - auto duration = duration_cast(high_resolution_clock::now() - start_time); - if (duration < min_cost) { - m_adb.screencap_method = AdbProperty::ScreencapMethod::Encode; - make_instance_inited(true); - min_cost = duration; - } - Log.info("Encode cost", duration.count(), "ms"); - } - else { - Log.info("Encode is not supported"); - } - static const std::unordered_map MethodName = { - { AdbProperty::ScreencapMethod::UnknownYet, "UnknownYet" }, - { AdbProperty::ScreencapMethod::RawByNc, "RawByNc" }, - { AdbProperty::ScreencapMethod::RawWithGzip, "RawWithGzip" }, - { AdbProperty::ScreencapMethod::Encode, "Encode" }, - }; - Log.info("The fastest way is", MethodName.at(m_adb.screencap_method), ", cost:", min_cost.count(), "ms"); - clear_lf_info(); - return m_adb.screencap_method != AdbProperty::ScreencapMethod::UnknownYet; - } break; - case AdbProperty::ScreencapMethod::RawByNc: { - return screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true); - } break; - case AdbProperty::ScreencapMethod::RawWithGzip: { - return screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect); - } break; - case AdbProperty::ScreencapMethod::Encode: { - return screencap(m_adb.screencap_encode, decode_encode, allow_reconnect); - } break; - } - - return false; -} - -bool asst::Controller::screencap(const std::string& cmd, const DecodeFunc& decode_func, bool allow_reconnect, - bool by_socket) -{ - if ((!m_support_socket || !m_server_started) && by_socket) [[unlikely]] { - return false; - } - - auto ret = call_command(cmd, 20000, allow_reconnect, by_socket); - - if (!ret || ret.value().empty()) [[unlikely]] { - Log.error("data is empty!"); - return false; - } - auto& data = ret.value(); - if (m_screencap_data_general_size && data.size() < m_screencap_data_general_size * 0.1) { - Log.warn("data is too small!"); - } - - 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; - } - m_screencap_data_general_size = data.size(); - return true; -} - -void asst::Controller::clear_lf_info() -{ - m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::UnknownYet; -} - cv::Mat asst::Controller::get_resized_image_cache() const { const static cv::Size d_size(m_scale_size.first, m_scale_size.second); @@ -708,517 +80,68 @@ cv::Mat asst::Controller::get_resized_image_cache() const bool asst::Controller::start_game(const std::string& client_type) { - if (client_type.empty()) { - return false; - } - auto intent_name = Config.get_intent_name(client_type); - if (!intent_name) { - return false; - } - std::string cur_cmd = utils::string_replace_all(m_adb.start, "[Intent]", intent_name.value()); - return call_command(cur_cmd).has_value(); + return m_controller->start_game(client_type); } bool asst::Controller::stop_game() { - return call_command(m_adb.stop).has_value(); + return m_controller->stop_game(); } bool asst::Controller::click(const Point& p) { - int x = static_cast(p.x * m_control_scale); - int y = static_cast(p.y * m_control_scale); - // log.trace("Click, raw:", p.x, p.y, "corr:", x, y); - - return click_without_scale(Point(x, y)); + return m_scale_proxy->click(p); } bool asst::Controller::click(const Rect& rect) { - return click(rand_point_in_rect(rect)); -} - -bool asst::Controller::click_without_scale(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"); - } - - if (m_minitouch_enabled && m_minitouch_available) { - Log.trace(m_use_maa_touch ? "maatouch" : "minitouch", "click:", p); - Minitoucher toucher(std::bind(&Controller::input_to_minitouch, this, std::placeholders::_1), m_minitouch_props); - return toucher.down(p.x, p.y) && toucher.up(); - } - else { - 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::Controller::click_without_scale(const Rect& rect) -{ - return click_without_scale(rand_point_in_rect(rect)); + return m_scale_proxy->click(rect); } bool asst::Controller::swipe(const Point& p1, const Point& p2, int duration, bool extra_swipe, double slope_in, double slope_out, bool with_pause) { - int x1 = static_cast(p1.x * m_control_scale); - int y1 = static_cast(p1.y * m_control_scale); - int x2 = static_cast(p2.x * m_control_scale); - int y2 = static_cast(p2.y * m_control_scale); - // log.trace("Swipe, raw:", p1.x, p1.y, p2.x, p2.y, "corr:", x1, y1, x2, y2); - - return swipe_without_scale(Point(x1, y1), Point(x2, y2), duration, extra_swipe, slope_in, slope_out, with_pause); + return m_scale_proxy->swipe(p1, p2, duration, extra_swipe, slope_in, slope_out, with_pause); } bool asst::Controller::swipe(const Rect& r1, const Rect& r2, int duration, bool extra_swipe, double slope_in, double slope_out, bool with_pause) { - return swipe(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, extra_swipe, slope_in, slope_out, - with_pause); + return m_scale_proxy->swipe(r1, r2, duration, extra_swipe, slope_in, slope_out, with_pause); } -bool asst::Controller::swipe_without_scale(const Point& p1, const Point& p2, int duration, bool extra_swipe, - double slope_in, double slope_out, bool with_pause) +bool asst::Controller::inject_input_event(InputEvent& event) { - 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(); - if (m_minitouch_enabled && m_minitouch_available) { - Log.trace(m_use_maa_touch ? "maatouch" : "minitouch", "swipe", p1, p2, duration, extra_swipe, slope_in, - slope_out); - Minitoucher toucher(std::bind(&Controller::input_to_minitouch, this, std::placeholders::_1), m_minitouch_props); - toucher.down(x1, y1); - - constexpr int TimeInterval = Minitoucher::DefaultSwipeDelay; - - auto cubic_spline = [](double slope_0, double slope_1, double t) { - const double a = slope_0; - const double b = -(2 * slope_0 + slope_1 - 3); - const double c = -(-slope_0 - slope_1 + 2); - return a * t + b * std::pow(t, 2) + c * std::pow(t, 3); - }; // TODO: move this to math.hpp - - bool need_pause = with_pause && support_swipe_with_pause(); - std::future pause_future; - auto minitouch_move = [&](int _x1, int _y1, int _x2, int _y2, int _duration) { - for (int cur_time = TimeInterval; cur_time < _duration; cur_time += TimeInterval) { - double progress = cubic_spline(slope_in, slope_out, static_cast(cur_time) / duration); - int cur_x = static_cast(std::lerp(_x1, _x2, progress)); - int cur_y = static_cast(std::lerp(_y1, _y2, progress)); - if (need_pause && std::sqrt(std::pow(cur_x - _x1, 2) + std::pow(cur_y - _y1, 2)) > - opt.swipe_with_pause_required_distance) { - need_pause = false; - if (m_use_maa_touch) { - constexpr int EscKeyCode = 111; - toucher.key_down(EscKeyCode); - toucher.key_up(EscKeyCode, 0); - } - else { - pause_future = std::async(std::launch::async, [&]() { press_esc(); }); - } - } - if (cur_x < 0 || cur_x > m_minitouch_props.max_x || cur_y < 0 || cur_y > m_minitouch_props.max_y) { - continue; - } - toucher.move(cur_x, cur_y); - } - if (_x2 >= 0 && _x2 <= m_minitouch_props.max_x && _y2 >= 0 && _y2 <= m_minitouch_props.max_y) { - toucher.move(_x2, _y2); - } - }; - - constexpr int DefaultDuration = 200; - minitouch_move(x1, y1, x2, y2, duration ? duration : DefaultDuration); - - if (extra_swipe && opt.minitouch_extra_swipe_duration > 0) { - constexpr int ExtraEndDelay = 100; // 停留终点 - toucher.wait(ExtraEndDelay); - minitouch_move(x2, y2, x2, y2 - opt.minitouch_extra_swipe_dist, opt.minitouch_extra_swipe_duration); - } - return toucher.up(); - } - else { - 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::Controller::swipe_without_scale(const Rect& r1, const Rect& r2, int duration, bool extra_swipe, double v0, - double v1, bool with_pause) -{ - return swipe_without_scale(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, extra_swipe, v0, v1, - with_pause); + return m_scale_proxy->inject_input_event(event); } bool asst::Controller::press_esc() { LogTraceFunction; - return call_command(m_adb.press_esc).has_value(); + return m_controller->press_esc(); } -bool asst::Controller::support_swipe_with_pause() const noexcept +asst::ControlFeat::Feat asst::Controller::support_features() { - return m_minitouch_enabled && m_minitouch_available && m_swipe_with_pause_enabled && !m_adb.press_esc.empty(); -} - -bool asst::Controller::support_precise_swipe() const noexcept -{ - return m_minitouch_enabled && m_minitouch_available; + return m_controller->support_features(); } bool asst::Controller::connect(const std::string& adb_path, const std::string& address, const std::string& config) { LogTraceFunction; - release_minitouch(); clear_info(); -#ifdef ASST_DEBUG - if (config == "DEBUG") { - make_instance_inited(true); - return true; - } -#endif + m_controller = + m_controller_factory->create_controller(m_controller_type, adb_path, address, config, m_platform_type); - auto get_info_json = [&]() -> json::value { - return json::object { - { "uuid", m_uuid }, - { "details", - json::object { - { "adb", adb_path }, - { "address", address }, - { "config", config }, - } }, - }; - }; - - 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); + if (!m_controller) { + Log.error("connect failed"); return false; } - const auto& adb_cfg = adb_ret.value(); - std::string display_id; - std::string nc_address = "10.0.2.2"; - uint16_t nc_port = 0; - - // 里面的值每次执行命令后可能更新,所以要用 lambda 拿最新的 - auto cmd_replace = [&](const std::string& cfg_cmd) -> std::string { - return utils::string_replace_all(cfg_cmd, { - { "[Adb]", adb_path }, - { "[AdbSerial]", address }, - { "[DisplayId]", display_id }, - { "[NcPort]", std::to_string(nc_port) }, - { "[NcAddress]", nc_address }, - }); - }; - - if (need_exit()) { - return false; - } - - /* connect */ - { - m_adb.connect = cmd_replace(adb_cfg.connect); - auto connect_ret = call_command(m_adb.connect, 60LL * 1000, false /* adb 连接时不允许重试 */); - bool is_connect_success = false; - if (connect_ret) { - auto& connect_str = connect_ret.value(); - is_connect_success = connect_str.find("error") == std::string::npos; - if (connect_str.find("daemon started successfully") != std::string::npos && - connect_str.find("daemon still not running") == std::string::npos) { - m_adb_release = cmd_replace(adb_cfg.release); - } - } - if (!is_connect_success) { - 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(cmd_replace(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; - } - - auto& uuid_str = uuid_ret.value(); - std::erase_if(uuid_str, [](char c) { return !std::isdigit(c) && !std::isalpha(c); }); - 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); - } - - if (need_exit()) { - return false; - } - - // 按需获取display ID 信息 - if (!adb_cfg.display_id.empty()) { - auto display_id_ret = call_command(cmd_replace(adb_cfg.display_id)); - 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; - } - - display_id = display_id_pipe_str.substr(last + 1); - // 去掉换行 - display_id.pop_back(); - } - - if (need_exit()) { - return false; - } - - /* display */ - { - auto display_ret = call_command(cmd_replace(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; - } - else if (m_width < WindowWidthDefault || m_height < WindowHeightDefault) { - info["what"] = "UnsupportedResolution"; - info["why"] = "Low screen resolution"; - callback(AsstMsg::ConnectionInfo, info); - return false; - } - else if (std::fabs(static_cast(WindowWidthDefault) / static_cast(WindowHeightDefault) - - static_cast(m_width) / static_cast(m_height)) > 1e-7) { - info["what"] = "UnsupportedResolution"; - info["why"] = "Not 16:9"; - callback(AsstMsg::ConnectionInfo, info); - return false; - } - } - - if (need_exit()) { - return false; - } - - /* calc ratio */ - { - constexpr double DefaultRatio = - static_cast(WindowWidthDefault) / static_cast(WindowHeightDefault); - double cur_ratio = static_cast(m_width) / static_cast(m_height); - - if (cur_ratio >= DefaultRatio // 说明是宽屏或默认16:9,按照高度计算缩放 - || std::fabs(cur_ratio - DefaultRatio) < DoubleDiff) { - int scale_width = static_cast(cur_ratio * WindowHeightDefault); - m_scale_size = std::make_pair(scale_width, WindowHeightDefault); - m_control_scale = static_cast(m_height) / static_cast(WindowHeightDefault); - } - else { // 否则可能是偏正方形的屏幕,按宽度计算 - int scale_height = static_cast(WindowWidthDefault / cur_ratio); - m_scale_size = std::make_pair(WindowWidthDefault, scale_height); - m_control_scale = static_cast(m_width) / static_cast(WindowWidthDefault); - } - } - - { - json::value info = get_info_json() | json::object { - { "what", "Connected" }, - { "why", "" }, - }; - callback(AsstMsg::ConnectionInfo, info); - } - - m_adb.click = cmd_replace(adb_cfg.click); - m_adb.swipe = cmd_replace(adb_cfg.swipe); - m_adb.press_esc = cmd_replace(adb_cfg.press_esc); - m_adb.screencap_raw_with_gzip = cmd_replace(adb_cfg.screencap_raw_with_gzip); - m_adb.screencap_encode = cmd_replace(adb_cfg.screencap_encode); - m_adb_release = m_adb.release = cmd_replace(adb_cfg.release); - m_adb.start = cmd_replace(adb_cfg.start); - m_adb.stop = cmd_replace(adb_cfg.stop); - - 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(cmd_replace(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) { - nc_address = nc_result_str.substr(0, pos); - } - } - - auto socket_opt = init_socket(bind_address); - if (socket_opt) { - nc_port = socket_opt.value(); - m_adb.screencap_raw_by_nc = cmd_replace(adb_cfg.screencap_raw_by_nc); - m_server_started = true; - } - else { - m_server_started = false; - } - } - - if (need_exit()) { - return false; - } - - while (m_minitouch_enabled) { - m_minitouch_available = false; - - std::string_view touch_program; - if (m_use_maa_touch) { - touch_program = "maatouch"; - m_minitouch_props.orientation = 0; - } - else { - std::string abilist = call_command(cmd_replace(adb_cfg.abilist)).value_or(std::string()); - for (const auto& abi : Config.get_options().minitouch_programs_order) { - if (abilist.find(abi) != std::string::npos) { - touch_program = abi; - break; - } - } - std::string orientation_str = call_command(cmd_replace(adb_cfg.orientation)).value_or("0"); - if (!orientation_str.empty()) { - char first = orientation_str.front(); - if (first == '0' || first == '1' || first == '2' || first == '3') { - m_minitouch_props.orientation = static_cast(first - '0'); - } - } - } - Log.info("touch_program", touch_program, "orientation", m_minitouch_props.orientation); - - if (touch_program.empty()) break; - - auto minitouch_cmd_rep = [&](const std::string& cfg_cmd) -> std::string { - using namespace asst::utils::path_literals; - return utils::string_replace_all( - cmd_replace(cfg_cmd), - { - { "[minitouchLocalPath]", - utils::path_to_utf8_string(ResDir.get() / "minitouch"_p / touch_program / "minitouch"_p) }, - { "[minitouchWorkingFile]", m_uuid }, - }); - }; - - if (!call_command(minitouch_cmd_rep(adb_cfg.push_minitouch))) break; - if (!call_command(minitouch_cmd_rep(adb_cfg.chmod_minitouch))) break; - - m_adb.call_minitouch = minitouch_cmd_rep(adb_cfg.call_minitouch); - m_adb.call_maatouch = minitouch_cmd_rep(adb_cfg.call_maatouch); - - if (!call_and_hup_minitouch()) break; - - m_minitouch_available = true; - break; - }; - - if (m_minitouch_enabled && !m_minitouch_available) { - json::value info = get_info_json() | json::object { - { "what", "TouchModeNotAvailable" }, - { "why", "" }, - }; - callback(AsstMsg::ConnectionInfo, info); - return false; - } + m_uuid = m_controller->get_uuid(); // try to find the fastest way if (!screencap()) { @@ -1226,77 +149,67 @@ bool asst::Controller::connect(const std::string& adb_path, const std::string& a return false; } + auto proxy_callback = [&](const json::object& details) { + json::value connection_info = json::object { + { "uuid", m_uuid }, + { "details", + json::object { + { "adb", adb_path }, + { "address", address }, + { "config", config }, + } }, + } | details; + callback(AsstMsg::ConnectionInfo, connection_info); + }; + + try { + m_scale_proxy = std::make_shared(m_controller, proxy_callback); + } + catch (const std::exception& e) { + Log.error("Cannot create controller proxy: {}", e.what()); + return false; + } + + if (!m_scale_proxy) { + Log.error("Cannot create controller proxy!"); + return false; + } + + m_scale_size = m_scale_proxy->get_scale_size(); + return true; } -void asst::Controller::make_instance_inited(bool inited) +bool asst::Controller::inited() noexcept { - Log.trace(__FUNCTION__, "|", inited, ", pre m_inited =", m_inited, ", pre m_instance_count =", m_instance_count); - - if (inited == m_inited) { - return; - } - m_inited = inited; - - if (inited) { - ++m_instance_count; - } - else { - // 所有实例全部释放,执行最终的 release 函数 - if (!--m_instance_count) { - release(); - } - } + return m_controller->inited(); } -void asst::Controller::kill_adb_daemon() +void asst::Controller::set_touch_mode(const TouchMode& mode) noexcept { - if (m_instance_count) return; -#ifndef _WIN32 - if (m_child) -#endif - { - if (!m_adb_release.empty()) { - call_command(m_adb_release, 20000, false); - m_adb_release.clear(); - } + switch (mode) { + case TouchMode::Adb: + m_controller_type = ControllerType::Adb; + break; + case TouchMode::Minitouch: + m_controller_type = ControllerType::Minitouch; + break; + case TouchMode::Maatouch: + m_controller_type = ControllerType::Maatouch; + break; + default: + m_controller_type = ControllerType::Minitouch; } } -void asst::Controller::release() -{ - close_socket(); - -#ifndef _WIN32 - if (m_child) -#endif - { - if (!m_adb.release.empty()) { - m_adb_release.clear(); - call_command(m_adb.release, 20000, false); - } - } -} - -bool asst::Controller::inited() const noexcept -{ - return m_inited; -} - -void asst::Controller::set_minitouch_enabled(bool enable, bool maa_touch) noexcept -{ - m_minitouch_enabled = enable; - m_use_maa_touch = maa_touch; -} - void asst::Controller::set_swipe_with_pause(bool enable) noexcept { - m_swipe_with_pause_enabled = enable; + m_controller->set_swipe_with_pause(enable); } void asst::Controller::set_adb_lite_enabled(bool enable) noexcept { - m_use_adb_lite = enable; + m_platform_type = enable ? PlatformType::AdbLite : PlatformType::Native; } const std::string& asst::Controller::get_uuid() const @@ -1306,7 +219,7 @@ const std::string& asst::Controller::get_uuid() const cv::Mat asst::Controller::get_image(bool raw) { - if (m_scale_size.first == 0 || m_scale_size.second == 0) { + if (get_scale_size() == std::pair(0, 0)) { Log.error("Unknown image size"); return {}; } @@ -1356,655 +269,8 @@ cv::Mat asst::Controller::get_image_cache() const return get_resized_image_cache(); } -#ifdef _WIN32 -std::optional asst::Controller::call_command_win32(const std::string& cmd, const bool recv_by_socket, - std::string& pipe_data, std::string& sock_data, - const int64_t timeout, - const std::chrono::steady_clock::time_point start_time) +bool asst::Controller::screencap(bool allow_reconnect) { - using namespace std::chrono; - - asst::platform::single_page_buffer pipe_buffer; - asst::platform::single_page_buffer sock_buffer; - - HANDLE pipe_parent_read = INVALID_HANDLE_VALUE, pipe_child_write = INVALID_HANDLE_VALUE; - SECURITY_ATTRIBUTES sa_inherit { .nLength = sizeof(SECURITY_ATTRIBUTES), .bInheritHandle = TRUE }; - if (!asst::win32::CreateOverlappablePipe(&pipe_parent_read, &pipe_child_write, nullptr, &sa_inherit, - (DWORD)pipe_buffer.size(), true, false)) { - DWORD err = GetLastError(); - Log.error("CreateOverlappablePipe failed, err", err); - return std::nullopt; - } - - STARTUPINFOW si {}; - si.cb = sizeof(STARTUPINFOW); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.hStdOutput = pipe_child_write; - si.hStdError = pipe_child_write; - ASST_AUTO_DEDUCED_ZERO_INIT_START - PROCESS_INFORMATION process_info = { nullptr }; // 进程信息结构体 - ASST_AUTO_DEDUCED_ZERO_INIT_END - - auto cmdline_osstr = asst::utils::to_osstring(cmd); - BOOL create_ret = - CreateProcessW(nullptr, cmdline_osstr.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &process_info); - if (!create_ret) { - DWORD err = GetLastError(); - Log.error("Call `", cmd, "` create process failed, ret", create_ret, "error code:", err); - return std::nullopt; - } - - CloseHandle(pipe_child_write); - pipe_child_write = INVALID_HANDLE_VALUE; - - std::vector wait_handles; - wait_handles.reserve(3); - bool process_running = true; - bool pipe_eof = false; - bool accept_pending = false; - bool socket_eof = false; - - OVERLAPPED pipeov { .hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr) }; - (void)ReadFile(pipe_parent_read, pipe_buffer.get(), (DWORD)pipe_buffer.size(), nullptr, &pipeov); - - OVERLAPPED sockov {}; - SOCKET client_socket = INVALID_SOCKET; - - if (recv_by_socket) { - sock_buffer = asst::platform::single_page_buffer(); - sockov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - DWORD dummy; - if (!m_server_accept_ex(m_server_sock, client_socket, sock_buffer.get(), - (DWORD)sock_buffer.size() - ((sizeof(sockaddr_in) + 16) * 2), sizeof(sockaddr_in) + 16, - sizeof(sockaddr_in) + 16, &dummy, &sockov)) { - DWORD err = WSAGetLastError(); - if (err == ERROR_IO_PENDING) { - accept_pending = true; - } - else { - Log.trace("AcceptEx failed, err:", err); - accept_pending = false; - socket_eof = true; - ::closesocket(client_socket); - } - } - } - - while (!need_exit()) { - wait_handles.clear(); - if (process_running) wait_handles.push_back(process_info.hProcess); - if (!pipe_eof) wait_handles.push_back(pipeov.hEvent); - if (recv_by_socket && ((accept_pending && process_running) || !socket_eof)) { - wait_handles.push_back(sockov.hEvent); - } - if (wait_handles.empty()) break; - auto elapsed = steady_clock::now() - start_time; - // TODO: 这里目前是隔 5000ms 判断一次,应该可以加一个 wait_handle 来判断外部中断(need_exit) - auto wait_time = - (std::min)(timeout - duration_cast(elapsed).count(), process_running ? 5LL * 1000 : 0LL); - if (wait_time < 0) break; - auto wait_result = - WaitForMultipleObjectsEx((DWORD)wait_handles.size(), wait_handles.data(), FALSE, (DWORD)wait_time, TRUE); - HANDLE signaled_object = INVALID_HANDLE_VALUE; - if (wait_result >= WAIT_OBJECT_0 && wait_result < WAIT_OBJECT_0 + wait_handles.size()) { - signaled_object = wait_handles[(size_t)wait_result - WAIT_OBJECT_0]; - } - else if (wait_result == WAIT_TIMEOUT) { - if (wait_time == 0) { - std::vector handle_string {}; - for (auto handle : wait_handles) { - if (handle == process_info.hProcess) { - handle_string.emplace_back("process_info.hProcess"); - } - else if (handle == pipeov.hEvent) { - handle_string.emplace_back("pipeov.hEvent"); - } - else if (recv_by_socket && handle == sockov.hEvent) { - handle_string.emplace_back("sockov.hEvent"); - } - else { - handle_string.emplace_back("UnknownHandle"); - } - } - Log.warn("Wait handles:", handle_string, "timeout."); - if (process_running) { - TerminateProcess(process_info.hProcess, 0); - } - break; - } - continue; - } - else { - // something bad happened - DWORD err = GetLastError(); - // throw std::system_error(std::error_code(err, std::system_category())); - Log.error(__FUNCTION__, "A fatal error occurred", err); - break; - } - - if (signaled_object == process_info.hProcess) { - process_running = false; - } - else if (signaled_object == pipeov.hEvent) { - // pipe read - DWORD len = 0; - if (GetOverlappedResult(pipe_parent_read, &pipeov, &len, FALSE)) { - pipe_data.insert(pipe_data.end(), pipe_buffer.get(), pipe_buffer.get() + len); - (void)ReadFile(pipe_parent_read, pipe_buffer.get(), (DWORD)pipe_buffer.size(), nullptr, &pipeov); - } - else { - DWORD err = GetLastError(); - if (err == ERROR_HANDLE_EOF || err == ERROR_BROKEN_PIPE) { - pipe_eof = true; - } - } - } - else if (signaled_object == sockov.hEvent) { - if (accept_pending) { - // AcceptEx, client_socker is connected and first chunk of data is received - DWORD len = 0; - if (GetOverlappedResult(reinterpret_cast(m_server_sock), &sockov, &len, FALSE)) { - accept_pending = false; - if (recv_by_socket) sock_data.insert(sock_data.end(), sock_buffer.get(), sock_buffer.get() + len); - - if (len == 0) { - socket_eof = true; - ::closesocket(client_socket); - } - else { - // reset the overlapped since we reuse it for different handle - auto event = sockov.hEvent; - sockov = {}; - sockov.hEvent = event; - - (void)ReadFile(reinterpret_cast(client_socket), sock_buffer.get(), - (DWORD)sock_buffer.size(), nullptr, &sockov); - } - } - } - else { - // ReadFile - DWORD len = 0; - if (GetOverlappedResult(reinterpret_cast(client_socket), &sockov, &len, FALSE)) { - if (recv_by_socket) sock_data.insert(sock_data.end(), sock_buffer.get(), sock_buffer.get() + len); - if (len == 0) { - socket_eof = true; - ::closesocket(client_socket); - } - else { - (void)ReadFile(reinterpret_cast(client_socket), sock_buffer.get(), - (DWORD)sock_buffer.size(), nullptr, &sockov); - } - } - else { - // err = GetLastError(); - socket_eof = true; - ::closesocket(client_socket); - } - } - } - } - - DWORD exit_ret = 0; - GetExitCodeProcess(process_info.hProcess, &exit_ret); - CloseHandle(process_info.hProcess); - CloseHandle(process_info.hThread); - CloseHandle(pipe_parent_read); - CloseHandle(pipeov.hEvent); - if (recv_by_socket) { - if (!socket_eof) closesocket(client_socket); - CloseHandle(sockov.hEvent); - } - - return static_cast(exit_ret); -} -#else -std::optional asst::Controller::call_command_posix(const std::string& cmd, const bool recv_by_socket, - std::string& pipe_data, std::string& sock_data, - const int64_t timeout, - const std::chrono::steady_clock::time_point start_time) -{ - using namespace std::chrono; - - asst::platform::single_page_buffer pipe_buffer; - asst::platform::single_page_buffer sock_buffer; - - auto check_timeout = [&]() -> bool { - return timeout && timeout < duration_cast(steady_clock::now() - start_time).count(); - }; - - int exit_ret = 0; - m_child = ::fork(); - if (m_child == 0) { - // child process - - ::dup2(m_pipe_in[PIPE_READ], STDIN_FILENO); - ::dup2(m_pipe_out[PIPE_WRITE], STDOUT_FILENO); - ::dup2(m_pipe_out[PIPE_WRITE], STDERR_FILENO); - - // all these are for use by parent only - // close(m_pipe_in[PIPE_READ]); - // close(m_pipe_in[PIPE_WRITE]); - // close(m_pipe_out[PIPE_READ]); - // close(m_pipe_out[PIPE_WRITE]); - - exit_ret = execlp("sh", "sh", "-c", cmd.c_str(), nullptr); - ::exit(exit_ret); - } - else if (m_child > 0) { - // parent process - if (recv_by_socket) { - sockaddr addr {}; - socklen_t len = sizeof(addr); - sock_buffer = asst::platform::single_page_buffer(); - - int client_socket = ::accept(m_server_sock, &addr, &len); - if (client_socket < 0) { - Log.error("accept failed:", strerror(errno)); - ::kill(m_child, SIGKILL); - ::waitpid(m_child, &exit_ret, 0); - return std::nullopt; - } - - ssize_t read_num = ::read(client_socket, sock_buffer.get(), sock_buffer.size()); - while (read_num > 0) { - sock_data.insert(sock_data.end(), sock_buffer.get(), sock_buffer.get() + read_num); - read_num = ::read(client_socket, sock_buffer.get(), sock_buffer.size()); - } - ::shutdown(client_socket, SHUT_RDWR); - ::close(client_socket); - } - else { - do { - ssize_t read_num = ::read(m_pipe_out[PIPE_READ], pipe_buffer.get(), pipe_buffer.size()); - while (read_num > 0) { - pipe_data.insert(pipe_data.end(), pipe_buffer.get(), pipe_buffer.get() + read_num); - read_num = ::read(m_pipe_out[PIPE_READ], pipe_buffer.get(), pipe_buffer.size()); - } - } while (::waitpid(m_child, &exit_ret, WNOHANG) == 0 && !check_timeout()); - } - ::waitpid(m_child, &exit_ret, 0); // if ::waitpid(m_child, &exit_ret, WNOHANG) == 0, repeat it will cause - // ECHILD, so not check the return value - } - else { - // failed to create child process - Log.error("Call `", cmd, "` create process failed, child:", m_child); - return std::nullopt; - } - - return exit_ret; -} -#endif - -std::optional asst::Controller::call_command_tcpip(const std::string& cmd, const bool recv_by_socket, - std::string& pipe_data, std::string& sock_data, - const int64_t timeout, - const std::chrono::steady_clock::time_point start_time) -{ - // TODO: 从上面的 call_command_win32/posix 里抽取出 socket 接收的部分 - if (recv_by_socket) { - Log.error("adb-lite does not support receiving data from socket"); - sock_data.clear(); - return std::nullopt; - } - - // TODO: 实现 timeout,目前暂时忽略 - std::ignore = timeout; - std::ignore = start_time; - - static const std::regex devices_regex(R"(^.+ devices$)"); - static const std::regex release_regex(R"(^.+ kill-server$)"); - static const std::regex connect_regex(R"(^.+ connect (\S+)$)"); - static const std::regex shell_regex(R"(^.+ -s \S+ shell (.+)$)"); - static const std::regex exec_regex(R"(^.+ -s \S+ exec-out (.+)$)"); - static const std::regex push_regex(R"#(^.+ -s \S+ push "(.+)" "(.+)"$)#"); - - // adb devices - if (std::regex_match(cmd, devices_regex)) { - try { - pipe_data = adb::devices(); - return 0; - } - catch (const std::exception& e) { - Log.error("adb devices failed:", e.what()); - return std::nullopt; - } - } - - // adb kill-server - if (std::regex_match(cmd, release_regex)) { - try { - adb::kill_server(); - return 0; - } - catch (const std::exception& e) { - Log.error("adb kill-server failed:", e.what()); - return std::nullopt; - } - } - - std::smatch match; - - // adb connect - // TODO: adb server 尚未实现,第一次连接需要执行一次 adb.exe 启动 daemon - if (std::regex_match(cmd, match, connect_regex)) { - m_adb_client = adb::client::create(match[1].str()); // TODO: compare address with existing (if any) - - try { - pipe_data = m_adb_client->connect(); - return 0; - } - catch (const std::exception& e) { - Log.error("adb connect failed:", e.what()); - // fallback 到 fork adb 进程的方式 - return std::nullopt; - } - } - - // adb shell - if (std::regex_match(cmd, match, shell_regex)) { - if (!m_adb_client) { - Log.error("adb client not initialized"); - return std::nullopt; - } - - std::string command = match[1].str(); - remove_quotes(command); - - try { - pipe_data = m_adb_client->shell(command); - return 0; - } - catch (const std::exception& e) { - Log.error("adb shell failed:", e.what()); - return -1; - } - } - - // adb exec-out - if (std::regex_match(cmd, match, exec_regex)) { - if (!m_adb_client) { - Log.error("adb client not initialized"); - return std::nullopt; - } - - std::string command = match[1].str(); - remove_quotes(command); - - try { - pipe_data = m_adb_client->exec(command); - return 0; - } - catch (const std::exception& e) { - Log.error("adb exec-out failed:", e.what()); - return -1; - } - } - - // adb push - if (std::regex_match(cmd, match, push_regex)) { - if (!m_adb_client) { - Log.error("adb client not initialized"); - return std::nullopt; - } - - try { - m_adb_client->push(match[1].str(), match[2].str(), 0644); - return 0; - } - catch (const std::exception& e) { - Log.error("adb push failed:", e.what()); - return -1; - } - } - - Log.info("adb-lite does not support command:", cmd); - return std::nullopt; -} - -#ifdef _WIN32 -bool asst::Controller::call_and_hup_minitouch_win32(const std::string& cmd, const auto& check_timeout, - std::string& pipe_str) -{ - constexpr int PipeReadBuffSize = 4096ULL; - constexpr int PipeWriteBuffSize = 64 * 1024ULL; - - SECURITY_ATTRIBUTES sa_attr_inherit { - .nLength = sizeof(SECURITY_ATTRIBUTES), - .lpSecurityDescriptor = nullptr, - .bInheritHandle = TRUE, - }; - HANDLE pipe_parent_read = INVALID_HANDLE_VALUE, pipe_child_write = INVALID_HANDLE_VALUE; - HANDLE pipe_child_read = INVALID_HANDLE_VALUE, pipe_parent_write = INVALID_HANDLE_VALUE; - if (!asst::win32::CreateOverlappablePipe(&pipe_parent_read, &pipe_child_write, nullptr, &sa_attr_inherit, - PipeReadBuffSize, true, false) || - !asst::win32::CreateOverlappablePipe(&pipe_child_read, &pipe_parent_write, &sa_attr_inherit, nullptr, - PipeWriteBuffSize, false, false)) { - DWORD err = GetLastError(); - Log.error("Failed to create pipe for minitouch, err", err); - return false; - } - - STARTUPINFOW si {}; - si.cb = sizeof(STARTUPINFOW); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.hStdInput = pipe_child_read; - si.hStdOutput = pipe_child_write; - si.hStdError = pipe_child_write; - - auto cmd_osstr = utils::to_osstring(cmd); - BOOL create_ret = CreateProcessW(NULL, cmd_osstr.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, - &m_minitouch_process_info); - CloseHandle(pipe_child_write); - CloseHandle(pipe_child_read); - pipe_child_write = INVALID_HANDLE_VALUE; - pipe_child_read = INVALID_HANDLE_VALUE; - - if (!create_ret) { - DWORD err = GetLastError(); - Log.error("Failed to create process for minitouch, err", err); - release_minitouch(true); - return false; - } - - auto start_time = std::chrono::steady_clock::now(); - - auto pipe_buffer = std::make_unique(PipeReadBuffSize); - OVERLAPPED pipeov { .hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr) }; - std::ignore = ReadFile(pipe_parent_read, pipe_buffer.get(), PipeReadBuffSize, nullptr, &pipeov); - - while (!need_exit() && check_timeout(start_time)) { - if (pipe_str.find('$') != std::string::npos) { - break; - } - DWORD len = 0; - if (!GetOverlappedResult(pipe_parent_read, &pipeov, &len, FALSE)) { - continue; - } - pipe_str.insert(pipe_str.end(), pipe_buffer.get(), pipe_buffer.get() + len); - std::ignore = ReadFile(pipe_parent_read, pipe_buffer.get(), PipeReadBuffSize, nullptr, &pipeov); - } - - CloseHandle(pipe_parent_read); - pipe_parent_read = INVALID_HANDLE_VALUE; - - m_minitouch_parent_write = pipe_parent_write; - return true; -} -#else -bool asst::Controller::call_and_hup_minitouch_posix(const std::string& cmd, const auto& check_timeout, - std::string& pipe_str) -{ - constexpr int PipeReadBuffSize = 4096ULL; - constexpr int PipeWriteBuffSize = 64 * 1024ULL; - - std::ignore = PipeWriteBuffSize; - - int pipe_to_child[2]; - int pipe_from_child[2]; - - if (::pipe(pipe_to_child)) return false; - if (::pipe(pipe_from_child)) { - ::close(pipe_to_child[0]); - ::close(pipe_to_child[1]); - return false; - } - - ::pid_t pid = fork(); - if (pid < 0) { - Log.error("failed to create process"); - return false; - } - if (pid == 0) { // child process - if (::dup2(pipe_to_child[0], STDIN_FILENO) < 0 || ::close(pipe_to_child[1]) < 0 || - ::close(pipe_from_child[0]) < 0 || ::dup2(pipe_from_child[1], STDOUT_FILENO) < 0 || - ::dup2(pipe_from_child[1], STDERR_FILENO) < 0) { - ::exit(-1); - } - - // set stdin of child to blocking - if (int val = ::fcntl(STDIN_FILENO, F_GETFL); val != -1 && (val & O_NONBLOCK)) { - val &= ~O_NONBLOCK; - ::fcntl(STDIN_FILENO, F_SETFL, val); - } - -#ifndef __APPLE__ - ::prctl(PR_SET_PDEATHSIG, SIGTERM); -#endif - - ::execlp("/bin/sh", "sh", "-c", cmd.c_str(), nullptr); - exit(-1); - } - - m_minitouch_process = pid; - m_write_to_minitouch_fd = pipe_to_child[1]; - - if (::close(pipe_from_child[1]) < 0 || ::close(pipe_to_child[0]) < 0) { - release_minitouch(true); - return false; - } - - // set stdout to non blocking - if (int val = ::fcntl(pipe_from_child[0], F_GETFL); val != -1) { - val |= O_NONBLOCK; - ::fcntl(pipe_from_child[0], F_SETFL, val); - } - else { - release_minitouch(true); - return false; - } - const auto start_time = std::chrono::steady_clock::now(); - - while (true) { - if (need_exit()) { - release_minitouch(true); - return false; - } - if (!check_timeout(start_time)) { - Log.info("unable to find $ from pipe_str:", Logger::separator::newline, pipe_str); - release_minitouch(true); - return false; - } - - if (pipe_str.find('$') != std::string::npos) { - break; - } - - char buf_from_child[PipeReadBuffSize]; - ssize_t ret = ::read(pipe_from_child[0], buf_from_child, PipeReadBuffSize); - if (ret <= 0) continue; - pipe_str.insert(pipe_str.end(), buf_from_child, buf_from_child + ret); - } - - ::dup2(::open("/dev/null", O_WRONLY), pipe_from_child[0]); - return true; -} -#endif - -bool asst::Controller::call_and_hup_minitouch_tcpip(const std::string& cmd, const int timeout, std::string& pipe_str) -{ - static const std::regex shell_regex(R"(^.+ -s \S+ shell (.+)$)"); - std::smatch match; - - if (std::regex_match(cmd, match, shell_regex)) { - if (!m_adb_client) { - Log.error("adb client not initialized"); - return false; - } - - std::string command = match[1].str(); - remove_quotes(command); - - try { - m_minitouch_handle = m_adb_client->interactive_shell(command); - } - catch (const std::exception& e) { - Log.error("adb shell failed:", e.what()); - release_minitouch(true); - return false; - } - } - else { - Log.error("unknown command to call minitouch:", cmd); - return false; - } - - while (true) { - std::string buffer; - try { - buffer += m_minitouch_handle->read(timeout); - } - catch (const std::exception& e) { - Log.error("read minitouch handle failed", e.what()); - release_minitouch(true); - return false; - } - // Timeout - if (buffer.empty()) { - Log.info("unable to find $ from pipe_str:", Logger::separator::newline, pipe_str); - release_minitouch(true); - return false; - } - // Read new data - pipe_str += buffer; - if (pipe_str.find('$') != std::string::npos) { - break; - } - } - - return true; -} - -bool asst::Controller::input_to_minitouch_adb(const std::string& cmd) -{ - if (m_minitouch_handle == nullptr) { - Log.error("Minitouch connect not active"); - return false; - } - - try { - m_minitouch_handle->write(cmd); - } - catch (const std::exception& e) { - Log.error("Failed to write to minitouch", e.what()); - return false; - } - - return true; -} - -bool asst::Controller::remove_quotes(std::string& data) -{ - if (data.size() < 2) return false; - - if (data.front() == '"' && data.back() == '"') { - data.erase(data.begin()); - data.pop_back(); - return true; - } - - return false; + std::unique_lock image_lock(m_image_mutex); + return m_controller->screencap(m_cache_image, allow_reconnect); } diff --git a/src/MaaCore/Controller/Controller.h b/src/MaaCore/Controller/Controller.h index 274959fc12..d7e6108b75 100644 --- a/src/MaaCore/Controller/Controller.h +++ b/src/MaaCore/Controller/Controller.h @@ -1,14 +1,5 @@ #pragma once -#ifdef _WIN32 -#include "Utils/Platform/SafeWindows.h" -#include -#else -#include -#include -#include -#endif - #include #include #include @@ -16,6 +7,18 @@ #include #include +#ifdef _WIN32 +#include "Platform/Win32IO.h" +#else +#include "Platform/PosixIO.h" +#endif +#include "Platform/AdbLiteIO.h" + +#include "ControllerAPI.h" +#include "ControllerFactory.h" + +#include "ControlScaleProxy.h" + #include "Common/AsstMsg.h" #include "Common/AsstTypes.h" #include "InstHelper.h" @@ -36,8 +39,8 @@ namespace asst ~Controller(); bool connect(const std::string& adb_path, const std::string& address, const std::string& config); - bool inited() const noexcept; - void set_minitouch_enabled(bool enable, bool maa_touch = false) noexcept; + bool inited() noexcept; + void set_touch_mode(const TouchMode& mode) noexcept; void set_swipe_with_pause(bool enable) noexcept; void set_adb_lite_enabled(bool enable) noexcept; @@ -51,21 +54,16 @@ namespace asst bool click(const Point& p); bool click(const Rect& rect); - bool click_without_scale(const Point& p); - bool click_without_scale(const Rect& rect); bool swipe(const Point& p1, const Point& p2, int duration = 0, bool extra_swipe = false, double slope_in = 1, double slope_out = 1, bool with_pause = false); bool swipe(const Rect& r1, const Rect& r2, int duration = 0, bool extra_swipe = false, double slope_in = 1, double slope_out = 1, bool with_pause = false); - bool swipe_without_scale(const Point& p1, const Point& p2, int duration = 0, bool extra_swipe = false, - double slope_in = 1, double slope_out = 1, bool with_pause = false); - bool swipe_without_scale(const Rect& r1, const Rect& r2, int duration = 0, bool extra_swipe = false, - double slope_in = 1, double slope_out = 1, bool with_pause = false); + + bool inject_input_event(InputEvent& event); bool press_esc(); - bool support_swipe_with_pause() const noexcept; - bool support_precise_swipe() const noexcept; + ControlFeat::Feat support_features(); std::pair get_scale_size() const noexcept; @@ -73,361 +71,30 @@ namespace asst Controller& operator=(Controller&&) = delete; private: - std::optional call_command(const std::string& cmd, int64_t timeout = 20000, - bool allow_reconnect = true, bool recv_by_socket = false); - -#ifdef _WIN32 - std::optional call_command_win32(const std::string& cmd, const bool recv_by_socket, std::string& pipe_data, - std::string& sock_data, const int64_t timeout, - const std::chrono::steady_clock::time_point start_time); -#else - std::optional call_command_posix(const std::string& cmd, const bool recv_by_socket, std::string& pipe_data, - std::string& sock_data, const int64_t timeout, - const std::chrono::steady_clock::time_point start_time); -#endif - - std::optional call_command_tcpip(const std::string& cmd, const bool recv_by_socket, std::string& pipe_data, - std::string& sock_data, const int64_t timeout, - const std::chrono::steady_clock::time_point start_time); - - void release(); - void kill_adb_daemon(); - void make_instance_inited(bool inited); - - void close_socket() noexcept; - std::optional init_socket(const std::string& local_address); - - using DecodeFunc = std::function; - bool screencap(const std::string& cmd, const DecodeFunc& decode_func, bool allow_reconnect = false, - bool by_socket = false); - void clear_lf_info(); cv::Mat get_resized_image_cache() const; - Point rand_point_in_rect(const Rect& rect); - - void random_delay() const; void clear_info() noexcept; void callback(AsstMsg msg, const json::value& details); - bool call_and_hup_minitouch(); -#ifdef _WIN32 - bool call_and_hup_minitouch_win32(const std::string& cmd, const auto& check_timeout, std::string& pipe_str); -#else - bool call_and_hup_minitouch_posix(const std::string& cmd, const auto& check_timeout, std::string& pipe_str); -#endif - bool call_and_hup_minitouch_tcpip(const std::string& cmd, const int timeout, std::string& pipe_str); - - bool input_to_minitouch(const std::string& cmd); - bool input_to_minitouch_adb(const std::string& cmd); - void release_minitouch(bool force = false); - - // 转换 data 中的 CRLF 为 LF:有些模拟器自带的 adb,exec-out 输出的 \n 会被替换成 \r\n, - // 导致解码错误,所以这里转一下回来(点名批评 mumu 和雷电) - static bool convert_lf(std::string& data); - - // adb 的 shell 请求无法识别用双引号包裹的字符串,所以这里去掉双引号 - static bool remove_quotes(std::string& data); - AsstCallback m_callback; std::minstd_rand m_rand_engine; - std::mutex m_callcmd_mutex; + asst::PlatformType m_platform_type = PlatformType::Native; - // adb-lite properties - bool m_use_adb_lite = false; - std::shared_ptr m_adb_client = nullptr; - std::shared_ptr m_minitouch_handle = nullptr; + asst::ControllerType m_controller_type = ControllerType::Minitouch; -#ifdef _WIN32 + std::shared_ptr m_controller = nullptr; - ASST_AUTO_DEDUCED_ZERO_INIT_START - WSADATA m_wsa_data {}; - SOCKET m_server_sock = INVALID_SOCKET; - sockaddr_in m_server_sock_addr {}; - LPFN_ACCEPTEX m_server_accept_ex = nullptr; - ASST_AUTO_DEDUCED_ZERO_INIT_END + std::unique_ptr m_controller_factory = nullptr; -#else - int m_server_sock = -1; - sockaddr_in m_server_sock_addr {}; - static constexpr int PIPE_READ = 0; - static constexpr int PIPE_WRITE = 1; - int m_pipe_in[2] = { 0 }; - int m_pipe_out[2] = { 0 }; - int m_child = 0; -#endif - - struct AdbProperty - { - /* command */ - std::string connect; - std::string call_minitouch; - std::string call_maatouch; - std::string click; - std::string swipe; - std::string press_esc; - - std::string screencap_raw_by_nc; - std::string screencap_raw_with_gzip; - std::string screencap_encode; - std::string release; - - std::string start; - std::string stop; - - /* properties */ - enum class ScreencapEndOfLine - { - UnknownYet, - CRLF, - LF, - CR - } screencap_end_of_line = ScreencapEndOfLine::UnknownYet; - - enum class ScreencapMethod - { - UnknownYet, - // Default, - RawByNc, - RawWithGzip, - Encode - } screencap_method = ScreencapMethod::UnknownYet; - } m_adb; - - bool m_swipe_with_pause_enabled = false; - - bool m_minitouch_enabled = true; // 开关 - bool m_use_maa_touch = false; - bool m_minitouch_available = false; // 状态 - -#ifdef _WIN32 - HANDLE m_minitouch_parent_write = INVALID_HANDLE_VALUE; - ASST_AUTO_DEDUCED_ZERO_INIT_START - PROCESS_INFORMATION m_minitouch_process_info = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; - ASST_AUTO_DEDUCED_ZERO_INIT_END -#else - ::pid_t m_minitouch_process = -1; - int m_write_to_minitouch_fd = -1; - // TODO -#endif + std::shared_ptr m_scale_proxy = nullptr; std::string m_uuid; - inline static std::string m_adb_release; // 开了 adb daemon,但是没连上模拟器的时候, - // m_adb 并不会存下 release 的命令,但最后仍然需要一次释放。 - std::pair m_scale_size = { WindowWidthDefault, WindowHeightDefault }; - double m_control_scale = 1.0; - int m_width = 0; - int m_height = 0; - bool m_support_socket = false; - bool m_server_started = false; - bool m_inited = false; - size_t m_screencap_data_general_size = 0; - inline static int m_instance_count = 0; + std::pair m_scale_size = { WindowWidthDefault, WindowHeightDefault }; mutable std::shared_mutex m_image_mutex; cv::Mat m_cache_image; - - private: - struct MinitouchProps - { - int max_contacts = 0; - int max_x = 0; - int max_y = 0; - int max_pressure = 0; - - double x_scaling = 0; - double y_scaling = 0; - int orientation = 0; - } m_minitouch_props; - - class Minitoucher - { - public: - using InputFunc = std::function; - - public: - static constexpr int DefaultClickDelay = 50; - static constexpr int DefaultSwipeDelay = 2; - static constexpr int ExtraDelay = 0; - - Minitoucher(InputFunc func, const MinitouchProps& props, bool auto_sleep = true) - : m_input_func(func), m_props(props), m_auto_sleep(auto_sleep) - {} - - ~Minitoucher() - { - if (m_auto_sleep) { - sleep(); - } - } - - bool reset() { return m_input_func(reset_cmd()); } - bool commit() { return m_input_func(commit_cmd()); } - bool down(int x, int y, int wait_ms = DefaultClickDelay, bool with_commit = true, int contact = 0) - { - return m_input_func(down_cmd(x, y, wait_ms, with_commit, contact)); - } - bool move(int x, int y, int wait_ms = DefaultSwipeDelay, bool with_commit = true, int contact = 0) - { - return m_input_func(move_cmd(x, y, wait_ms, with_commit, contact)); - } - bool up(int wait_ms = DefaultClickDelay, bool with_commit = true, int contact = 0) - { - return m_input_func(up_cmd(wait_ms, with_commit, contact)); - } - bool key_down(int key_code, int wait_ms = DefaultClickDelay, bool with_commit = true) - { - return m_input_func(key_down_cmd(key_code, wait_ms, with_commit)); - } - bool key_up(int key_code, int wait_ms = DefaultClickDelay, bool with_commit = true) - { - return m_input_func(key_up_cmd(key_code, wait_ms, with_commit)); - } - bool wait(int ms) { return m_input_func(wait_cmd(ms)); } - void clear() noexcept { m_wait_ms_count = 0; } - - private: - [[nodiscard]] std::string reset_cmd() const noexcept { return "r\n"; } - [[nodiscard]] std::string commit_cmd() const noexcept { return "c\n"; } -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4996) -#endif - [[nodiscard]] std::string down_cmd(int x, int y, int wait_ms = DefaultClickDelay, bool with_commit = true, - int contact = 0) - { - // mac 编不过 - // std::string str = std::format("d {} {} {} {}\n", contact, x, y, pressure); - - char buff[64] = { 0 }; - auto [c_x, c_y] = scale(x, y); - sprintf(buff, "d %d %d %d %d\n", contact, c_x, c_y, m_props.max_pressure); - std::string str = buff; - - if (with_commit) str += commit_cmd(); - if (wait_ms) str += wait_cmd(wait_ms); - return str; - } - - [[nodiscard]] std::string move_cmd(int x, int y, int wait_ms = DefaultSwipeDelay, bool with_commit = true, - int contact = 0) - { - char buff[64] = { 0 }; - auto [c_x, c_y] = scale(x, y); - sprintf(buff, "m %d %d %d %d\n", contact, c_x, c_y, m_props.max_pressure); - std::string str = buff; - - if (with_commit) str += commit_cmd(); - if (wait_ms) str += wait_cmd(wait_ms); - return str; - } - - [[nodiscard]] std::string up_cmd(int wait_ms = DefaultClickDelay, bool with_commit = true, int contact = 0) - { - char buff[16] = { 0 }; - sprintf(buff, "u %d\n", contact); - std::string str = buff; - - if (with_commit) str += commit_cmd(); - if (wait_ms) str += wait_cmd(wait_ms); - return str; - } - - [[nodiscard]] std::string key_down_cmd(int key_code, int wait_ms = DefaultClickDelay, - bool with_commit = true) - { - char buff[64] = { 0 }; - sprintf(buff, "k %d d\n", key_code); - std::string str = buff; - - if (with_commit) str += commit_cmd(); - if (wait_ms) str += wait_cmd(wait_ms); - return str; - } - - [[nodiscard]] std::string key_up_cmd(int key_code, int wait_ms = DefaultClickDelay, bool with_commit = true) - { - char buff[64] = { 0 }; - sprintf(buff, "k %d u\n", key_code); - std::string str = buff; - - if (with_commit) str += commit_cmd(); - if (wait_ms) str += wait_cmd(wait_ms); - return str; - } - - [[nodiscard]] std::string wait_cmd(int ms) - { - m_wait_ms_count += ms; - char buff[16] = { 0 }; - sprintf(buff, "w %d\n", ms); - return buff; - } -#ifdef _MSC_VER -#pragma warning(pop) -#endif - void sleep() - { - using namespace std::chrono_literals; - std::this_thread::sleep_for(m_wait_ms_count * 1ms); - m_wait_ms_count = 0; - } - - private: - Point scale(int x, int y) const noexcept - { - switch (m_props.orientation) { - case 0: - default: - return { - static_cast(x * m_props.x_scaling), - static_cast(y * m_props.y_scaling), - }; - case 1: - return { - m_props.max_y - static_cast(y * m_props.y_scaling), - static_cast(x * m_props.x_scaling), - }; - case 2: - return { - m_props.max_x - static_cast(x * m_props.x_scaling), - m_props.max_y - static_cast(y * m_props.y_scaling), - }; - case 3: - return { - static_cast(y * m_props.y_scaling), - m_props.max_x - static_cast(x * m_props.x_scaling), - }; - } - } - - const std::function m_input_func = nullptr; - const MinitouchProps& m_props; - int m_wait_ms_count = ExtraDelay; - bool m_auto_sleep = false; - }; - - private: -#ifdef _WIN32 - // for Windows socket - class WsaHelper : public SingletonHolder - { - friend class SingletonHolder; - - public: - virtual ~WsaHelper() override { WSACleanup(); } - bool operator()() const noexcept { return m_supports; } - - private: - WsaHelper() - { - m_supports = WSAStartup(MAKEWORD(2, 2), &m_wsa_data) == 0 && m_wsa_data.wVersion == MAKEWORD(2, 2); - } - WSADATA m_wsa_data = { 0 }; - bool m_supports = false; - }; -#endif }; } // namespace asst diff --git a/src/MaaCore/Controller/ControllerAPI.h b/src/MaaCore/Controller/ControllerAPI.h new file mode 100644 index 0000000000..795b01a969 --- /dev/null +++ b/src/MaaCore/Controller/ControllerAPI.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include "Common/AsstTypes.h" +#include "Utils/NoWarningCVMat.h" + +namespace asst +{ + struct InputEvent; + + enum class ControllerType + { + Adb, + Minitouch, + Maatouch, + }; + + class ControllerAPI + { + public: + virtual ~ControllerAPI() = default; + + virtual bool connect(const std::string& adb_path, const std::string& address, const std::string& config) = 0; + virtual bool inited() const noexcept = 0; + virtual void set_swipe_with_pause(bool enable) noexcept = 0; + + virtual const std::string& get_uuid() const = 0; + + virtual bool screencap(cv::Mat& image_payload, bool allow_reconnect = false) = 0; + + virtual bool start_game(const std::string& client_type) = 0; + virtual bool stop_game() = 0; + + virtual bool click(const Point& p) = 0; + + virtual bool swipe(const Point& p1, const Point& p2, int duration = 0, bool extra_swipe = false, + double slope_in = 1, double slope_out = 1, bool with_pause = false) = 0; + // TODO: 抽象出gesture类 + + virtual bool inject_input_event(const InputEvent& event) = 0; + + virtual bool press_esc() = 0; + virtual ControlFeat::Feat support_features() const noexcept = 0; + + virtual std::pair get_screen_res() const noexcept = 0; + + ControllerAPI& operator=(const ControllerAPI&) = delete; + ControllerAPI& operator=(ControllerAPI&&) = delete; + }; + + struct InputEvent + { + enum class Type + { + TOUCH_DOWN, + TOUCH_UP, + TOUCH_MOVE, + TOUCH_RESET, + KEY_DOWN, + KEY_UP, + WAIT_MS, + COMMIT, + UNKNOWN = INT_MAX + } type = Type::UNKNOWN; + + int pointerId = 0; + asst::Point point = { 0, 0 }; + int keycode = 0; + long milisec = 0; + }; +} diff --git a/src/MaaCore/Controller/ControllerFactory.h b/src/MaaCore/Controller/ControllerFactory.h new file mode 100644 index 0000000000..e8274e2559 --- /dev/null +++ b/src/MaaCore/Controller/ControllerFactory.h @@ -0,0 +1,48 @@ +#pragma once + +#include "AdbController.h" +#include "ControllerAPI.h" +#include "MaatouchController.h" +#include "MinitouchController.h" + +namespace asst +{ + class ControllerFactory + { + public: + ControllerFactory(const AsstCallback& callback, Assistant* inst) : m_callback(callback), m_inst(inst) {} + ~ControllerFactory() = default; + + std::shared_ptr create_controller(ControllerType type, const std::string& adb_path, + const std::string& address, const std::string& config, + PlatformType platform_type) + { + std::shared_ptr controller; + try { + switch (type) { + case ControllerType::Adb: + controller = std::make_shared(m_callback, m_inst, platform_type); + break; + case ControllerType::Minitouch: + controller = std::make_shared(m_callback, m_inst, platform_type); + break; + case ControllerType::Maatouch: + controller = std::make_shared(m_callback, m_inst, platform_type); + break; + default: + return nullptr; + } + } + catch (const std::exception& e) { + Log.error("Cannot create controller: {}", e.what()); + return nullptr; + } + controller->connect(adb_path, address, config); + return controller; + } + + private: + AsstCallback m_callback; + Assistant* m_inst; + }; +} diff --git a/src/MaaCore/Controller/MaatouchController.h b/src/MaaCore/Controller/MaatouchController.h new file mode 100644 index 0000000000..3d1ede287c --- /dev/null +++ b/src/MaaCore/Controller/MaatouchController.h @@ -0,0 +1,19 @@ +#pragma once + +#include "MinitouchController.h" + +namespace asst +{ + class MaatouchController : public MinitouchController + { + public: + MaatouchController(const AsstCallback& callback, Assistant* inst, PlatformType type) + : MinitouchController(callback, inst, type) + { + m_use_maa_touch = true; + } + MaatouchController(const MaatouchController&) = delete; + MaatouchController(MaatouchController&&) = delete; + virtual ~MaatouchController() = default; + }; +} diff --git a/src/MaaCore/Controller/MinitouchController.cpp b/src/MaaCore/Controller/MinitouchController.cpp new file mode 100644 index 0000000000..29ca313772 --- /dev/null +++ b/src/MaaCore/Controller/MinitouchController.cpp @@ -0,0 +1,359 @@ +#include "MinitouchController.h" + +#include + +#include "Common/AsstTypes.h" +#include "Config/GeneralConfig.h" +#include "Utils/Logger.hpp" +#include "Utils/StringMisc.hpp" + +asst::MinitouchController::~MinitouchController() +{ + LogTraceFunction; + + release_minitouch(); +} + +void asst::MinitouchController::release_minitouch([[maybe_unused]] bool force) +{ + m_minitoucher.reset(); + m_minitouch_handler.reset(); +} + +bool asst::MinitouchController::call_and_hup_minitouch() +{ + LogTraceFunction; + release_minitouch(true); + + std::string cmd = m_use_maa_touch ? m_adb.call_maatouch : m_adb.call_minitouch; + Log.info(cmd); + + std::string pipe_str; + + m_minitouch_handler = m_platform_io->interactive_shell(cmd); + if (!m_minitouch_handler) { + Log.error("unable to start minitouch"); + return false; + } + + auto check_timeout = [&](const auto& start_time) -> bool { + using namespace std::chrono_literals; + return std::chrono::steady_clock::now() - start_time < 3s; + }; + + const auto start_time = std::chrono::steady_clock::now(); + while (true) { + if (need_exit()) { + release_minitouch(true); + return false; + } + + if (!check_timeout(start_time)) { + Log.info("unable to find $ from pipe_str:", Logger::separator::newline, pipe_str); + release_minitouch(true); + return false; + } + + if (pipe_str.find('$') != std::string::npos) { + break; + } + + pipe_str += m_minitouch_handler->read(); + } + + Log.info("pipe str", Logger::separator::newline, pipe_str); + + convert_lf(pipe_str); + size_t s_pos = pipe_str.find('^'); + size_t e_pos = pipe_str.find('\n', s_pos); + if (s_pos == std::string::npos || e_pos == std::string::npos) { + Log.error("Failed to find ^ in minitouch pipe"); + release_minitouch(true); + return false; + } + std::string key_info = pipe_str.substr(s_pos + 1, e_pos - s_pos - 1); + Log.info("minitouch key props", key_info); + int size_1 = 0, size_2 = 0; + std::stringstream ss; + ss << key_info; + ss >> m_minitouch_props.max_contacts; + ss >> size_1; + ss >> size_2; + ss >> m_minitouch_props.max_pressure; + + // 有些模拟器在竖屏分辨率时,这里的输出是反过来的 + // 考虑到应该没人竖屏玩明日方舟,所以取较大值为 x,较小值为 y + m_minitouch_props.max_x = std::max(size_1, size_2); + m_minitouch_props.max_y = std::min(size_1, size_2); + + m_minitouch_props.x_scaling = static_cast(m_minitouch_props.max_x) / m_width; + m_minitouch_props.y_scaling = static_cast(m_minitouch_props.max_y) / m_height; + + return true; +} + +bool asst::MinitouchController::input_to_minitouch(const std::string& cmd) +{ + return m_minitouch_handler->write(cmd); +} + +void asst::MinitouchController::set_swipe_with_pause(bool enable) noexcept +{ + m_swipe_with_pause_enabled = enable; +} + +bool asst::MinitouchController::use_swipe_with_pause() const noexcept +{ + return m_swipe_with_pause_enabled && (!m_adb.press_esc.empty() || m_use_maa_touch); +} + +bool asst::MinitouchController::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"); + } + + Log.trace(m_use_maa_touch ? "maatouch" : "minitouch", "click:", p); + bool ret = m_minitoucher->down(p.x, p.y) && m_minitoucher->up(); + m_minitoucher->extra_sleep(); + return ret; +} + +bool asst::MinitouchController::swipe(const Point& p1, const Point& p2, int duration, bool extra_swipe, double slope_in, + double slope_out, 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(); + Log.trace(m_use_maa_touch ? "maatouch" : "minitouch", "swipe", p1, p2, duration, extra_swipe, slope_in, slope_out); + m_minitoucher->down(x1, y1); + + constexpr int TimeInterval = Minitoucher::DefaultSwipeDelay; + + auto cubic_spline = [](double slope_0, double slope_1, double t) { + const double a = slope_0; + const double b = -(2 * slope_0 + slope_1 - 3); + const double c = -(-slope_0 - slope_1 + 2); + return a * t + b * std::pow(t, 2) + c * std::pow(t, 3); + }; // TODO: move this to math.hpp + + bool need_pause = with_pause && use_swipe_with_pause(); + std::future pause_future; + auto minitouch_move = [&](int _x1, int _y1, int _x2, int _y2, int _duration) { + for (int cur_time = TimeInterval; cur_time < _duration; cur_time += TimeInterval) { + double progress = cubic_spline(slope_in, slope_out, static_cast(cur_time) / duration); + int cur_x = static_cast(std::lerp(_x1, _x2, progress)); + int cur_y = static_cast(std::lerp(_y1, _y2, progress)); + if (need_pause && std::sqrt(std::pow(cur_x - _x1, 2) + std::pow(cur_y - _y1, 2)) > + opt.swipe_with_pause_required_distance) { + need_pause = false; + if (m_use_maa_touch) { + constexpr int EscKeyCode = 111; + m_minitoucher->key_down(EscKeyCode); + m_minitoucher->key_up(EscKeyCode, 0); + } + else { + pause_future = std::async(std::launch::async, [&]() { press_esc(); }); + } + } + if (cur_x < 0 || cur_x > m_minitouch_props.max_x || cur_y < 0 || cur_y > m_minitouch_props.max_y) { + continue; + } + m_minitoucher->move(cur_x, cur_y); + } + if (_x2 >= 0 && _x2 <= m_minitouch_props.max_x && _y2 >= 0 && _y2 <= m_minitouch_props.max_y) { + m_minitoucher->move(_x2, _y2); + } + }; + + constexpr int DefaultDuration = 200; + minitouch_move(x1, y1, x2, y2, duration ? duration : DefaultDuration); + + if (extra_swipe && opt.minitouch_extra_swipe_duration > 0) { + constexpr int ExtraEndDelay = 100; // 停留终点 + m_minitoucher->wait(ExtraEndDelay); + minitouch_move(x2, y2, x2, y2 - opt.minitouch_extra_swipe_dist, opt.minitouch_extra_swipe_duration); + } + bool ret = m_minitoucher->up(); + m_minitoucher->extra_sleep(); + return ret; +} + +bool asst::MinitouchController::inject_input_event(const InputEvent& event) +{ + LogTraceFunction; + + if (!m_minitoucher) { + Log.error("minitoucher is not initialized"); + return false; + } + + switch (event.type) { + case InputEvent::Type::KEY_DOWN: + return m_minitoucher->key_down(event.keycode, 0, false); + case InputEvent::Type::KEY_UP: + return m_minitoucher->key_up(event.keycode, 0, false); + case InputEvent::Type::TOUCH_DOWN: + return m_minitoucher->down(event.point.x, event.point.y, 0, false, event.pointerId); + case InputEvent::Type::TOUCH_UP: + return m_minitoucher->up(0, false, event.pointerId); + case InputEvent::Type::TOUCH_MOVE: + return m_minitoucher->move(event.point.x, event.point.y, 0, false, event.pointerId); + case InputEvent::Type::TOUCH_RESET: + return m_minitoucher->reset(); + case InputEvent::Type::WAIT_MS: + return m_minitoucher->wait(event.milisec); + case InputEvent::Type::COMMIT: + return m_minitoucher->commit(); + case InputEvent::Type::UNKNOWN: + default: + Log.error("unknown input event type"); + return false; + } +} + +asst::ControlFeat::Feat asst::MinitouchController::support_features() const noexcept +{ + auto feat = ControlFeat::PRECISE_SWIPE; + if (use_swipe_with_pause()) { + feat |= ControlFeat::SWIPE_WITH_PAUSE; + } + return feat; +} + +void asst::MinitouchController::clear_info() noexcept +{ + AdbController::clear_info(); + m_minitouch_available = false; + m_minitouch_props = decltype(m_minitouch_props)(); +} + +bool asst::MinitouchController::probe_minitouch(const AdbCfg& adb_cfg, + std::function cmd_replace) +{ + LogTraceFunction; + + std::string_view touch_program; + if (m_use_maa_touch) { + touch_program = "maatouch"; + m_minitouch_props.orientation = 0; + } + else { + std::string abilist = call_command(cmd_replace(adb_cfg.abilist)).value_or(std::string()); + for (const auto& abi : Config.get_options().minitouch_programs_order) { + if (abilist.find(abi) != std::string::npos) { + touch_program = abi; + break; + } + } + std::string orientation_str = call_command(cmd_replace(adb_cfg.orientation)).value_or("0"); + if (!orientation_str.empty()) { + char first = orientation_str.front(); + if (first == '0' || first == '1' || first == '2' || first == '3') { + m_minitouch_props.orientation = static_cast(first - '0'); + } + } + } + Log.info("touch_program", touch_program, "orientation", m_minitouch_props.orientation); + + if (touch_program.empty()) return false; + + auto minitouch_cmd_rep = [&](const std::string& cfg_cmd) -> std::string { + using namespace asst::utils::path_literals; + return utils::string_replace_all( + cmd_replace(cfg_cmd), + { + { "[minitouchLocalPath]", + utils::path_to_utf8_string(ResDir.get() / "minitouch"_p / touch_program / "minitouch"_p) }, + { "[minitouchWorkingFile]", m_uuid }, + }); + }; + + if (!call_command(minitouch_cmd_rep(adb_cfg.push_minitouch))) return false; + if (!call_command(minitouch_cmd_rep(adb_cfg.chmod_minitouch))) return false; + + m_adb.call_minitouch = minitouch_cmd_rep(adb_cfg.call_minitouch); + m_adb.call_maatouch = minitouch_cmd_rep(adb_cfg.call_maatouch); + + if (!call_and_hup_minitouch()) return false; + + return true; +} + +bool asst::MinitouchController::connect(const std::string& adb_path, const std::string& address, + const std::string& config) +{ + LogTraceFunction; + + release_minitouch(); + +#ifdef ASST_DEBUG + if (config == "DEBUG") { + make_instance_inited(true); + return true; + } +#endif + + auto ret = AdbController::connect(adb_path, address, config); + + if (!ret) { + return false; + } + + auto get_info_json = [&]() -> json::value { + return json::object { + { "uuid", m_uuid }, + { "details", + json::object { + { "adb", adb_path }, + { "address", address }, + { "config", config }, + } }, + }; + }; + + std::string display_id; + std::string nc_address = "10.0.2.2"; + uint16_t nc_port = 0; + + auto cmd_replace = [&](const std::string& cfg_cmd) -> std::string { + return utils::string_replace_all(cfg_cmd, { + { "[Adb]", adb_path }, + { "[AdbSerial]", address }, + { "[DisplayId]", display_id }, + { "[NcPort]", std::to_string(nc_port) }, + { "[NcAddress]", nc_address }, + }); + }; + + auto adb_ret = Config.get_adb_cfg(config); + + if (!adb_ret) { + return false; + } + + const auto& adb_cfg = adb_ret.value(); + + m_minitouch_available = probe_minitouch(adb_cfg, cmd_replace); + + if (!m_minitouch_available) { + json::value info = get_info_json() | json::object { + { "what", "TouchModeNotAvailable" }, + { "why", "" }, + }; + callback(AsstMsg::ConnectionInfo, info); + return false; + } + + m_minitoucher = std::make_unique( + std::bind(&MinitouchController::input_to_minitouch, this, std::placeholders::_1), m_minitouch_props); + return true; +} diff --git a/src/MaaCore/Controller/MinitouchController.h b/src/MaaCore/Controller/MinitouchController.h new file mode 100644 index 0000000000..472762c920 --- /dev/null +++ b/src/MaaCore/Controller/MinitouchController.h @@ -0,0 +1,232 @@ +#pragma once + +#include "AdbController.h" + +#include "Config/GeneralConfig.h" + +#include + +namespace asst +{ + class MinitouchController : public AdbController + { + public: + MinitouchController(const AsstCallback& callback, Assistant* inst, PlatformType type) + : AdbController(callback, inst, type) + {} + MinitouchController(const MinitouchController&) = delete; + MinitouchController(MinitouchController&&) = delete; + virtual ~MinitouchController(); + + virtual bool connect(const std::string& adb_path, const std::string& address, + const std::string& config) override; + + virtual void set_swipe_with_pause(bool enable) noexcept override; + + virtual bool click(const Point& p) override; + + virtual bool swipe(const Point& p1, const Point& p2, int duration = 0, bool extra_swipe = false, + double slope_in = 1, double slope_out = 1, bool with_pause = false) override; + + virtual bool inject_input_event(const InputEvent& event) override; + + virtual ControlFeat::Feat support_features() const noexcept override; + + MinitouchController& operator=(const MinitouchController&) = delete; + MinitouchController& operator=(MinitouchController&&) = delete; + + protected: + bool call_and_hup_minitouch(); + + bool probe_minitouch(const AdbCfg& adb_cfg, std::function cmd_replace); + + bool input_to_minitouch(const std::string& cmd); + void release_minitouch(bool force = false); + + bool use_swipe_with_pause() const noexcept; + + virtual void clear_info() noexcept override; + + bool m_minitouch_available = false; + bool m_use_maa_touch = false; + bool m_swipe_with_pause_enabled = false; + + std::shared_ptr m_minitouch_handler = nullptr; + + class Minitoucher; + std::unique_ptr m_minitoucher = nullptr; + + struct MinitouchProps + { + int max_contacts = 0; + int max_x = 0; + int max_y = 0; + int max_pressure = 0; + + double x_scaling = 0; + double y_scaling = 0; + int orientation = 0; + } m_minitouch_props; + + class Minitoucher + { + public: + using InputFunc = std::function; + + public: + static constexpr int DefaultClickDelay = 50; + static constexpr int DefaultSwipeDelay = 2; + static constexpr int ExtraDelay = 0; + + Minitoucher(InputFunc func, const MinitouchProps& props) : m_input_func(func), m_props(props) {} + + ~Minitoucher() = default; + + bool reset() { return m_input_func(reset_cmd()); } + bool commit() { return m_input_func(commit_cmd()); } + bool down(int x, int y, int wait_ms = DefaultClickDelay, bool with_commit = true, int contact = 0) + { + return m_input_func(down_cmd(x, y, wait_ms, with_commit, contact)); + } + bool move(int x, int y, int wait_ms = DefaultSwipeDelay, bool with_commit = true, int contact = 0) + { + return m_input_func(move_cmd(x, y, wait_ms, with_commit, contact)); + } + bool up(int wait_ms = DefaultClickDelay, bool with_commit = true, int contact = 0) + { + return m_input_func(up_cmd(wait_ms, with_commit, contact)); + } + bool key_down(int key_code, int wait_ms = DefaultClickDelay, bool with_commit = true) + { + return m_input_func(key_down_cmd(key_code, wait_ms, with_commit)); + } + bool key_up(int key_code, int wait_ms = DefaultClickDelay, bool with_commit = true) + { + return m_input_func(key_up_cmd(key_code, wait_ms, with_commit)); + } + bool wait(int ms) { return m_input_func(wait_cmd(ms)); } + void clear() noexcept { m_wait_ms_count = 0; } + + void extra_sleep() { sleep(); } + + private: + [[nodiscard]] std::string reset_cmd() const noexcept { return "r\n"; } + [[nodiscard]] std::string commit_cmd() const noexcept { return "c\n"; } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + [[nodiscard]] std::string down_cmd(int x, int y, int wait_ms = DefaultClickDelay, bool with_commit = true, + int contact = 0) + { + // mac 编不过 + // std::string str = std::format("d {} {} {} {}\n", contact, x, y, pressure); + + char buff[64] = { 0 }; + auto [c_x, c_y] = scale(x, y); + sprintf(buff, "d %d %d %d %d\n", contact, c_x, c_y, m_props.max_pressure); + std::string str = buff; + + if (with_commit) str += commit_cmd(); + if (wait_ms) str += wait_cmd(wait_ms); + return str; + } + + [[nodiscard]] std::string move_cmd(int x, int y, int wait_ms = DefaultSwipeDelay, bool with_commit = true, + int contact = 0) + { + char buff[64] = { 0 }; + auto [c_x, c_y] = scale(x, y); + sprintf(buff, "m %d %d %d %d\n", contact, c_x, c_y, m_props.max_pressure); + std::string str = buff; + + if (with_commit) str += commit_cmd(); + if (wait_ms) str += wait_cmd(wait_ms); + return str; + } + + [[nodiscard]] std::string up_cmd(int wait_ms = DefaultClickDelay, bool with_commit = true, int contact = 0) + { + char buff[16] = { 0 }; + sprintf(buff, "u %d\n", contact); + std::string str = buff; + + if (with_commit) str += commit_cmd(); + if (wait_ms) str += wait_cmd(wait_ms); + return str; + } + + [[nodiscard]] std::string key_down_cmd(int key_code, int wait_ms = DefaultClickDelay, + bool with_commit = true) + { + char buff[64] = { 0 }; + sprintf(buff, "k %d d\n", key_code); + std::string str = buff; + + if (with_commit) str += commit_cmd(); + if (wait_ms) str += wait_cmd(wait_ms); + return str; + } + + [[nodiscard]] std::string key_up_cmd(int key_code, int wait_ms = DefaultClickDelay, bool with_commit = true) + { + char buff[64] = { 0 }; + sprintf(buff, "k %d u\n", key_code); + std::string str = buff; + + if (with_commit) str += commit_cmd(); + if (wait_ms) str += wait_cmd(wait_ms); + return str; + } + + [[nodiscard]] std::string wait_cmd(int ms) + { + m_wait_ms_count += ms; + char buff[16] = { 0 }; + sprintf(buff, "w %d\n", ms); + return buff; + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + void sleep() + { + using namespace std::chrono_literals; + std::this_thread::sleep_for(m_wait_ms_count * 1ms); + m_wait_ms_count = 0; + } + + private: + Point scale(int x, int y) const noexcept + { + switch (m_props.orientation) { + case 0: + default: + return { + static_cast(x * m_props.x_scaling), + static_cast(y * m_props.y_scaling), + }; + case 1: + return { + m_props.max_y - static_cast(y * m_props.y_scaling), + static_cast(x * m_props.x_scaling), + }; + case 2: + return { + m_props.max_x - static_cast(x * m_props.x_scaling), + m_props.max_y - static_cast(y * m_props.y_scaling), + }; + case 3: + return { + static_cast(y * m_props.y_scaling), + m_props.max_x - static_cast(x * m_props.x_scaling), + }; + } + } + + const std::function m_input_func = nullptr; + const MinitouchProps& m_props; + int m_wait_ms_count = ExtraDelay; + }; + }; +} // namespace asst diff --git a/src/MaaCore/Controller/Platform/AdbLiteIO.cpp b/src/MaaCore/Controller/Platform/AdbLiteIO.cpp new file mode 100644 index 0000000000..60ca378479 --- /dev/null +++ b/src/MaaCore/Controller/Platform/AdbLiteIO.cpp @@ -0,0 +1,205 @@ +#include "AdbLiteIO.h" + +#include + +#include "Utils/Logger.hpp" + +std::optional asst::AdbLiteIO::call_command(const std::string& cmd, const bool recv_by_socket, + std::string& pipe_data, std::string& sock_data, const int64_t timeout, + const std::chrono::steady_clock::time_point start_time) +{ + // TODO: 从上面的 call_command_win32/posix 里抽取出 socket 接收的部分 + if (recv_by_socket) { + Log.error("adb-lite does not support receiving data from socket"); + sock_data.clear(); + return std::nullopt; + } + + // TODO: 实现 timeout,目前暂时忽略 + std::ignore = timeout; + std::ignore = start_time; + + static const std::regex devices_regex(R"(^.+ devices$)"); + static const std::regex release_regex(R"(^.+ kill-server$)"); + static const std::regex connect_regex(R"(^.+ connect (\S+)$)"); + static const std::regex shell_regex(R"(^.+ -s \S+ shell (.+)$)"); + static const std::regex exec_regex(R"(^.+ -s \S+ exec-out (.+)$)"); + static const std::regex push_regex(R"#(^.+ -s \S+ push "(.+)" "(.+)"$)#"); + + // adb devices + if (std::regex_match(cmd, devices_regex)) { + try { + pipe_data = adb::devices(); + return 0; + } + catch (const std::exception& e) { + Log.error("adb devices failed:", e.what()); + return std::nullopt; + } + } + + // adb kill-server + if (std::regex_match(cmd, release_regex)) { + try { + adb::kill_server(); + return 0; + } + catch (const std::exception& e) { + Log.error("adb kill-server failed:", e.what()); + return std::nullopt; + } + } + + std::smatch match; + + // adb connect + // TODO: adb server 尚未实现,第一次连接需要执行一次 adb.exe 启动 daemon + if (std::regex_match(cmd, match, connect_regex)) { + m_adb_client = adb::client::create(match[1].str()); // TODO: compare address with existing (if any) + + try { + pipe_data = m_adb_client->connect(); + return 0; + } + catch (const std::exception& e) { + Log.error("adb connect failed:", e.what()); + // fallback 到 fork adb 进程的方式 + return std::nullopt; + } + } + + // adb shell + if (std::regex_match(cmd, match, shell_regex)) { + if (!m_adb_client) { + Log.error("adb client not initialized"); + return std::nullopt; + } + + std::string command = match[1].str(); + remove_quotes(command); + + try { + pipe_data = m_adb_client->shell(command); + return 0; + } + catch (const std::exception& e) { + Log.error("adb shell failed:", e.what()); + return -1; + } + } + + // adb exec-out + if (std::regex_match(cmd, match, exec_regex)) { + if (!m_adb_client) { + Log.error("adb client not initialized"); + return std::nullopt; + } + + std::string command = match[1].str(); + remove_quotes(command); + + try { + pipe_data = m_adb_client->exec(command); + return 0; + } + catch (const std::exception& e) { + Log.error("adb exec-out failed:", e.what()); + return -1; + } + } + + // adb push + if (std::regex_match(cmd, match, push_regex)) { + if (!m_adb_client) { + Log.error("adb client not initialized"); + return std::nullopt; + } + + try { + m_adb_client->push(match[1].str(), match[2].str(), 0644); + return 0; + } + catch (const std::exception& e) { + Log.error("adb push failed:", e.what()); + return -1; + } + } + + Log.info("adb-lite does not support command:", cmd); + return std::nullopt; +} + +std::shared_ptr asst::AdbLiteIO::interactive_shell(const std::string& cmd) +{ + static const std::regex shell_regex(R"(^.+ -s \S+ shell (.+)$)"); + std::smatch match; + + if (std::regex_match(cmd, match, shell_regex)) { + if (!m_adb_client) { + Log.error("adb client not initialized"); + return nullptr; + } + + std::string command = match[1].str(); + remove_quotes(command); + + try { + return std::make_shared(m_adb_client->interactive_shell(command)); + } + catch (const std::exception& e) { + Log.error("adb shell failed:", e.what()); + return nullptr; + } + } + else { + Log.error("unknown command to call interactive shell:", cmd); + return nullptr; + } +} + +void asst::AdbLiteIO::release_adb(const std::string& adb_release, int64_t timeout) +{ + if (m_adb_client) { + std::string pipe_data; + std::string sock_data; + auto start_time = std::chrono::steady_clock::now(); + + call_command(adb_release, false, pipe_data, sock_data, timeout, start_time); + } +} + +bool asst::AdbLiteIO::remove_quotes(std::string& data) +{ + if (data.size() < 2) return false; + + if (data.front() == '"' && data.back() == '"') { + data.erase(data.begin()); + data.pop_back(); + return true; + } + + return false; +} + +bool asst::IOHandlerAdbLite::write(const std::string_view data) +{ + try { + m_handle->write(data); + return true; + } + catch (const std::exception& e) { + Log.error("IOHandler write failed:", e.what()); + return false; + } +} + +std::string asst::IOHandlerAdbLite::read() +{ + try { + return m_handle->read(); + } + catch (const std::exception& e) { + Log.error("IOHandler read failed:", e.what()); + return NULL; + } +} diff --git a/src/MaaCore/Controller/Platform/AdbLiteIO.h b/src/MaaCore/Controller/Platform/AdbLiteIO.h new file mode 100644 index 0000000000..593bf7079c --- /dev/null +++ b/src/MaaCore/Controller/Platform/AdbLiteIO.h @@ -0,0 +1,59 @@ +#pragma once + +#include "PlatformIO.h" + +#ifdef _WIN32 +#include "Win32IO.h" +#else +#include "PosixIO.h" +#endif + +#include "../adb-lite/client.hpp" + +#include "Utils/Logger.hpp" + +namespace asst +{ + +#ifdef _WIN32 + using NativeIO = asst::Win32IO; +#else + using NativeIO = asst::PosixIO; +#endif + class AdbLiteIO : public NativeIO + { + public: + AdbLiteIO(Assistant* inst) : NativeIO(inst) {}; + AdbLiteIO(const AdbLiteIO&) = delete; + AdbLiteIO(AdbLiteIO&&) = delete; + virtual ~AdbLiteIO() = default; + + virtual std::optional call_command(const std::string& cmd, const bool recv_by_socket, + std::string& pipe_data, std::string& sock_data, const int64_t timeout, + const std::chrono::steady_clock::time_point start_time) override; + + virtual std::shared_ptr interactive_shell(const std::string& cmd) override; + + virtual void release_adb(const std::string& adb_release, int64_t timeout = 20000) override; + + private: + static bool remove_quotes(std::string& data); + + std::shared_ptr m_adb_client = nullptr; + }; + + class IOHandlerAdbLite : public IOHandler + { + public: + IOHandlerAdbLite(std::shared_ptr handle) : m_handle(handle) {}; + IOHandlerAdbLite(const IOHandlerAdbLite&) = delete; + IOHandlerAdbLite(IOHandlerAdbLite&&) = delete; + virtual ~IOHandlerAdbLite() = default; + + virtual bool write(const std::string_view data) override; + virtual std::string read() override; + + private: + std::shared_ptr m_handle = nullptr; + }; +} // namespace asst diff --git a/src/MaaCore/Controller/Platform/PlatformFactory.h b/src/MaaCore/Controller/Platform/PlatformFactory.h new file mode 100644 index 0000000000..8a6ecc4030 --- /dev/null +++ b/src/MaaCore/Controller/Platform/PlatformFactory.h @@ -0,0 +1,27 @@ +#pragma once + +#include "AdbLiteIO.h" +#ifdef _WIN32 +#include "Win32IO.h" +#else +#include "PosixIO.h" +#endif + +namespace asst +{ + class PlatformFactory + { + public: + static std::shared_ptr create_platform(Assistant* inst, PlatformType type) + { + switch (type) { + case PlatformType::AdbLite: + return std::make_shared(inst); + case PlatformType::Native: + return std::make_shared(inst); + default: + return nullptr; + } + } + }; +} diff --git a/src/MaaCore/Controller/Platform/PlatformIO.h b/src/MaaCore/Controller/Platform/PlatformIO.h new file mode 100644 index 0000000000..1ed0ce83d0 --- /dev/null +++ b/src/MaaCore/Controller/Platform/PlatformIO.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include + +namespace asst +{ + + class IOHandler; + + enum class PlatformType + { + Native, // Win32IO or PosixIO + AdbLite // AdbLiteIO + }; + + class PlatformIO + { + public: + virtual ~PlatformIO() = default; + + virtual std::optional call_command(const std::string& cmd, const bool recv_by_socket, + std::string& pipe_data, std::string& sock_data, const int64_t timeout, + const std::chrono::steady_clock::time_point start_time) = 0; + + virtual std::optional init_socket(const std::string& local_address) = 0; + virtual void close_socket() noexcept = 0; + + virtual std::shared_ptr interactive_shell(const std::string& cmd) = 0; + + virtual void release_adb(const std::string& adb_release, int64_t timeout = 20000) = 0; + + bool m_support_socket = false; + }; + + class IOHandler + { + public: + virtual ~IOHandler() = default; + + virtual bool write(const std::string_view data) = 0; + virtual std::string read() = 0; + }; +} diff --git a/src/MaaCore/Controller/Platform/PosixIO.cpp b/src/MaaCore/Controller/Platform/PosixIO.cpp new file mode 100644 index 0000000000..c84b6b10ba --- /dev/null +++ b/src/MaaCore/Controller/Platform/PosixIO.cpp @@ -0,0 +1,282 @@ +#ifndef _WIN32 +#include "PosixIO.h" + +#include +#include +#include +#include +#ifndef __APPLE__ +#include +#endif +#include +#include + +#include + +#include "Common/AsstTypes.h" +#include "Utils/Logger.hpp" +#include "Utils/NoWarningCV.h" + +asst::PosixIO::PosixIO(Assistant* inst) : InstHelper(inst) +{ + LogTraceFunction; + + int pipe_in_ret = ::pipe(m_pipe_in); + int pipe_out_ret = ::pipe(m_pipe_out); + ::fcntl(m_pipe_out[PIPE_READ], F_SETFL, O_NONBLOCK); + + if (pipe_in_ret < 0 || pipe_out_ret < 0) { + Log.error(__FUNCTION__, "controller pipe created failed", pipe_in_ret, pipe_out_ret); + } + + m_support_socket = true; +} + +asst::PosixIO::~PosixIO() +{ + LogTraceFunction; + + if (m_server_sock >= 0) { + ::close(m_server_sock); + m_server_sock = -1; + } + + ::close(m_pipe_in[PIPE_READ]); + ::close(m_pipe_in[PIPE_WRITE]); + ::close(m_pipe_out[PIPE_READ]); + ::close(m_pipe_out[PIPE_WRITE]); +} + +std::optional asst::PosixIO::call_command(const std::string& cmd, const bool recv_by_socket, + std::string& pipe_data, std::string& sock_data, const int64_t timeout, + const std::chrono::steady_clock::time_point start_time) +{ + using namespace std::chrono; + + asst::platform::single_page_buffer pipe_buffer; + asst::platform::single_page_buffer sock_buffer; + + auto check_timeout = [&]() -> bool { + return timeout && timeout < duration_cast(steady_clock::now() - start_time).count(); + }; + + int exit_ret = 0; + m_child = ::fork(); + if (m_child == 0) { + // child process + + ::dup2(m_pipe_in[PIPE_READ], STDIN_FILENO); + ::dup2(m_pipe_out[PIPE_WRITE], STDOUT_FILENO); + ::dup2(m_pipe_out[PIPE_WRITE], STDERR_FILENO); + + // all these are for use by parent only + // close(m_pipe_in[PIPE_READ]); + // close(m_pipe_in[PIPE_WRITE]); + // close(m_pipe_out[PIPE_READ]); + // close(m_pipe_out[PIPE_WRITE]); + + exit_ret = execlp("sh", "sh", "-c", cmd.c_str(), nullptr); + ::exit(exit_ret); + } + else if (m_child > 0) { + // parent process + if (recv_by_socket) { + sockaddr addr {}; + socklen_t len = sizeof(addr); + sock_buffer = asst::platform::single_page_buffer(); + + int client_socket = ::accept(m_server_sock, &addr, &len); + if (client_socket < 0) { + Log.error("accept failed:", strerror(errno)); + ::kill(m_child, SIGKILL); + ::waitpid(m_child, &exit_ret, 0); + return std::nullopt; + } + + ssize_t read_num = ::read(client_socket, sock_buffer.get(), sock_buffer.size()); + while (read_num > 0) { + sock_data.insert(sock_data.end(), sock_buffer.get(), sock_buffer.get() + read_num); + read_num = ::read(client_socket, sock_buffer.get(), sock_buffer.size()); + } + ::shutdown(client_socket, SHUT_RDWR); + ::close(client_socket); + } + else { + do { + ssize_t read_num = ::read(m_pipe_out[PIPE_READ], pipe_buffer.get(), pipe_buffer.size()); + while (read_num > 0) { + pipe_data.insert(pipe_data.end(), pipe_buffer.get(), pipe_buffer.get() + read_num); + read_num = ::read(m_pipe_out[PIPE_READ], pipe_buffer.get(), pipe_buffer.size()); + } + } while (::waitpid(m_child, &exit_ret, WNOHANG) == 0 && !check_timeout()); + } + ::waitpid(m_child, &exit_ret, 0); // if ::waitpid(m_child, &exit_ret, WNOHANG) == 0, repeat it will cause + // ECHILD, so not check the return value + } + else { + // failed to create child process + Log.error("Call `", cmd, "` create process failed, child:", m_child); + return std::nullopt; + } + + return exit_ret; +} + +std::optional asst::PosixIO::init_socket(const std::string& local_address) +{ + LogTraceFunction; + + m_server_sock = ::socket(AF_INET, SOCK_STREAM, 0); + if (m_server_sock < 0) { + return std::nullopt; + } + + m_server_sock_addr.sin_family = AF_INET; + m_server_sock_addr.sin_addr.s_addr = INADDR_ANY; + + bool server_start = false; + uint16_t port_result = 0; + + m_server_sock_addr.sin_port = htons(0); + int bind_ret = ::bind(m_server_sock, reinterpret_cast(&m_server_sock_addr), sizeof(::sockaddr_in)); + socklen_t addrlen = sizeof(m_server_sock_addr); + int getname_ret = ::getsockname(m_server_sock, reinterpret_cast(&m_server_sock_addr), &addrlen); + int listen_ret = ::listen(m_server_sock, 3); + struct timeval timeout = { 6, 0 }; + int timeout_ret = ::setsockopt(m_server_sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(struct timeval)); + server_start = bind_ret == 0 && getname_ret == 0 && listen_ret == 0 && timeout_ret == 0; + + if (!server_start) { + Log.info("not supports socket"); + return std::nullopt; + } + + port_result = ntohs(m_server_sock_addr.sin_port); + + Log.info("command server start", local_address, port_result); + return port_result; +} + +void asst::PosixIO::close_socket() noexcept +{ + if (m_server_sock >= 0) { + ::close(m_server_sock); + m_server_sock = -1; + } +} + +std::shared_ptr asst::PosixIO::interactive_shell(const std::string& cmd) +{ + LogTraceFunction; + + int pipe_to_child[2]; + int pipe_from_child[2]; + + if (::pipe(pipe_to_child)) return nullptr; + if (::pipe(pipe_from_child)) { + ::close(pipe_to_child[0]); + ::close(pipe_to_child[1]); + return nullptr; + } + + ::pid_t pid = fork(); + if (pid < 0) { + ::close(pipe_to_child[0]); + ::close(pipe_to_child[1]); + ::close(pipe_from_child[0]); + ::close(pipe_from_child[1]); + Log.error("fork failed:", strerror(errno)); + return nullptr; + } + if (pid == 0) { + // child process + if (::dup2(pipe_to_child[0], STDIN_FILENO) < 0 || ::close(pipe_to_child[1]) < 0 || + ::close(pipe_from_child[0]) < 0 || ::dup2(pipe_from_child[1], STDOUT_FILENO) < 0 || + ::dup2(pipe_from_child[1], STDERR_FILENO) < 0) { + ::exit(-1); + } + + // set stdin of child to blocking + if (int val = ::fcntl(STDIN_FILENO, F_GETFL); val != -1 && (val & O_NONBLOCK)) { + val &= ~O_NONBLOCK; + ::fcntl(STDIN_FILENO, F_SETFL, val); + } + +#ifndef __APPLE__ + ::prctl(PR_SET_PDEATHSIG, SIGTERM); +#endif + + ::execlp("/bin/sh", "sh", "-c", cmd.c_str(), nullptr); + exit(-1); + } + + if (::close(pipe_to_child[0]) < 0 || ::close(pipe_from_child[1]) < 0) { + ::kill(pid, SIGTERM); + ::waitpid(pid, nullptr, 0); + ::close(pipe_to_child[1]); + ::close(pipe_from_child[0]); + return nullptr; + } + + // set stdout to non blocking + if (int val = ::fcntl(pipe_from_child[0], F_GETFL); val != -1) { + val |= O_NONBLOCK; + ::fcntl(pipe_from_child[0], F_SETFL, val); + } + else { + ::kill(pid, SIGTERM); + ::waitpid(pid, nullptr, 0); + ::close(pipe_to_child[1]); + ::close(pipe_from_child[0]); + return nullptr; + } + + return std::make_shared(pipe_from_child[0], pipe_to_child[1], pid); +} + +void asst::PosixIO::release_adb(const std::string& adb_release, int64_t timeout) +{ + if (m_child) { + std::string pipe_data; + std::string sock_data; + auto start_time = std::chrono::steady_clock::now(); + + call_command(adb_release, false, pipe_data, sock_data, timeout, start_time); + } +} + +asst::IOHandlerPosix::~IOHandlerPosix() +{ + if (m_write_fd != -1) ::close(m_write_fd); + if (m_read_fd != -1) ::close(m_read_fd); + if (m_process > 0) ::kill(m_process, SIGTERM); +} + +bool asst::IOHandlerPosix::write(const std::string_view data) +{ + if (m_process < 0 || m_write_fd < 0) return false; + if (::write(m_write_fd, data.data(), data.length()) >= 0) return true; + Log.error("Failed to write to IOHandlerPosix, err", errno); + return false; +} + +std::string asst::IOHandlerPosix::read() +{ + if (m_process < 0 || m_read_fd < 0) return NULL; + std::string ret_str; + constexpr int PipeReadBuffSize = 4096ULL; + + while (true) { + char buf_from_child[PipeReadBuffSize]; + + ssize_t ret_read = ::read(m_read_fd, buf_from_child, PipeReadBuffSize); + if (ret_read > 0) { + ret_str.insert(ret_str.end(), buf_from_child, buf_from_child + ret_read); + } + else { + break; + } + } + return ret_str; +} +#endif diff --git a/src/MaaCore/Controller/Platform/PosixIO.h b/src/MaaCore/Controller/Platform/PosixIO.h new file mode 100644 index 0000000000..5580b8cea1 --- /dev/null +++ b/src/MaaCore/Controller/Platform/PosixIO.h @@ -0,0 +1,62 @@ +#pragma once + +#ifndef _WIN32 +#include + +#include "PlatformIO.h" + +#include "InstHelper.h" + +namespace asst +{ + class PosixIO : public PlatformIO, private InstHelper + { + public: + PosixIO(Assistant* inst); + PosixIO(const PosixIO&) = delete; + PosixIO(PosixIO&&) = delete; + virtual ~PosixIO(); + + virtual std::optional call_command(const std::string& cmd, const bool recv_by_socket, + std::string& pipe_data, std::string& sock_data, const int64_t timeout, + const std::chrono::steady_clock::time_point start_time) override; + + virtual std::optional init_socket(const std::string& local_address) override; + virtual void close_socket() noexcept override; + + virtual std::shared_ptr interactive_shell(const std::string& cmd) override; + + virtual void release_adb(const std::string& adb_release, int64_t timeout = 20000) override; + + int m_server_sock = -1; + sockaddr_in m_server_sock_addr {}; + static constexpr int PIPE_READ = 0; + static constexpr int PIPE_WRITE = 1; + int m_pipe_in[2] = { 0 }; + int m_pipe_out[2] = { 0 }; + int m_child = 0; + }; + + class IOHandlerPosix : public IOHandler + { + public: + IOHandlerPosix(int read_fd, int write_fd, ::pid_t process) + { + m_read_fd = read_fd; + m_write_fd = write_fd; + m_process = process; + } + IOHandlerPosix(const IOHandlerPosix&) = delete; + IOHandlerPosix(IOHandlerPosix&&) = delete; + virtual ~IOHandlerPosix(); + + virtual bool write(const std::string_view data) override; + virtual std::string read() override; + + private: + int m_read_fd = -1; + int m_write_fd = -1; + ::pid_t m_process = -1; + }; +} +#endif diff --git a/src/MaaCore/Controller/Platform/Win32IO.cpp b/src/MaaCore/Controller/Platform/Win32IO.cpp new file mode 100644 index 0000000000..6e95fffb06 --- /dev/null +++ b/src/MaaCore/Controller/Platform/Win32IO.cpp @@ -0,0 +1,409 @@ +#ifdef _WIN32 +#include "Win32IO.h" + +#include + +#include "Utils/Logger.hpp" + +asst::Win32IO::Win32IO(Assistant* inst) : InstHelper(inst) +{ + LogTraceFunction; + + m_support_socket = WsaHelper::get_instance()(); +} + +asst::Win32IO::~Win32IO() +{ + if (m_server_sock != INVALID_SOCKET) { + ::closesocket(m_server_sock); + m_server_sock = INVALID_SOCKET; + } +} + +std::optional asst::Win32IO::call_command(const std::string& cmd, const bool recv_by_socket, + std::string& pipe_data, std::string& sock_data, const int64_t timeout, + const std::chrono::steady_clock::time_point start_time) +{ + using namespace std::chrono; + + asst::platform::single_page_buffer pipe_buffer; + asst::platform::single_page_buffer sock_buffer; + + HANDLE pipe_parent_read = INVALID_HANDLE_VALUE, pipe_child_write = INVALID_HANDLE_VALUE; + SECURITY_ATTRIBUTES sa_inherit { .nLength = sizeof(SECURITY_ATTRIBUTES), .bInheritHandle = TRUE }; + if (!asst::win32::CreateOverlappablePipe(&pipe_parent_read, &pipe_child_write, nullptr, &sa_inherit, + (DWORD)pipe_buffer.size(), true, false)) { + DWORD err = GetLastError(); + Log.error("CreateOverlappablePipe failed, err", err); + return std::nullopt; + } + + STARTUPINFOW si {}; + si.cb = sizeof(STARTUPINFOW); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdOutput = pipe_child_write; + si.hStdError = pipe_child_write; + ASST_AUTO_DEDUCED_ZERO_INIT_START + PROCESS_INFORMATION process_info = { nullptr }; // 进程信息结构体 + ASST_AUTO_DEDUCED_ZERO_INIT_END + + auto cmdline_osstr = asst::utils::to_osstring(cmd); + BOOL create_ret = + CreateProcessW(nullptr, cmdline_osstr.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &process_info); + if (!create_ret) { + DWORD err = GetLastError(); + Log.error("Call `", cmd, "` create process failed, ret", create_ret, "error code:", err); + return std::nullopt; + } + + CloseHandle(pipe_child_write); + pipe_child_write = INVALID_HANDLE_VALUE; + + std::vector wait_handles; + wait_handles.reserve(3); + bool process_running = true; + bool pipe_eof = false; + bool accept_pending = false; + bool socket_eof = false; + + OVERLAPPED pipeov { .hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr) }; + (void)ReadFile(pipe_parent_read, pipe_buffer.get(), (DWORD)pipe_buffer.size(), nullptr, &pipeov); + + OVERLAPPED sockov {}; + SOCKET client_socket = INVALID_SOCKET; + + if (recv_by_socket) { + sock_buffer = asst::platform::single_page_buffer(); + sockov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + DWORD dummy; + if (!m_server_accept_ex(m_server_sock, client_socket, sock_buffer.get(), + (DWORD)sock_buffer.size() - ((sizeof(sockaddr_in) + 16) * 2), sizeof(sockaddr_in) + 16, + sizeof(sockaddr_in) + 16, &dummy, &sockov)) { + DWORD err = WSAGetLastError(); + if (err == ERROR_IO_PENDING) { + accept_pending = true; + } + else { + Log.trace("AcceptEx failed, err:", err); + accept_pending = false; + socket_eof = true; + ::closesocket(client_socket); + } + } + } + + while (!need_exit()) { + wait_handles.clear(); + if (process_running) wait_handles.push_back(process_info.hProcess); + if (!pipe_eof) wait_handles.push_back(pipeov.hEvent); + if (recv_by_socket && ((accept_pending && process_running) || !socket_eof)) { + wait_handles.push_back(sockov.hEvent); + } + if (wait_handles.empty()) break; + auto elapsed = steady_clock::now() - start_time; + // TODO: 这里目前是隔 5000ms 判断一次,应该可以加一个 wait_handle 来判断外部中断(need_exit) + auto wait_time = + (std::min)(timeout - duration_cast(elapsed).count(), process_running ? 5LL * 1000 : 0LL); + if (wait_time < 0) break; + auto wait_result = + WaitForMultipleObjectsEx((DWORD)wait_handles.size(), wait_handles.data(), FALSE, (DWORD)wait_time, TRUE); + HANDLE signaled_object = INVALID_HANDLE_VALUE; + if (wait_result >= WAIT_OBJECT_0 && wait_result < WAIT_OBJECT_0 + wait_handles.size()) { + signaled_object = wait_handles[(size_t)wait_result - WAIT_OBJECT_0]; + } + else if (wait_result == WAIT_TIMEOUT) { + if (wait_time == 0) { + std::vector handle_string {}; + for (auto handle : wait_handles) { + if (handle == process_info.hProcess) { + handle_string.emplace_back("process_info.hProcess"); + } + else if (handle == pipeov.hEvent) { + handle_string.emplace_back("pipeov.hEvent"); + } + else if (recv_by_socket && handle == sockov.hEvent) { + handle_string.emplace_back("sockov.hEvent"); + } + else { + handle_string.emplace_back("UnknownHandle"); + } + } + Log.warn("Wait handles:", handle_string, "timeout."); + if (process_running) { + TerminateProcess(process_info.hProcess, 0); + } + break; + } + continue; + } + else { + // something bad happened + DWORD err = GetLastError(); + // throw std::system_error(std::error_code(err, std::system_category())); + Log.error(__FUNCTION__, "A fatal error occurred", err); + break; + } + + if (signaled_object == process_info.hProcess) { + process_running = false; + } + else if (signaled_object == pipeov.hEvent) { + // pipe read + DWORD len = 0; + if (GetOverlappedResult(pipe_parent_read, &pipeov, &len, FALSE)) { + pipe_data.insert(pipe_data.end(), pipe_buffer.get(), pipe_buffer.get() + len); + (void)ReadFile(pipe_parent_read, pipe_buffer.get(), (DWORD)pipe_buffer.size(), nullptr, &pipeov); + } + else { + DWORD err = GetLastError(); + if (err == ERROR_HANDLE_EOF || err == ERROR_BROKEN_PIPE) { + pipe_eof = true; + } + } + } + else if (signaled_object == sockov.hEvent) { + if (accept_pending) { + // AcceptEx, client_socker is connected and first chunk of data is received + DWORD len = 0; + if (GetOverlappedResult(reinterpret_cast(m_server_sock), &sockov, &len, FALSE)) { + accept_pending = false; + if (recv_by_socket) sock_data.insert(sock_data.end(), sock_buffer.get(), sock_buffer.get() + len); + + if (len == 0) { + socket_eof = true; + ::closesocket(client_socket); + } + else { + // reset the overlapped since we reuse it for different handle + auto event = sockov.hEvent; + sockov = {}; + sockov.hEvent = event; + + (void)ReadFile(reinterpret_cast(client_socket), sock_buffer.get(), + (DWORD)sock_buffer.size(), nullptr, &sockov); + } + } + } + else { + // ReadFile + DWORD len = 0; + if (GetOverlappedResult(reinterpret_cast(client_socket), &sockov, &len, FALSE)) { + if (recv_by_socket) sock_data.insert(sock_data.end(), sock_buffer.get(), sock_buffer.get() + len); + if (len == 0) { + socket_eof = true; + ::closesocket(client_socket); + } + else { + (void)ReadFile(reinterpret_cast(client_socket), sock_buffer.get(), + (DWORD)sock_buffer.size(), nullptr, &sockov); + } + } + else { + // err = GetLastError(); + socket_eof = true; + ::closesocket(client_socket); + } + } + } + } + + DWORD exit_ret = 0; + GetExitCodeProcess(process_info.hProcess, &exit_ret); + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + CloseHandle(pipe_parent_read); + CloseHandle(pipeov.hEvent); + if (recv_by_socket) { + if (!socket_eof) closesocket(client_socket); + CloseHandle(sockov.hEvent); + } + + return static_cast(exit_ret); +} + +std::optional asst::Win32IO::init_socket(const std::string& local_address) +{ + LogTraceFunction; + + if (m_server_sock == INVALID_SOCKET) { + m_server_sock = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_server_sock == INVALID_SOCKET) { + return std::nullopt; + } + } + + DWORD dummy = 0; + GUID guid_accept_ex = WSAID_ACCEPTEX; + int err = WSAIoctl(m_server_sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid_accept_ex, sizeof(guid_accept_ex), + &m_server_accept_ex, sizeof(m_server_accept_ex), &dummy, NULL, NULL); + if (err == SOCKET_ERROR) { + err = WSAGetLastError(); + Log.error("failed to resolve AcceptEx, err:", err); + ::closesocket(m_server_sock); + return std::nullopt; + } + m_server_sock_addr.sin_family = PF_INET; + ::inet_pton(AF_INET, local_address.c_str(), &m_server_sock_addr.sin_addr); + + bool server_start = false; + uint16_t port_result = 0; + + m_server_sock_addr.sin_port = ::htons(0); + int bind_ret = ::bind(m_server_sock, reinterpret_cast(&m_server_sock_addr), sizeof(SOCKADDR)); + int addrlen = sizeof(m_server_sock_addr); + int getname_ret = ::getsockname(m_server_sock, reinterpret_cast(&m_server_sock_addr), &addrlen); + int listen_ret = ::listen(m_server_sock, 3); + server_start = bind_ret == 0 && getname_ret == 0 && listen_ret == 0; + + if (!server_start) { + Log.info("not supports socket"); + return std::nullopt; + } + + port_result = ::ntohs(m_server_sock_addr.sin_port); + + Log.info("command server start", local_address, port_result); + return port_result; +} + +void asst::Win32IO::close_socket() noexcept +{ + if (m_server_sock != INVALID_SOCKET) { + ::closesocket(m_server_sock); + m_server_sock = INVALID_SOCKET; + } +} + +std::shared_ptr asst::Win32IO::interactive_shell(const std::string& cmd) +{ + constexpr int PipeReadBuffSize = 4096ULL; + constexpr int PipeWriteBuffSize = 64 * 1024ULL; + + auto check_timeout = [&](const auto& start_time) -> bool { + using namespace std::chrono_literals; + return std::chrono::steady_clock::now() - start_time < 3s; + }; + + SECURITY_ATTRIBUTES sa_attr_inherit { + .nLength = sizeof(SECURITY_ATTRIBUTES), + .lpSecurityDescriptor = nullptr, + .bInheritHandle = TRUE, + }; + PROCESS_INFORMATION m_process_info = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; + HANDLE pipe_parent_read = INVALID_HANDLE_VALUE, pipe_child_write = INVALID_HANDLE_VALUE; + HANDLE pipe_child_read = INVALID_HANDLE_VALUE, pipe_parent_write = INVALID_HANDLE_VALUE; + if (!asst::win32::CreateOverlappablePipe(&pipe_parent_read, &pipe_child_write, nullptr, &sa_attr_inherit, + PipeReadBuffSize, true, false) || + !asst::win32::CreateOverlappablePipe(&pipe_child_read, &pipe_parent_write, &sa_attr_inherit, nullptr, + PipeWriteBuffSize, false, false)) { + DWORD err = GetLastError(); + Log.error("Failed to create pipe for minitouch, err", err); + return nullptr; + } + + STARTUPINFOW si {}; + si.cb = sizeof(STARTUPINFOW); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdInput = pipe_child_read; + si.hStdOutput = pipe_child_write; + si.hStdError = pipe_child_write; + + auto cmd_osstr = utils::to_osstring(cmd); + BOOL create_ret = + CreateProcessW(NULL, cmd_osstr.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &m_process_info); + CloseHandle(pipe_child_write); + CloseHandle(pipe_child_read); + pipe_child_write = INVALID_HANDLE_VALUE; + pipe_child_read = INVALID_HANDLE_VALUE; + + if (!create_ret) { + DWORD err = GetLastError(); + Log.error("Failed to create process for minitouch, err", err); + CloseHandle(m_process_info.hProcess); + CloseHandle(m_process_info.hThread); + CloseHandle(pipe_parent_read); + CloseHandle(pipe_parent_write); + return nullptr; + } + + auto start_time = std::chrono::steady_clock::now(); + + auto pipe_buffer = std::make_unique(PipeReadBuffSize); + OVERLAPPED pipeov { .hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr) }; + std::ignore = ReadFile(pipe_parent_read, pipe_buffer.get(), PipeReadBuffSize, nullptr, &pipeov); + + while (true) { + if (!check_timeout(start_time)) { + Log.error("minitouch start timeout"); + CloseHandle(m_process_info.hProcess); + CloseHandle(m_process_info.hThread); + CloseHandle(pipe_parent_read); + CloseHandle(pipe_parent_write); + return nullptr; + } + DWORD len = 0; + if (GetOverlappedResult(pipe_parent_read, &pipeov, &len, FALSE)) { + break; + } + } + + return std::make_shared(pipe_parent_read, pipe_parent_write, m_process_info); +} + +void asst::Win32IO::release_adb(const std::string& adb_release, int64_t timeout) +{ + std::string pipe_data; + std::string sock_data; + auto start_time = std::chrono::steady_clock::now(); + + call_command(adb_release, false, pipe_data, sock_data, timeout, start_time); +} + +asst::IOHandlerWin32::~IOHandlerWin32() +{ + if (m_process_info.hProcess != INVALID_HANDLE_VALUE) { + CloseHandle(m_process_info.hProcess); + m_process_info.hProcess = INVALID_HANDLE_VALUE; + } + if (m_process_info.hThread != INVALID_HANDLE_VALUE) { + CloseHandle(m_process_info.hThread); + m_process_info.hThread = INVALID_HANDLE_VALUE; + } + if (m_read != INVALID_HANDLE_VALUE) { + CloseHandle(m_read); + m_read = INVALID_HANDLE_VALUE; + } + if (m_write != INVALID_HANDLE_VALUE) { + CloseHandle(m_write); + m_write = INVALID_HANDLE_VALUE; + } +} + +std::string asst::IOHandlerWin32::read() +{ + auto pipe_buffer = std::make_unique(PipeBufferSize); + OVERLAPPED pipeov { .hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr) }; + std::ignore = ReadFile(m_read, pipe_buffer.get(), PipeBufferSize, nullptr, &pipeov); + return std::string(pipe_buffer.get()); +} + +bool asst::IOHandlerWin32::write(const std::string_view data) +{ + if (m_write == INVALID_HANDLE_VALUE) { + Log.error("IOHandler write handle invalid", m_write); + return false; + } + DWORD written = 0; + if (!WriteFile(m_write, data.data(), static_cast(data.size() * sizeof(std::string::value_type)), &written, + NULL)) { + auto err = GetLastError(); + Log.error("Failed to write to minitouch, err", err); + return false; + } + + return data.size() == written; +} +#endif diff --git a/src/MaaCore/Controller/Platform/Win32IO.h b/src/MaaCore/Controller/Platform/Win32IO.h new file mode 100644 index 0000000000..7c208b2908 --- /dev/null +++ b/src/MaaCore/Controller/Platform/Win32IO.h @@ -0,0 +1,82 @@ +#pragma once + +#ifdef _WIN32 +#include "PlatformIO.h" + +#include "Utils/Platform/SafeWindows.h" +#include + +#include "Common/AsstTypes.h" +#include "InstHelper.h" +#include "Utils/SingletonHolder.hpp" + +namespace asst +{ + class Win32IO : public PlatformIO, private InstHelper + { + public: + Win32IO(Assistant* inst); + Win32IO(const Win32IO&) = delete; + Win32IO(Win32IO&&) = delete; + virtual ~Win32IO(); + + virtual std::optional call_command(const std::string& cmd, const bool recv_by_socket, + std::string& pipe_data, std::string& sock_data, const int64_t timeout, + const std::chrono::steady_clock::time_point start_time) override; + + virtual std::optional init_socket(const std::string& local_address) override; + virtual void close_socket() noexcept override; + + virtual std::shared_ptr interactive_shell(const std::string& cmd) override; + + virtual void release_adb(const std::string& adb_release, int64_t timeout = 20000); + + ASST_AUTO_DEDUCED_ZERO_INIT_START + WSADATA m_wsa_data {}; + SOCKET m_server_sock = INVALID_SOCKET; + sockaddr_in m_server_sock_addr {}; + LPFN_ACCEPTEX m_server_accept_ex = nullptr; + ASST_AUTO_DEDUCED_ZERO_INIT_END + + private: + // for Windows socket + class WsaHelper : public SingletonHolder + { + friend class SingletonHolder; + + public: + virtual ~WsaHelper() override { WSACleanup(); } + bool operator()() const noexcept { return m_supports; } + + private: + WsaHelper() + { + m_supports = WSAStartup(MAKEWORD(2, 2), &m_wsa_data) == 0 && m_wsa_data.wVersion == MAKEWORD(2, 2); + } + WSADATA m_wsa_data = { 0 }; + bool m_supports = false; + }; + }; + + class IOHandlerWin32 : public IOHandler + { + public: + IOHandlerWin32(HANDLE read, HANDLE write, PROCESS_INFORMATION process_info) + : m_read(read), m_write(write), m_process_info(process_info) + {} + IOHandlerWin32(const IOHandlerWin32&) = delete; + IOHandlerWin32(IOHandlerWin32&&) = delete; + virtual ~IOHandlerWin32(); + + virtual bool write(const std::string_view data) override; + virtual std::string read() override; + + private: + HANDLE m_read = INVALID_HANDLE_VALUE; + HANDLE m_write = INVALID_HANDLE_VALUE; + PROCESS_INFORMATION m_process_info = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; + + const int PipeBufferSize = 4096; + }; +} // namespace asst +#endif diff --git a/src/MaaCore/MaaCore.vcxproj b/src/MaaCore/MaaCore.vcxproj index b8189ed937..565bbb2444 100644 --- a/src/MaaCore/MaaCore.vcxproj +++ b/src/MaaCore/MaaCore.vcxproj @@ -43,6 +43,17 @@ + + + + + + + + + + + @@ -179,6 +190,12 @@ + + + + + + diff --git a/src/MaaCore/MaaCore.vcxproj.filters b/src/MaaCore/MaaCore.vcxproj.filters index 302a9901ac..18ab9a600a 100644 --- a/src/MaaCore/MaaCore.vcxproj.filters +++ b/src/MaaCore/MaaCore.vcxproj.filters @@ -77,6 +77,9 @@ {a61a19a3-e1ba-45f2-9264-acb4102106b9} + + {7be68f84-acf3-41c4-9491-19b635a90d66} + {3b2a04b8-fb22-43c3-9444-1b455851d8e2} @@ -522,6 +525,39 @@ 源文件\Controller\adb-lite + + 源文件\Controller + + + 源文件\Controller + + + 源文件\Controller + + + 源文件\Controller + + + 源文件\Controller + + + 源文件\Controller + + + 源文件\Controller\Platform + + + 源文件\Controller\Platform + + + 源文件\Controller\Platform + + + 源文件\Controller\Platform + + + 源文件\Controller\Platform + 源文件\Resource\Miscellaneous @@ -866,6 +902,24 @@ 源文件\Controller\adb-lite + + 源文件\Controller + + + 源文件\Controller + + + 源文件\Controller + + + 源文件\Controller\Platform + + + 源文件\Controller\Platform + + + 源文件\Controller\Platform + 源文件\Resource\Miscellaneous diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp index 1d13808b70..f24f637344 100644 --- a/src/MaaCore/Task/BattleHelper.cpp +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -308,7 +308,8 @@ bool asst::BattleHelper::deploy_oper(const std::string& name, const Point& loc, if (int min_duration = swipe_oper_task_ptr->special_params.at(3); duration < min_duration) { duration = min_duration; } - bool deploy_with_pause = m_inst_helper.ctrler()->support_swipe_with_pause(); + bool deploy_with_pause = + ControlFeat::support(m_inst_helper.ctrler()->support_features(), ControlFeat::SWIPE_WITH_PAUSE); m_inst_helper.ctrler()->swipe(oper_rect, Rect(target_point.x, target_point.y, 1, 1), duration, false, swipe_oper_task_ptr->special_params.at(1), swipe_oper_task_ptr->special_params.at(2), deploy_with_pause); diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp index c1802f6deb..dbb43b71ea 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp @@ -521,7 +521,8 @@ void asst::RoguelikeRecruitTaskPlugin::slowly_swipe(bool to_left, int swipe_dist { std::string swipe_task_name = to_left ? "RoguelikeRecruitOperListSlowlySwipeToTheLeft" : "RoguelikeRecruitOperListSlowlySwipeToTheRight"; - if (!ctrler()->support_precise_swipe()) { // 不能精准滑动时不使用 swipe_dist 参数 + if (!ControlFeat::support(ctrler()->support_features(), + ControlFeat::PRECISE_SWIPE)) { // 不能精准滑动时不使用 swipe_dist 参数 ProcessTask(*this, { swipe_task_name }).run(); return; }