diff --git a/.gitignore b/.gitignore index 823d1c5bb8..a2fa26ff55 100644 --- a/.gitignore +++ b/.gitignore @@ -432,3 +432,4 @@ screen.png adb_screen.png tools/*.png resource/infrast +.vscode/settings.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d5c9f7c35..cba69e67fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 2.8) project(MeoAssistantArknights) +option(BUILD_TEST "build a demo" OFF) + include_directories(include 3rdparty/include) aux_source_directory(src/MeoAssistant SRC) add_definitions(-DASST_DLL_EXPORTS) @@ -16,9 +18,20 @@ set(CMAKE_CXX_STANDARD 17) add_library(MeoAssistant SHARED ${SRC}) -find_library(Opencv_LIB NAMES opencv_world453 PATHS 3rdparty/lib) find_library(PaddleOCR_LIB NAMES ppocr PATHS 3rdparty/lib) find_library(Penguin_LIB NAMES penguin-stats-recognize PATHS 3rdparty/lib) -find_library(MeoJson_LIB NAMES libmeojson PATHS 3rdparty/lib) +find_library(MeoJson_LIB NAMES meojson PATHS 3rdparty/lib) -target_link_libraries(MeoAssistant ${Opencv_LIB} ${PaddleOCR_LIB} ${Penguin_LIB} ${MeoJson_LIB}) +if (WIN32) + find_library(OpenCV NAMES opencv_world453 PATHS 3rdparty/lib) +else () + find_package(OpenCV REQUIRED) +endif () + + +target_link_libraries(MeoAssistant ${OpenCV} ${PaddleOCR_LIB} ${Penguin_LIB} ${MeoJson_LIB}) + +if (BUILD_TEST) + add_executable(test tools/TestCaller/main.cpp) + target_link_libraries(test MeoAssistant) +endif(BUILD_TEST) diff --git a/src/MeoAssistant/AbstractConfiger.cpp b/src/MeoAssistant/AbstractConfiger.cpp index d375f6d319..5840482ecc 100644 --- a/src/MeoAssistant/AbstractConfiger.cpp +++ b/src/MeoAssistant/AbstractConfiger.cpp @@ -1,27 +1,27 @@ -#include "AbstractConfiger.h" - -#include - -#include "AsstUtils.hpp" - -bool asst::AbstractConfiger::load(const std::string& filename) -{ - std::string content = utils::load_file_without_bom(filename); - - auto&& ret = json::parser::parse(content); - if (!ret) { - m_last_error = "json pasing error " + content; - return false; - } - - json::value root = std::move(ret.value()); - - try { - return parse(root); - } - catch (json::exception& e) { - m_last_error = std::string("json field error ") + e.what(); - return false; - } - return true; -} +#include "AbstractConfiger.h" + +#include + +#include "AsstUtils.hpp" + +bool asst::AbstractConfiger::load(const std::string& filename) +{ + std::string content = utils::load_file_without_bom(filename); + + auto&& ret = json::parser::parse(content); + if (!ret) { + m_last_error = "json pasing error, content:" + content; + return false; + } + + json::value root = std::move(ret.value()); + + try { + return parse(root); + } + catch (json::exception& e) { + m_last_error = std::string("json field error ") + e.what(); + return false; + } + return true; +} diff --git a/src/MeoAssistant/AbstractConfiger.h b/src/MeoAssistant/AbstractConfiger.h index c3e7a4cb00..6003772054 100644 --- a/src/MeoAssistant/AbstractConfiger.h +++ b/src/MeoAssistant/AbstractConfiger.h @@ -1,23 +1,23 @@ -#pragma once - -#include "AbstractResource.h" - -#include - -namespace json -{ - class value; -} - -namespace asst -{ - class AbstractConfiger : public AbstractResource - { - public: - virtual ~AbstractConfiger() = default; - virtual bool load(const std::string& filename) override; - - protected: - virtual bool parse(const json::value& json) = 0; - }; -} +#pragma once + +#include "AbstractResource.h" + +#include + +namespace json +{ + class value; +} + +namespace asst +{ + class AbstractConfiger : public AbstractResource + { + public: + virtual ~AbstractConfiger() = default; + virtual bool load(const std::string& filename) override; + + protected: + virtual bool parse(const json::value& json) = 0; + }; +} diff --git a/src/MeoAssistant/AbstractImageAnalyzer.h b/src/MeoAssistant/AbstractImageAnalyzer.h index d3c2282b79..d912ee6ace 100644 --- a/src/MeoAssistant/AbstractImageAnalyzer.h +++ b/src/MeoAssistant/AbstractImageAnalyzer.h @@ -1,41 +1,41 @@ -#pragma once - -#include - -#include "AsstDef.h" - -namespace asst -{ - class AbstractImageAnalyzer - { - public: - AbstractImageAnalyzer() = default; - AbstractImageAnalyzer(const cv::Mat& image); - AbstractImageAnalyzer(const cv::Mat& image, const Rect& roi); - AbstractImageAnalyzer(const AbstractImageAnalyzer&) = delete; - AbstractImageAnalyzer(AbstractImageAnalyzer&&) = delete; - virtual ~AbstractImageAnalyzer() = default; - - virtual void set_image(const cv::Mat& image); - virtual void set_image(const cv::Mat& image, const Rect& roi); - virtual void set_roi(const Rect& roi) noexcept; - virtual bool analyze() = 0; - virtual void correct_roi() noexcept; - - std::string calc_name_hash() const; // 使用m_roi - std::string calc_name_hash(const Rect& roi) const; // 使用参数roi - - AbstractImageAnalyzer& operator=(const AbstractImageAnalyzer&) = delete; - AbstractImageAnalyzer& operator=(AbstractImageAnalyzer&&) = delete; - - protected: - static Rect empty_rect_to_full(const Rect& rect, const cv::Mat& image) noexcept; - - cv::Mat m_image; - Rect m_roi; - -#ifdef LOG_TRACE - cv::Mat m_image_draw; -#endif - }; -} +#pragma once + +#include + +#include "AsstDef.h" + +namespace asst +{ + class AbstractImageAnalyzer + { + public: + AbstractImageAnalyzer() = default; + AbstractImageAnalyzer(const cv::Mat& image); + AbstractImageAnalyzer(const cv::Mat& image, const Rect& roi); + AbstractImageAnalyzer(const AbstractImageAnalyzer&) = delete; + AbstractImageAnalyzer(AbstractImageAnalyzer&&) = delete; + virtual ~AbstractImageAnalyzer() = default; + + virtual void set_image(const cv::Mat& image); + virtual void set_image(const cv::Mat& image, const Rect& roi); + virtual void set_roi(const Rect& roi) noexcept; + virtual bool analyze() = 0; + virtual void correct_roi() noexcept; + + std::string calc_name_hash() const; // 使用m_roi + std::string calc_name_hash(const Rect& roi) const; // 使用参数roi + + AbstractImageAnalyzer& operator=(const AbstractImageAnalyzer&) = delete; + AbstractImageAnalyzer& operator=(AbstractImageAnalyzer&&) = delete; + + protected: + static Rect empty_rect_to_full(const Rect& rect, const cv::Mat& image) noexcept; + + cv::Mat m_image; + Rect m_roi; + +#ifdef LOG_TRACE + cv::Mat m_image_draw; +#endif + }; +} diff --git a/src/MeoAssistant/AbstractResource.h b/src/MeoAssistant/AbstractResource.h index 6c7e84e8b7..78f9d1ca14 100644 --- a/src/MeoAssistant/AbstractResource.h +++ b/src/MeoAssistant/AbstractResource.h @@ -1,28 +1,28 @@ -#pragma once - -#include - -namespace asst -{ - class AbstractResource - { - public: - AbstractResource() = default; - AbstractResource(const AbstractResource& rhs) = delete; - AbstractResource(AbstractResource&& rhs) noexcept = delete; - - virtual ~AbstractResource() = default; - - virtual bool load(const std::string& filename) = 0; - virtual const std::string& get_last_error() const noexcept - { - return m_last_error; - } - - AbstractResource& operator=(const AbstractResource& rhs) = delete; - AbstractResource& operator=(AbstractResource&& rhs) noexcept = delete; - - protected: - std::string m_last_error; - }; -} +#pragma once + +#include + +namespace asst +{ + class AbstractResource + { + public: + AbstractResource() = default; + AbstractResource(const AbstractResource& rhs) = delete; + AbstractResource(AbstractResource&& rhs) noexcept = delete; + + virtual ~AbstractResource() = default; + + virtual bool load(const std::string& filename) = 0; + virtual const std::string& get_last_error() const noexcept + { + return m_last_error; + } + + AbstractResource& operator=(const AbstractResource& rhs) = delete; + AbstractResource& operator=(AbstractResource&& rhs) noexcept = delete; + + protected: + std::string m_last_error; + }; +} diff --git a/src/MeoAssistant/AipOcr.cpp b/src/MeoAssistant/AipOcr.cpp index f0db597b0f..06912fba2c 100644 --- a/src/MeoAssistant/AipOcr.cpp +++ b/src/MeoAssistant/AipOcr.cpp @@ -1,145 +1,152 @@ -#include "AipOcr.h" - -#include - -#include -#include -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" -#include "Resource.h" - -bool asst::AipOcr::request_access_token(const std::string& client_id, const std::string& client_secret) -{ - LogTraceFunction; - - m_access_token.clear(); - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s")"; - - constexpr int cmd_len = 256; - char cmd[cmd_len] = { 0 }; - sprintf_s(cmd, cmd_len, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); - - Log.trace("call cmd:", cmd); - - std::string response = utils::callcmd(cmd); - - Log.trace("response:", response); - - auto parse_res = json::parse(response); - if (!parse_res) { - return false; - } - try { - auto json = parse_res.value(); - m_access_token = json.at("access_token").as_string(); - } - catch (...) { - return false; - } - return true; -} - -bool asst::AipOcr::request_ocr_general(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) -{ - LogTraceFunction; - - if (m_access_token.empty()) { - auto& cfg = Resrc.cfg().get_options().aip_ocr; - request_access_token(cfg.client_id, cfg.client_secret); - } - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/general?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; - - return request_ocr_and_parse(cmd_fmt, image, out_result, pred); -} - -bool asst::AipOcr::request_ocr_accurate(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) -{ - LogTraceFunction; - - if (m_access_token.empty()) { - auto& cfg = Resrc.cfg().get_options().aip_ocr; - request_access_token(cfg.client_id, cfg.client_secret); - } - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; - - return request_ocr_and_parse(cmd_fmt, image, out_result, pred); -} - -bool asst::AipOcr::request_ocr_and_parse(std::string_view cmd_fmt, const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) -{ - if (m_access_token.empty()) { - return false; - } - // CreateProcess 最大只能接受 32767 chars,一张图片随便就超过了 - // 这个功能暂时没法用,得集成下 libcurl 或者别的 - std::vector buf; - cv::imencode(".png", image, buf); - auto* enc_msg = reinterpret_cast(buf.data()); - std::string encoded = base64_encode(enc_msg, buf.size()); - - size_t cmd_len = encoded.size() + cmd_fmt.size() + m_access_token.size(); - char* cmd = new char[cmd_len]; - memset(cmd, 0, cmd_len); - sprintf_s(cmd, cmd_len, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); - - Log.trace("call cmd:", cmd); - std::string response = utils::callcmd(cmd); - delete[] cmd; - Log.trace("response:", response); - - auto parse_res = json::parse(response); - if (!parse_res) { - return false; - } - try { - auto json = parse_res.value(); - return parse_response(json, out_result, pred); - } - catch (...) { - return false; - } -} - -bool asst::AipOcr::parse_response(const json::value& json, std::vector& out_result, const TextRectProc& pred) -{ - if (!json.exist("words_result")) { - return false; - } - - std::vector result; - std::string log_str_raw; - std::string log_str_proc; - for (const auto& word : json.at("words_result").as_array()) { - std::string text = word.at("words").as_string(); - - auto& loc = word.at("location").as_object(); - int left = loc.at("left").as_integer(); - int right = loc.at("right").as_integer(); - int top = loc.at("top").as_integer(); - int bottom = loc.at("bottom").as_integer(); - - Rect rect(left, top, right - left, bottom - top); - - TextRect tr{ text, rect, 0 }; - - log_str_raw += tr.to_string() + ", "; - if (!pred || pred(tr)) { - log_str_proc += tr.to_string() + ", "; - result.emplace_back(std::move(tr)); - } - } - - Log.trace("AipOcr::parse_response | raw : ", log_str_raw); - Log.trace("AipOcr::parse_response | proc : ", log_str_proc); - - out_result = result; - return true; -} +#include "AipOcr.h" + +#include + +#include +#include +#include + +#include "AsstUtils.hpp" +#include "Logger.hpp" +#include "Resource.h" + +bool asst::AipOcr::request_access_token(const std::string& client_id, const std::string& client_secret) +{ + LogTraceFunction; + + m_access_token.clear(); + + std::string_view cmd_fmt = + R"(curl -s -k "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s")"; + + constexpr int cmd_len = 256; + char cmd[cmd_len] = { 0 }; +#ifdef _MSC_VER + sprintf_s(cmd, cmd_len, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); +#else + sprintf(cmd, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); +#endif + + Log.trace("call cmd:", cmd); + + std::string response = utils::callcmd(cmd); + + Log.trace("response:", response); + + auto parse_res = json::parse(response); + if (!parse_res) { + return false; + } + try { + auto json = parse_res.value(); + m_access_token = json.at("access_token").as_string(); + } + catch (...) { + return false; + } + return true; +} + +bool asst::AipOcr::request_ocr_general(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) +{ + LogTraceFunction; + + if (m_access_token.empty()) { + auto& cfg = Resrc.cfg().get_options().aip_ocr; + request_access_token(cfg.client_id, cfg.client_secret); + } + + std::string_view cmd_fmt = + R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/general?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; + + return request_ocr_and_parse(cmd_fmt, image, out_result, pred); +} + +bool asst::AipOcr::request_ocr_accurate(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) +{ + LogTraceFunction; + + if (m_access_token.empty()) { + auto& cfg = Resrc.cfg().get_options().aip_ocr; + request_access_token(cfg.client_id, cfg.client_secret); + } + + std::string_view cmd_fmt = + R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; + + return request_ocr_and_parse(cmd_fmt, image, out_result, pred); +} + +bool asst::AipOcr::request_ocr_and_parse(std::string_view cmd_fmt, const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) +{ + if (m_access_token.empty()) { + return false; + } + // CreateProcess 最大只能接受 32767 chars,一张图片随便就超过了 + // 这个功能暂时没法用,得集成下 libcurl 或者别的 + std::vector buf; + cv::imencode(".png", image, buf); + auto* enc_msg = reinterpret_cast(buf.data()); + std::string encoded = base64_encode(enc_msg, buf.size()); + + size_t cmd_len = encoded.size() + cmd_fmt.size() + m_access_token.size(); + char* cmd = new char[cmd_len]; + memset(cmd, 0, cmd_len); +#ifdef _MSC_VER + sprintf_s(cmd, cmd_len, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); +#else + sprintf(cmd, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); +#endif + Log.trace("call cmd:", cmd); + std::string response = utils::callcmd(cmd); + delete[] cmd; + Log.trace("response:", response); + + auto parse_res = json::parse(response); + if (!parse_res) { + return false; + } + try { + auto json = parse_res.value(); + return parse_response(json, out_result, pred); + } + catch (...) { + return false; + } +} + +bool asst::AipOcr::parse_response(const json::value& json, std::vector& out_result, const TextRectProc& pred) +{ + if (!json.exist("words_result")) { + return false; + } + + std::vector result; + std::string log_str_raw; + std::string log_str_proc; + for (const auto& word : json.at("words_result").as_array()) { + std::string text = word.at("words").as_string(); + + auto& loc = word.at("location").as_object(); + int left = loc.at("left").as_integer(); + int right = loc.at("right").as_integer(); + int top = loc.at("top").as_integer(); + int bottom = loc.at("bottom").as_integer(); + + Rect rect(left, top, right - left, bottom - top); + + TextRect tr{ text, rect, 0 }; + + log_str_raw += tr.to_string() + ", "; + if (!pred || pred(tr)) { + log_str_proc += tr.to_string() + ", "; + result.emplace_back(std::move(tr)); + } + } + + Log.trace("AipOcr::parse_response | raw : ", log_str_raw); + Log.trace("AipOcr::parse_response | proc : ", log_str_proc); + + out_result = result; + return true; +} diff --git a/src/MeoAssistant/Assistant.cpp b/src/MeoAssistant/Assistant.cpp index 8aadb9d3bf..a1d480b1ce 100644 --- a/src/MeoAssistant/Assistant.cpp +++ b/src/MeoAssistant/Assistant.cpp @@ -1,638 +1,641 @@ -#include "Assistant.h" - -#include -#include - -#include -#include - -#include "AsstUtils.hpp" -#include "Controller.h" -#include "Logger.hpp" -#include "Resource.h" - -#include "CreditShoppingTask.h" -#include "InfrastDormTask.h" -#include "InfrastInfoTask.h" -#include "InfrastMfgTask.h" -#include "InfrastOfficeTask.h" -#include "InfrastPowerTask.h" -#include "InfrastReceptionTask.h" -#include "InfrastTradeTask.h" -#include "ProcessTask.h" -#include "RecruitTask.h" -#include "AutoRecruitTask.h" -#include "InfrastControlTask.h" -#include "RuntimeStatus.h" - -using namespace asst; - -Assistant::Assistant(std::string dirname, AsstCallback callback, void* callback_arg) - : m_dirname(std::move(dirname) + "\\"), - m_callback(callback), - m_callback_arg(callback_arg) -{ - Logger::set_dirname(m_dirname); - Controller::set_dirname(m_dirname); - - LogTraceFunction; - - bool resource_ret = Resrc.load(m_dirname + "Resource\\"); - if (!resource_ret) { - const std::string& error = Resrc.get_last_error(); - Log.error("resource broken", error); - if (m_callback == nullptr) { - throw error; - } - json::value callback_json; - callback_json["type"] = "resource broken"; - callback_json["what"] = error; - m_callback(AsstMsg::InitFaild, callback_json, m_callback_arg); - throw error; - } - - m_working_thread = std::thread(std::bind(&Assistant::working_proc, this)); - m_msg_thread = std::thread(std::bind(&Assistant::msg_proc, this)); -} - -Assistant::~Assistant() -{ - LogTraceFunction; - - m_thread_exit = true; - m_thread_idle = true; - m_condvar.notify_all(); - m_msg_condvar.notify_all(); - - if (m_working_thread.joinable()) { - m_working_thread.join(); - } - if (m_msg_thread.joinable()) { - m_msg_thread.join(); - } -} - -bool asst::Assistant::catch_default() -{ - LogTraceFunction; - - auto& opt = Resrc.cfg().get_options(); - switch (opt.connect_type) { - case ConnectType::Emulator: - return catch_emulator(); - case ConnectType::Custom: - return catch_custom(); - default: - return false; - } -} - -bool Assistant::catch_emulator(const std::string& emulator_name) -{ - LogTraceFunction; - - stop(); - - bool ret = false; - //std::string cor_name = emulator_name; - auto& cfg = Resrc.cfg(); - - std::unique_lock lock(m_mutex); - - // 自动匹配模拟器,逐个找 - if (emulator_name.empty()) { - for (const auto& [name, info] : cfg.get_emulators_info()) { - ret = Ctrler.try_capture(info); - if (ret) { - //cor_name = name; - break; - } - } - } - else { // 指定的模拟器 - auto& info = cfg.get_emulators_info().at(emulator_name); - ret = Ctrler.try_capture(info); - } - - m_inited = ret; - return ret; -} - -bool asst::Assistant::catch_custom(const std::string& address) -{ - LogTraceFunction; - - stop(); - - bool ret = false; - auto& cfg = Resrc.cfg(); - - std::unique_lock lock(m_mutex); - - EmulatorInfo remote_info = cfg.get_emulators_info().at("Custom"); - if (!address.empty()) { - remote_info.adb.addresses.push_back(address); - } - - ret = Ctrler.try_capture(remote_info, true); - - m_inited = ret; - return ret; -} - -bool asst::Assistant::catch_fake() -{ - LogTraceFunction; - - stop(); - - m_inited = true; - return true; -} - -bool asst::Assistant::append_start_up(bool only_append) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - std::unique_lock lock(m_mutex); - - auto task_ptr = std::make_shared(task_callback, (void*)this); - task_ptr->set_task_chain("StartUp"); - task_ptr->set_tasks({ "StartUp" }); - task_ptr->set_times_limit("ReturnToTerminal", 0); - task_ptr->set_times_limit("Terminal", 0); - - m_tasks_queue.emplace(task_ptr); - - if (!only_append) { - return start(false); - } - - return true; -} - -bool asst::Assistant::append_fight(const std::string& stage, int mecidine, int stone, int times, bool only_append) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - constexpr const char* TaskChain = "Fight"; - - // 进入选关界面(主界面的“终端”点进去) - auto terminal_task_ptr = std::make_shared(task_callback, (void*)this); - terminal_task_ptr->set_task_chain(TaskChain); - terminal_task_ptr->set_tasks({ "StageBegin" }); - terminal_task_ptr->set_times_limit("LastBattle", 0); - - // 进入对应的关卡 - auto stage_task_ptr = std::make_shared(task_callback, (void*)this); - stage_task_ptr->set_task_chain(TaskChain); - stage_task_ptr->set_tasks({ stage }); - - // 开始战斗任务 - auto fight_task_ptr = std::make_shared(task_callback, (void*)this); - fight_task_ptr->set_task_chain(TaskChain); - fight_task_ptr->set_tasks({ "FightBegin" }); - fight_task_ptr->set_times_limit("MedicineConfirm", mecidine); - fight_task_ptr->set_times_limit("StoneConfirm", stone); - fight_task_ptr->set_times_limit("StartButton1", times); - - std::unique_lock lock(m_mutex); - - if (!stage.empty()) { - m_tasks_queue.emplace(terminal_task_ptr); - m_tasks_queue.emplace(stage_task_ptr); - } - m_tasks_queue.emplace(fight_task_ptr); - - if (!only_append) { - return start(false); - } - - return true; -} - -bool asst::Assistant::append_award(bool only_append) -{ - return append_process_task("AwardBegin", "Award"); -} - -bool asst::Assistant::append_visit(bool only_append) -{ - return append_process_task("VisitBegin", "Visit"); -} - -bool asst::Assistant::append_mall(bool with_shopping, bool only_append) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - std::unique_lock lock(m_mutex); - - const std::string task_chain = "Mall"; - - append_process_task("MallBegin", task_chain); - - if (with_shopping) { - auto shopping_task_ptr = std::make_shared(task_callback, (void*)this); - shopping_task_ptr->set_task_chain(task_chain); - m_tasks_queue.emplace(shopping_task_ptr); - } - - if (!only_append) { - return start(false); - } - - return true; -} - -bool Assistant::append_process_task(const std::string& task, std::string task_chain, int retry_times) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - //std::unique_lock lock(m_mutex); - - if (task_chain.empty()) { - task_chain = task; - } - - auto task_ptr = std::make_shared(task_callback, (void*)this); - task_ptr->set_task_chain(task_chain); - task_ptr->set_tasks({ task }); - task_ptr->set_retry_times(retry_times); - - m_tasks_queue.emplace(task_ptr); - - //if (!only_append) { - // return start(false); - //} - - return true; -} - -bool asst::Assistant::append_recruit(unsigned max_times, const std::vector& select_level, const std::vector& confirm_level, bool need_refresh) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - static const std::string TaskChain = "Recruit"; - - append_process_task("RecruitBegin", TaskChain); - - auto recruit_task_ptr = std::make_shared(task_callback, (void*)this); - recruit_task_ptr->set_max_times(max_times); - recruit_task_ptr->set_need_refresh(need_refresh); - recruit_task_ptr->set_select_level(select_level); - recruit_task_ptr->set_confirm_level(confirm_level); - recruit_task_ptr->set_task_chain(TaskChain); - recruit_task_ptr->set_retry_times(AutoRecruitTaskRetryTimesDefault); - - m_tasks_queue.emplace(recruit_task_ptr); - - return true; -} - -#ifdef LOG_TRACE -bool Assistant::append_debug() -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - std::unique_lock lock(m_mutex); - - { - constexpr static const char* DebugTaskChain = "Debug"; - auto shift_task_ptr = std::make_shared(task_callback, (void*)this); - shift_task_ptr->set_work_mode(infrast::WorkMode::Aggressive); - shift_task_ptr->set_facility("Control"); - shift_task_ptr->set_product("MoodAddition"); - shift_task_ptr->set_task_chain(DebugTaskChain); - m_tasks_queue.emplace(shift_task_ptr); - } - - return true; -} -#endif - -bool Assistant::start_recruit_calc(const std::vector& select_level, bool set_time) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - std::unique_lock lock(m_mutex); - - auto task_ptr = std::make_shared(task_callback, (void*)this); - task_ptr->set_param(select_level, set_time); - task_ptr->set_retry_times(OpenRecruitTaskRetryTimesDefault); - task_ptr->set_task_chain("RecruitCalc"); - m_tasks_queue.emplace(task_ptr); - - return start(false); -} - -bool asst::Assistant::append_infrast(infrast::WorkMode work_mode, const std::vector& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - // 保留接口,目前强制按激进模式进行换班 - work_mode = infrast::WorkMode::Aggressive; - - constexpr static const char* InfrastTaskCahin = "Infrast"; - - // 这个流程任务,结束的时候是处于基建主界面的。既可以用于进入基建,也可以用于从设施里返回基建主界面 - - auto append_infrast_begin = [&]() { - append_process_task("InfrastBegin", InfrastTaskCahin); - }; - - append_infrast_begin(); - - auto info_task_ptr = std::make_shared(task_callback, (void*)this); - info_task_ptr->set_work_mode(work_mode); - info_task_ptr->set_task_chain(InfrastTaskCahin); - info_task_ptr->set_mood_threshold(dorm_threshold); - - m_tasks_queue.emplace(info_task_ptr); - - // 因为后期要考虑多任务间的联动等,所以这些任务的声明暂时不放到for循环中 - auto mfg_task_ptr = std::make_shared(task_callback, (void*)this); - mfg_task_ptr->set_work_mode(work_mode); - mfg_task_ptr->set_task_chain(InfrastTaskCahin); - mfg_task_ptr->set_mood_threshold(dorm_threshold); - mfg_task_ptr->set_uses_of_drone(uses_of_drones); - auto trade_task_ptr = std::make_shared(task_callback, (void*)this); - trade_task_ptr->set_work_mode(work_mode); - trade_task_ptr->set_task_chain(InfrastTaskCahin); - trade_task_ptr->set_mood_threshold(dorm_threshold); - trade_task_ptr->set_uses_of_drone(uses_of_drones); - auto power_task_ptr = std::make_shared(task_callback, (void*)this); - power_task_ptr->set_work_mode(work_mode); - power_task_ptr->set_task_chain(InfrastTaskCahin); - power_task_ptr->set_mood_threshold(dorm_threshold); - auto office_task_ptr = std::make_shared(task_callback, (void*)this); - office_task_ptr->set_work_mode(work_mode); - office_task_ptr->set_task_chain(InfrastTaskCahin); - office_task_ptr->set_mood_threshold(dorm_threshold); - auto recpt_task_ptr = std::make_shared(task_callback, (void*)this); - recpt_task_ptr->set_work_mode(work_mode); - recpt_task_ptr->set_task_chain(InfrastTaskCahin); - recpt_task_ptr->set_mood_threshold(dorm_threshold); - auto control_task_ptr = std::make_shared(task_callback, (void*)this); - control_task_ptr->set_work_mode(work_mode); - control_task_ptr->set_task_chain(InfrastTaskCahin); - control_task_ptr->set_mood_threshold(dorm_threshold); - - auto dorm_task_ptr = std::make_shared(task_callback, (void*)this); - dorm_task_ptr->set_work_mode(work_mode); - dorm_task_ptr->set_task_chain(InfrastTaskCahin); - dorm_task_ptr->set_mood_threshold(dorm_threshold); - - for (const auto& facility : order) { - if (facility == "Dorm") { - m_tasks_queue.emplace(dorm_task_ptr); - } - else if (facility == "Mfg") { - m_tasks_queue.emplace(mfg_task_ptr); - } - else if (facility == "Trade") { - m_tasks_queue.emplace(trade_task_ptr); - } - else if (facility == "Power") { - m_tasks_queue.emplace(power_task_ptr); - } - else if (facility == "Office") { - m_tasks_queue.emplace(office_task_ptr); - } - else if (facility == "Reception") { - m_tasks_queue.emplace(recpt_task_ptr); - } - else if (facility == "Control") { - m_tasks_queue.emplace(control_task_ptr); - } - else { - Log.error("append_infrast | Unknown facility", facility); - } - append_infrast_begin(); - } - - if (!only_append) { - return start(false); - } - - return true; -} - -void asst::Assistant::set_penguin_id(const std::string& id) -{ - auto& opt = Resrc.cfg().get_options(); - if (id.empty()) { - opt.penguin_report.extra_param.clear(); - } - else { - opt.penguin_report.extra_param = "-H \"authorization: PenguinID " + id + "\""; - } -} - -bool asst::Assistant::start(bool block) -{ - LogTraceFunction; - Log.trace("Start |", block ? "block" : "non block"); - - if (!m_thread_idle || !m_inited) { - return false; - } - std::unique_lock lock; - if (block) { // 外部调用 - lock = std::unique_lock(m_mutex); - } - - m_thread_idle = false; - m_condvar.notify_one(); - - return true; -} - -bool Assistant::stop(bool block) -{ - LogTraceFunction; - Log.trace("Stop |", block ? "block" : "non block"); - - m_thread_idle = true; - - std::unique_lock lock; - if (block) { // 外部调用 - lock = std::unique_lock(m_mutex); - } - decltype(m_tasks_queue) empty; - m_tasks_queue.swap(empty); - - clear_cache(); - - return true; -} - -void Assistant::working_proc() -{ - LogTraceFunction; - - std::string pre_taskchain; - while (!m_thread_exit) { - //LogTraceScope("Assistant::working_proc Loop"); - std::unique_lock lock(m_mutex); - - if (!m_thread_idle && !m_tasks_queue.empty()) { - auto start_time = std::chrono::steady_clock::now(); - - auto task_ptr = m_tasks_queue.front(); - - std::string cur_taskchain = task_ptr->get_task_chain(); - json::value task_json = json::object{ - {"task_chain", cur_taskchain}, - }; - - if (cur_taskchain != pre_taskchain) { - task_callback(AsstMsg::TaskChainStart, task_json, this); - pre_taskchain = cur_taskchain; - } - - task_ptr->set_exit_flag(&m_thread_idle); - bool ret = task_ptr->run(); - m_tasks_queue.pop(); - - if (!ret) { - task_callback(AsstMsg::TaskError, task_json, this); - } - else if (m_tasks_queue.empty() || cur_taskchain != m_tasks_queue.front()->get_task_chain()) { - task_callback(AsstMsg::TaskChainCompleted, task_json, this); - } - if (m_tasks_queue.empty()) { - task_callback(AsstMsg::AllTasksCompleted, task_json, this); - } - - //clear_cache(); - - auto& delay = Resrc.cfg().get_options().task_delay; - m_condvar.wait_until(lock, start_time + std::chrono::milliseconds(delay), - [&]() -> bool { return m_thread_idle; }); - } - else { - pre_taskchain.clear(); - m_thread_idle = true; - Log.flush(); - //controller.set_idle(true); - m_condvar.wait(lock); - } - } -} - -void Assistant::msg_proc() -{ - LogTraceFunction; - - while (!m_thread_exit) { - //LogTraceScope("Assistant::msg_proc Loop"); - std::unique_lock lock(m_msg_mutex); - if (!m_msg_queue.empty()) { - // 结构化绑定只能是引用,后续的pop会使引用失效,所以需要重新构造一份,这里采用了move的方式 - auto&& [temp_msg, temp_detail] = m_msg_queue.front(); - AsstMsg msg = std::move(temp_msg); - json::value detail = std::move(temp_detail); - m_msg_queue.pop(); - lock.unlock(); - - if (m_callback) { - m_callback(msg, detail, m_callback_arg); - } - } - else { - m_msg_condvar.wait(lock); - } - } -} - -void Assistant::task_callback(AsstMsg msg, const json::value& detail, void* custom_arg) -{ - Log.trace("Assistant::task_callback |", msg, detail.to_string()); - - Assistant* p_this = (Assistant*)custom_arg; - json::value more_detail = detail; - switch (msg) { - case AsstMsg::PtrIsNull: - case AsstMsg::ImageIsEmpty: - p_this->stop(false); - break; - case AsstMsg::StageDrops: - more_detail = p_this->organize_stage_drop(more_detail); - break; - default: - break; - } - - // Todo: 有些不需要回调给外部的消息,得在这里给拦截掉 - // 加入回调消息队列,由回调消息线程外抛给外部 - p_this->append_callback(msg, std::move(more_detail)); -} - -void asst::Assistant::append_callback(AsstMsg msg, json::value detail) -{ - std::unique_lock lock(m_msg_mutex); - m_msg_queue.emplace(msg, std::move(detail)); - m_msg_condvar.notify_one(); -} - -void Assistant::clear_cache() -{ - Resrc.item().clear_drop_count(); - //task.clear_cache(); -} - -json::value asst::Assistant::organize_stage_drop(const json::value& rec) -{ - json::value dst = rec; - auto& item = Resrc.item(); - for (json::value& drop : dst["drops"].as_array()) { - std::string id = drop["itemId"].as_string(); - int quantity = drop["quantity"].as_integer(); - item.increase_drop_count(id, quantity); - const std::string& name = item.get_item_name(id); - drop["itemName"] = name.empty() ? "未知材料" : name; - } - std::vector statistics_vec; - for (auto&& [id, count] : item.get_drop_count()) { - json::value info; - info["itemId"] = id; - const std::string& name = item.get_item_name(id); - info["itemName"] = name.empty() ? "未知材料" : name; - info["count"] = count; - statistics_vec.emplace_back(std::move(info)); - } - //// 排个序,数量多的放前面 - //std::sort(statistics_vec.begin(), statistics_vec.end(), - // [](const json::value& lhs, const json::value& rhs) -> bool { - // return lhs.at("count").as_integer() > rhs.at("count").as_integer(); - // }); - - dst["statistics"] = json::array(std::move(statistics_vec)); - - Log.trace("organize_stage_drop | ", dst.to_string()); - - return dst; -} +#include "Assistant.h" + +#include +#include + +#include +#include + +#include "AsstUtils.hpp" +#include "Controller.h" +#include "Logger.hpp" +#include "Resource.h" + +#include "CreditShoppingTask.h" +#include "InfrastDormTask.h" +#include "InfrastInfoTask.h" +#include "InfrastMfgTask.h" +#include "InfrastOfficeTask.h" +#include "InfrastPowerTask.h" +#include "InfrastReceptionTask.h" +#include "InfrastTradeTask.h" +#include "ProcessTask.h" +#include "RecruitTask.h" +#include "AutoRecruitTask.h" +#include "InfrastControlTask.h" +#include "RuntimeStatus.h" + +using namespace asst; + +Assistant::Assistant(std::string dirname, AsstCallback callback, void* callback_arg) + : m_dirname(std::move(dirname) + "/"), + m_callback(callback), + m_callback_arg(callback_arg) +{ + Logger::set_dirname(m_dirname); + Controller::set_dirname(m_dirname); + + LogTraceFunction; + + bool resource_ret = Resrc.load(m_dirname + "Resource/"); + if (!resource_ret) { + const std::string& error = Resrc.get_last_error(); + Log.error("resource broken:", error); + if (m_callback == nullptr) { + throw error; + } + json::value callback_json; + callback_json["type"] = "resource broken"; + callback_json["what"] = error; + m_callback(AsstMsg::InitFaild, callback_json, m_callback_arg); + throw error; + } + + m_working_thread = std::thread(std::bind(&Assistant::working_proc, this)); + m_msg_thread = std::thread(std::bind(&Assistant::msg_proc, this)); +} + +Assistant::~Assistant() +{ + LogTraceFunction; + + m_thread_exit = true; + m_thread_idle = true; + m_condvar.notify_all(); + m_msg_condvar.notify_all(); + + if (m_working_thread.joinable()) { + m_working_thread.join(); + } + if (m_msg_thread.joinable()) { + m_msg_thread.join(); + } +} + +bool asst::Assistant::catch_default() +{ + LogTraceFunction; + + auto& opt = Resrc.cfg().get_options(); + switch (opt.connect_type) { + case ConnectType::Emulator: + return catch_emulator(); + case ConnectType::Custom: + return catch_custom(); + default: + return false; + } +} + +bool Assistant::catch_emulator(const std::string& emulator_name) +{ + LogTraceFunction; + + stop(); +#ifdef _WIN32 + bool ret = false; + //std::string cor_name = emulator_name; + auto& cfg = Resrc.cfg(); + + std::unique_lock lock(m_mutex); + + // 自动匹配模拟器,逐个找 + if (emulator_name.empty()) { + for (const auto& [name, info] : cfg.get_emulators_info()) { + ret = Ctrler.try_capture(info); + if (ret) { + //cor_name = name; + break; + } + } + } + else { // 指定的模拟器 + auto& info = cfg.get_emulators_info().at(emulator_name); + ret = Ctrler.try_capture(info); + } + + m_inited = ret; + return ret; +#else // Not supported catch emulator in Linux + return false; +#endif +} + +bool asst::Assistant::catch_custom(const std::string& address) +{ + LogTraceFunction; + + stop(); + + bool ret = false; + auto& cfg = Resrc.cfg(); + + std::unique_lock lock(m_mutex); + + EmulatorInfo remote_info = cfg.get_emulators_info().at("Custom"); + if (!address.empty()) { + remote_info.adb.addresses.push_back(address); + } + + ret = Ctrler.try_capture(remote_info, true); + + m_inited = ret; + return ret; +} + +bool asst::Assistant::catch_fake() +{ + LogTraceFunction; + + stop(); + + m_inited = true; + return true; +} + +bool asst::Assistant::append_start_up(bool only_append) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + std::unique_lock lock(m_mutex); + + auto task_ptr = std::make_shared(task_callback, (void*)this); + task_ptr->set_task_chain("StartUp"); + task_ptr->set_tasks({ "StartUp" }); + task_ptr->set_times_limit("ReturnToTerminal", 0); + task_ptr->set_times_limit("Terminal", 0); + + m_tasks_queue.emplace(task_ptr); + + if (!only_append) { + return start(false); + } + + return true; +} + +bool asst::Assistant::append_fight(const std::string& stage, int mecidine, int stone, int times, bool only_append) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + constexpr const char* TaskChain = "Fight"; + + // 进入选关界面(主界面的“终端”点进去) + auto terminal_task_ptr = std::make_shared(task_callback, (void*)this); + terminal_task_ptr->set_task_chain(TaskChain); + terminal_task_ptr->set_tasks({ "StageBegin" }); + terminal_task_ptr->set_times_limit("LastBattle", 0); + + // 进入对应的关卡 + auto stage_task_ptr = std::make_shared(task_callback, (void*)this); + stage_task_ptr->set_task_chain(TaskChain); + stage_task_ptr->set_tasks({ stage }); + + // 开始战斗任务 + auto fight_task_ptr = std::make_shared(task_callback, (void*)this); + fight_task_ptr->set_task_chain(TaskChain); + fight_task_ptr->set_tasks({ "FightBegin" }); + fight_task_ptr->set_times_limit("MedicineConfirm", mecidine); + fight_task_ptr->set_times_limit("StoneConfirm", stone); + fight_task_ptr->set_times_limit("StartButton1", times); + + std::unique_lock lock(m_mutex); + + if (!stage.empty()) { + m_tasks_queue.emplace(terminal_task_ptr); + m_tasks_queue.emplace(stage_task_ptr); + } + m_tasks_queue.emplace(fight_task_ptr); + + if (!only_append) { + return start(false); + } + + return true; +} + +bool asst::Assistant::append_award(bool only_append) +{ + return append_process_task("AwardBegin", "Award"); +} + +bool asst::Assistant::append_visit(bool only_append) +{ + return append_process_task("VisitBegin", "Visit"); +} + +bool asst::Assistant::append_mall(bool with_shopping, bool only_append) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + std::unique_lock lock(m_mutex); + + const std::string task_chain = "Mall"; + + append_process_task("MallBegin", task_chain); + + if (with_shopping) { + auto shopping_task_ptr = std::make_shared(task_callback, (void*)this); + shopping_task_ptr->set_task_chain(task_chain); + m_tasks_queue.emplace(shopping_task_ptr); + } + + if (!only_append) { + return start(false); + } + + return true; +} + +bool Assistant::append_process_task(const std::string& task, std::string task_chain, int retry_times) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + //std::unique_lock lock(m_mutex); + + if (task_chain.empty()) { + task_chain = task; + } + + auto task_ptr = std::make_shared(task_callback, (void*)this); + task_ptr->set_task_chain(task_chain); + task_ptr->set_tasks({ task }); + task_ptr->set_retry_times(retry_times); + + m_tasks_queue.emplace(task_ptr); + + //if (!only_append) { + // return start(false); + //} + + return true; +} + +bool asst::Assistant::append_recruit(unsigned max_times, const std::vector& select_level, const std::vector& confirm_level, bool need_refresh) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + static const std::string TaskChain = "Recruit"; + + append_process_task("RecruitBegin", TaskChain); + + auto recruit_task_ptr = std::make_shared(task_callback, (void*)this); + recruit_task_ptr->set_max_times(max_times); + recruit_task_ptr->set_need_refresh(need_refresh); + recruit_task_ptr->set_select_level(select_level); + recruit_task_ptr->set_confirm_level(confirm_level); + recruit_task_ptr->set_task_chain(TaskChain); + recruit_task_ptr->set_retry_times(AutoRecruitTaskRetryTimesDefault); + + m_tasks_queue.emplace(recruit_task_ptr); + + return true; +} + +#ifdef LOG_TRACE +bool Assistant::append_debug() +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + std::unique_lock lock(m_mutex); + + { + constexpr static const char* DebugTaskChain = "Debug"; + auto shift_task_ptr = std::make_shared(task_callback, (void*)this); + shift_task_ptr->set_work_mode(infrast::WorkMode::Aggressive); + shift_task_ptr->set_facility("Control"); + shift_task_ptr->set_product("MoodAddition"); + shift_task_ptr->set_task_chain(DebugTaskChain); + m_tasks_queue.emplace(shift_task_ptr); + } + + return true; +} +#endif + +bool Assistant::start_recruit_calc(const std::vector& select_level, bool set_time) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + std::unique_lock lock(m_mutex); + + auto task_ptr = std::make_shared(task_callback, (void*)this); + task_ptr->set_param(select_level, set_time); + task_ptr->set_retry_times(OpenRecruitTaskRetryTimesDefault); + task_ptr->set_task_chain("RecruitCalc"); + m_tasks_queue.emplace(task_ptr); + + return start(false); +} + +bool asst::Assistant::append_infrast(infrast::WorkMode work_mode, const std::vector& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + // 保留接口,目前强制按激进模式进行换班 + work_mode = infrast::WorkMode::Aggressive; + + constexpr static const char* InfrastTaskCahin = "Infrast"; + + // 这个流程任务,结束的时候是处于基建主界面的。既可以用于进入基建,也可以用于从设施里返回基建主界面 + + auto append_infrast_begin = [&]() { + append_process_task("InfrastBegin", InfrastTaskCahin); + }; + + append_infrast_begin(); + + auto info_task_ptr = std::make_shared(task_callback, (void*)this); + info_task_ptr->set_work_mode(work_mode); + info_task_ptr->set_task_chain(InfrastTaskCahin); + info_task_ptr->set_mood_threshold(dorm_threshold); + + m_tasks_queue.emplace(info_task_ptr); + + // 因为后期要考虑多任务间的联动等,所以这些任务的声明暂时不放到for循环中 + auto mfg_task_ptr = std::make_shared(task_callback, (void*)this); + mfg_task_ptr->set_work_mode(work_mode); + mfg_task_ptr->set_task_chain(InfrastTaskCahin); + mfg_task_ptr->set_mood_threshold(dorm_threshold); + mfg_task_ptr->set_uses_of_drone(uses_of_drones); + auto trade_task_ptr = std::make_shared(task_callback, (void*)this); + trade_task_ptr->set_work_mode(work_mode); + trade_task_ptr->set_task_chain(InfrastTaskCahin); + trade_task_ptr->set_mood_threshold(dorm_threshold); + trade_task_ptr->set_uses_of_drone(uses_of_drones); + auto power_task_ptr = std::make_shared(task_callback, (void*)this); + power_task_ptr->set_work_mode(work_mode); + power_task_ptr->set_task_chain(InfrastTaskCahin); + power_task_ptr->set_mood_threshold(dorm_threshold); + auto office_task_ptr = std::make_shared(task_callback, (void*)this); + office_task_ptr->set_work_mode(work_mode); + office_task_ptr->set_task_chain(InfrastTaskCahin); + office_task_ptr->set_mood_threshold(dorm_threshold); + auto recpt_task_ptr = std::make_shared(task_callback, (void*)this); + recpt_task_ptr->set_work_mode(work_mode); + recpt_task_ptr->set_task_chain(InfrastTaskCahin); + recpt_task_ptr->set_mood_threshold(dorm_threshold); + auto control_task_ptr = std::make_shared(task_callback, (void*)this); + control_task_ptr->set_work_mode(work_mode); + control_task_ptr->set_task_chain(InfrastTaskCahin); + control_task_ptr->set_mood_threshold(dorm_threshold); + + auto dorm_task_ptr = std::make_shared(task_callback, (void*)this); + dorm_task_ptr->set_work_mode(work_mode); + dorm_task_ptr->set_task_chain(InfrastTaskCahin); + dorm_task_ptr->set_mood_threshold(dorm_threshold); + + for (const auto& facility : order) { + if (facility == "Dorm") { + m_tasks_queue.emplace(dorm_task_ptr); + } + else if (facility == "Mfg") { + m_tasks_queue.emplace(mfg_task_ptr); + } + else if (facility == "Trade") { + m_tasks_queue.emplace(trade_task_ptr); + } + else if (facility == "Power") { + m_tasks_queue.emplace(power_task_ptr); + } + else if (facility == "Office") { + m_tasks_queue.emplace(office_task_ptr); + } + else if (facility == "Reception") { + m_tasks_queue.emplace(recpt_task_ptr); + } + else if (facility == "Control") { + m_tasks_queue.emplace(control_task_ptr); + } + else { + Log.error("append_infrast | Unknown facility", facility); + } + append_infrast_begin(); + } + + if (!only_append) { + return start(false); + } + + return true; +} + +void asst::Assistant::set_penguin_id(const std::string& id) +{ + auto& opt = Resrc.cfg().get_options(); + if (id.empty()) { + opt.penguin_report.extra_param.clear(); + } + else { + opt.penguin_report.extra_param = "-H \"authorization: PenguinID " + id + "\""; + } +} + +bool asst::Assistant::start(bool block) +{ + LogTraceFunction; + Log.trace("Start |", block ? "block" : "non block"); + + if (!m_thread_idle || !m_inited) { + return false; + } + std::unique_lock lock; + if (block) { // 外部调用 + lock = std::unique_lock(m_mutex); + } + + m_thread_idle = false; + m_condvar.notify_one(); + + return true; +} + +bool Assistant::stop(bool block) +{ + LogTraceFunction; + Log.trace("Stop |", block ? "block" : "non block"); + + m_thread_idle = true; + + std::unique_lock lock; + if (block) { // 外部调用 + lock = std::unique_lock(m_mutex); + } + decltype(m_tasks_queue) empty; + m_tasks_queue.swap(empty); + + clear_cache(); + + return true; +} + +void Assistant::working_proc() +{ + LogTraceFunction; + + std::string pre_taskchain; + while (!m_thread_exit) { + //LogTraceScope("Assistant::working_proc Loop"); + std::unique_lock lock(m_mutex); + + if (!m_thread_idle && !m_tasks_queue.empty()) { + auto start_time = std::chrono::steady_clock::now(); + + auto task_ptr = m_tasks_queue.front(); + + std::string cur_taskchain = task_ptr->get_task_chain(); + json::value task_json = json::object{ + {"task_chain", cur_taskchain}, + }; + + if (cur_taskchain != pre_taskchain) { + task_callback(AsstMsg::TaskChainStart, task_json, this); + pre_taskchain = cur_taskchain; + } + + task_ptr->set_exit_flag(&m_thread_idle); + bool ret = task_ptr->run(); + m_tasks_queue.pop(); + + if (!ret) { + task_callback(AsstMsg::TaskError, task_json, this); + } + else if (m_tasks_queue.empty() || cur_taskchain != m_tasks_queue.front()->get_task_chain()) { + task_callback(AsstMsg::TaskChainCompleted, task_json, this); + } + if (m_tasks_queue.empty()) { + task_callback(AsstMsg::AllTasksCompleted, task_json, this); + } + + //clear_cache(); + + auto& delay = Resrc.cfg().get_options().task_delay; + m_condvar.wait_until(lock, start_time + std::chrono::milliseconds(delay), + [&]() -> bool { return m_thread_idle; }); + } + else { + pre_taskchain.clear(); + m_thread_idle = true; + Log.flush(); + //controller.set_idle(true); + m_condvar.wait(lock); + } + } +} + +void Assistant::msg_proc() +{ + LogTraceFunction; + + while (!m_thread_exit) { + //LogTraceScope("Assistant::msg_proc Loop"); + std::unique_lock lock(m_msg_mutex); + if (!m_msg_queue.empty()) { + // 结构化绑定只能是引用,后续的pop会使引用失效,所以需要重新构造一份,这里采用了move的方式 + auto&& [temp_msg, temp_detail] = m_msg_queue.front(); + AsstMsg msg = std::move(temp_msg); + json::value detail = std::move(temp_detail); + m_msg_queue.pop(); + lock.unlock(); + + if (m_callback) { + m_callback(msg, detail, m_callback_arg); + } + } + else { + m_msg_condvar.wait(lock); + } + } +} + +void Assistant::task_callback(AsstMsg msg, const json::value& detail, void* custom_arg) +{ + Log.trace("Assistant::task_callback |", msg, detail.to_string()); + + Assistant* p_this = (Assistant*)custom_arg; + json::value more_detail = detail; + switch (msg) { + case AsstMsg::PtrIsNull: + case AsstMsg::ImageIsEmpty: + p_this->stop(false); + break; + case AsstMsg::StageDrops: + more_detail = p_this->organize_stage_drop(more_detail); + break; + default: + break; + } + + // Todo: 有些不需要回调给外部的消息,得在这里给拦截掉 + // 加入回调消息队列,由回调消息线程外抛给外部 + p_this->append_callback(msg, std::move(more_detail)); +} + +void asst::Assistant::append_callback(AsstMsg msg, json::value detail) +{ + std::unique_lock lock(m_msg_mutex); + m_msg_queue.emplace(msg, std::move(detail)); + m_msg_condvar.notify_one(); +} + +void Assistant::clear_cache() +{ + Resrc.item().clear_drop_count(); + //task.clear_cache(); +} + +json::value asst::Assistant::organize_stage_drop(const json::value& rec) +{ + json::value dst = rec; + auto& item = Resrc.item(); + for (json::value& drop : dst["drops"].as_array()) { + std::string id = drop["itemId"].as_string(); + int quantity = drop["quantity"].as_integer(); + item.increase_drop_count(id, quantity); + const std::string& name = item.get_item_name(id); + drop["itemName"] = name.empty() ? "未知材料" : name; + } + std::vector statistics_vec; + for (auto&& [id, count] : item.get_drop_count()) { + json::value info; + info["itemId"] = id; + const std::string& name = item.get_item_name(id); + info["itemName"] = name.empty() ? "未知材料" : name; + info["count"] = count; + statistics_vec.emplace_back(std::move(info)); + } + //// 排个序,数量多的放前面 + //std::sort(statistics_vec.begin(), statistics_vec.end(), + // [](const json::value& lhs, const json::value& rhs) -> bool { + // return lhs.at("count").as_integer() > rhs.at("count").as_integer(); + // }); + + dst["statistics"] = json::array(std::move(statistics_vec)); + + Log.trace("organize_stage_drop | ", dst.to_string()); + + return dst; +} diff --git a/src/MeoAssistant/Assistant.h b/src/MeoAssistant/Assistant.h index e39592c67e..ee81b4af3d 100644 --- a/src/MeoAssistant/Assistant.h +++ b/src/MeoAssistant/Assistant.h @@ -1,111 +1,111 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "AbstractTask.h" -#include "AsstDef.h" -#include "AsstMsg.h" -#include "AsstInfrastDef.h" - -namespace cv -{ - class Mat; -} - -namespace asst -{ - class Controller; - class Identify; - - class Assistant - { - public: - Assistant(std::string dirname, AsstCallback callback = nullptr, void* callback_arg = nullptr); - ~Assistant(); - - // 根据配置文件,决定捕获模拟器、USB 还是远程设备 - bool catch_default(); - // 捕获模拟器 - bool catch_emulator(const std::string& emulator_name = std::string()); - // 捕获自定义设备 - bool catch_custom(const std::string& address = std::string()); - // 不实际进行捕获,调试用接口 - bool catch_fake(); - - // 添加开始游戏的任务(进入到主界面) - bool append_start_up(bool only_append = true); - // 添加刷理智任务 - bool append_fight(const std::string& stage, int mecidine = 0, int stone = 0, int times = INT_MAX, bool only_append = true); - // 添加领取日常任务奖励任务 - bool append_award(bool only_append = true); - // 添加访问好友任务 - bool append_visit(bool only_append = true); - // 添加领取当日信用及信用购的任务 - bool append_mall(bool with_shopping, bool only_append = true); - - // 添加基建换班任务任务 - bool append_infrast(infrast::WorkMode work_mode, const std::vector& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append = true); - - // 添加自动公招任务 - // 参数max_times: 最多进行几次公招 - // 参数select_level: 需要的选择Tags的等级 - // 参数confirm_level: 需要点击确认按钮的等级 - bool append_recruit(unsigned max_times, const std::vector& select_level, const std::vector& confirm_level, bool need_refresh); - -#ifdef LOG_TRACE - // 调试用 - bool append_debug(); -#endif - - // 开始公开招募计算 - bool start_recruit_calc(const std::vector& select_level, bool set_time); - - // 开始执行任务队列 - bool start(bool block = true); - // 停止任务队列并清空 - bool stop(bool block = true); - - // 设置企鹅数据汇报个人ID - void set_penguin_id(const std::string& id); - - [[deprecated]] bool set_param(const std::string& type, const std::string& param, const std::string& value); - - static constexpr int ProcessTaskRetryTimesDefault = 20; - static constexpr int OpenRecruitTaskRetryTimesDefault = 10; - static constexpr int AutoRecruitTaskRetryTimesDefault = 5; - - private: - void working_proc(); - void msg_proc(); - static void task_callback(AsstMsg msg, const json::value& detail, void* custom_arg); - - bool append_process_task(const std::string& task, std::string task_chain = std::string(), int retry_times = ProcessTaskRetryTimesDefault); - void append_callback(AsstMsg msg, json::value detail); - void clear_cache(); - json::value organize_stage_drop(const json::value& rec); // 整理关卡掉落的材料信息 - - bool m_inited = false; - std::string m_dirname; - - bool m_thread_exit = false; - std::queue> m_tasks_queue; - AsstCallback m_callback = nullptr; - void* m_callback_arg = nullptr; - - bool m_thread_idle = true; - std::thread m_working_thread; - std::mutex m_mutex; - std::condition_variable m_condvar; - - std::thread m_msg_thread; - std::queue> m_msg_queue; - std::mutex m_msg_mutex; - std::condition_variable m_msg_condvar; - }; -} +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "AbstractTask.h" +#include "AsstDef.h" +#include "AsstMsg.h" +#include "AsstInfrastDef.h" + +namespace cv +{ + class Mat; +} + +namespace asst +{ + class Controller; + class Identify; + + class Assistant + { + public: + Assistant(std::string dirname, AsstCallback callback = nullptr, void* callback_arg = nullptr); + ~Assistant(); + + // 根据配置文件,决定捕获模拟器、USB 还是远程设备 + bool catch_default(); + // 捕获模拟器 + bool catch_emulator(const std::string& emulator_name = std::string()); + // 捕获自定义设备 + bool catch_custom(const std::string& address = std::string()); + // 不实际进行捕获,调试用接口 + bool catch_fake(); + + // 添加开始游戏的任务(进入到主界面) + bool append_start_up(bool only_append = true); + // 添加刷理智任务 + bool append_fight(const std::string& stage, int mecidine = 0, int stone = 0, int times = INT_MAX, bool only_append = true); + // 添加领取日常任务奖励任务 + bool append_award(bool only_append = true); + // 添加访问好友任务 + bool append_visit(bool only_append = true); + // 添加领取当日信用及信用购的任务 + bool append_mall(bool with_shopping, bool only_append = true); + + // 添加基建换班任务任务 + bool append_infrast(infrast::WorkMode work_mode, const std::vector& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append = true); + + // 添加自动公招任务 + // 参数max_times: 最多进行几次公招 + // 参数select_level: 需要的选择Tags的等级 + // 参数confirm_level: 需要点击确认按钮的等级 + bool append_recruit(unsigned max_times, const std::vector& select_level, const std::vector& confirm_level, bool need_refresh); + +#ifdef LOG_TRACE + // 调试用 + bool append_debug(); +#endif + + // 开始公开招募计算 + bool start_recruit_calc(const std::vector& select_level, bool set_time); + + // 开始执行任务队列 + bool start(bool block = true); + // 停止任务队列并清空 + bool stop(bool block = true); + + // 设置企鹅数据汇报个人ID + void set_penguin_id(const std::string& id); + + [[deprecated]] bool set_param(const std::string& type, const std::string& param, const std::string& value); + + static constexpr int ProcessTaskRetryTimesDefault = 20; + static constexpr int OpenRecruitTaskRetryTimesDefault = 10; + static constexpr int AutoRecruitTaskRetryTimesDefault = 5; + + private: + void working_proc(); + void msg_proc(); + static void task_callback(AsstMsg msg, const json::value& detail, void* custom_arg); + + bool append_process_task(const std::string& task, std::string task_chain = std::string(), int retry_times = ProcessTaskRetryTimesDefault); + void append_callback(AsstMsg msg, json::value detail); + void clear_cache(); + json::value organize_stage_drop(const json::value& rec); // 整理关卡掉落的材料信息 + + bool m_inited = false; + std::string m_dirname; + + bool m_thread_exit = false; + std::queue> m_tasks_queue; + AsstCallback m_callback = nullptr; + void* m_callback_arg = nullptr; + + bool m_thread_idle = true; + std::thread m_working_thread; + std::mutex m_mutex; + std::condition_variable m_condvar; + + std::thread m_msg_thread; + std::queue> m_msg_queue; + std::mutex m_msg_mutex; + std::condition_variable m_msg_condvar; + }; +} diff --git a/src/MeoAssistant/AsstCaller.cpp b/src/MeoAssistant/AsstCaller.cpp index bf10c9b52a..3f00b85413 100644 --- a/src/MeoAssistant/AsstCaller.cpp +++ b/src/MeoAssistant/AsstCaller.cpp @@ -1,259 +1,264 @@ -#include "AsstCaller.h" - -#include - -#include - -#include "Assistant.h" -#include "AsstDef.h" -#include "AsstUtils.hpp" -#include "Version.h" - -#if 0 -#if _MSC_VER -// Win32平台下Dll的入口 -BOOL APIENTRY DllMain(HINSTANCE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved -) -{ - UNREFERENCED_PARAMETER(hModule); - UNREFERENCED_PARAMETER(lpReserved); - switch (ul_reason_for_call) { - case DLL_PROCESS_ATTACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; - } - return TRUE; -} -#elif VA_GNUC - -#endif -#endif - -AsstCallback _callback = nullptr; - -void CallbackTrans(asst::AsstMsg msg, const json::value& json, void* custom_arg) -{ - _callback(static_cast(msg), asst::utils::utf8_to_gbk(json.to_string()).c_str(), custom_arg); -} - -asst::Assistant* AsstCreate(const char* dirname) -{ - try { - return new asst::Assistant(dirname); - } - catch (...) { - return nullptr; - } -} - -asst::Assistant* AsstCreateEx(const char* dirname, AsstCallback callback, void* custom_arg) -{ - try { - // 创建多实例回调会有问题,有空再慢慢整 - _callback = callback; - return new asst::Assistant(dirname, CallbackTrans, custom_arg); - } - catch (...) { - return nullptr; - } -} - -void AsstDestroy(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return; - } - - delete p_asst; - p_asst = nullptr; -} - -bool AsstCatchDefault(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return ((asst::Assistant*)p_asst)->catch_default(); -} - -bool AsstCatchEmulator(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return ((asst::Assistant*)p_asst)->catch_emulator(); -} - -bool AsstCatchCustom(asst::Assistant* p_asst, const char* address) -{ - if (p_asst == nullptr) { - return false; - } - - return ((asst::Assistant*)p_asst)->catch_custom(address); -} - -bool AsstCatchFake(asst::Assistant* p_asst) -{ -#ifdef LOG_TRACE - if (p_asst == nullptr) { - return false; - } - - return p_asst->catch_fake(); -#else - return false; -#endif // LOG_TRACE -} - -bool ASSTAPI AsstAppendStartUp(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_start_up(); -} - -bool AsstAppendFight(asst::Assistant* p_asst, const char* stage, int max_mecidine, int max_stone, int max_times) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_fight(stage, max_mecidine, max_stone, max_times); -} - -bool AsstAppendAward(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_award(); -} - -bool AsstAppendVisit(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_visit(); -} - -bool AsstAppendMall(asst::Assistant* p_asst, bool with_shopping) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_mall(with_shopping); -} - -//bool AsstAppendProcessTask(asst::Assistant* p_asst, const char* task) -//{ -// if (p_asst == nullptr) { -// return false; -// } -// -// return p_asst->append_process_task(task); -//} - -bool AsstStartRecruitCalc(asst::Assistant* p_asst, const int select_level[], int required_len, bool set_time) -{ - if (p_asst == nullptr) { - return false; - } - std::vector level_vector; - level_vector.assign(select_level, select_level + required_len); - return p_asst->start_recruit_calc(level_vector, set_time); -} - -bool AsstAppendInfrast(asst::Assistant* p_asst, int work_mode, const char** order, int order_size, const char* uses_of_drones, double dorm_threshold) -{ - if (p_asst == nullptr) { - return false; - } - std::vector order_vector; - order_vector.assign(order, order + order_size); - - return p_asst->append_infrast( - static_cast(work_mode), - order_vector, - uses_of_drones, - dorm_threshold); -} - -bool AsstAppendRecruit(asst::Assistant* p_asst, int max_times, const int select_level[], int select_len, const int confirm_level[], int confirm_len, bool need_refresh) -{ - if (p_asst == nullptr) { - return false; - } - std::vector required_vector; - required_vector.assign(select_level, select_level + select_len); - std::vector confirm_vector; - confirm_vector.assign(confirm_level, confirm_level + confirm_len); - - return p_asst->append_recruit(max_times, required_vector, confirm_vector, need_refresh); -} - -bool AsstStart(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->start(); -} - -bool AsstStop(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->stop(); -} - -bool AsstSetPenguinId(asst::Assistant* p_asst, const char* id) -{ - if (p_asst == nullptr) { - return false; - } - - p_asst->set_penguin_id(id); - return true; -} - -//bool AsstSetParam(asst::Assistant* p_asst, const char* type, const char* param, const char* value) -//{ -// if (p_asst == nullptr) { -// return false; -// } -// -// return p_asst->set_param(type, param, value); -//} - -const char* AsstGetVersion() -{ - return asst::Version; -} - -bool AsstAppendDebug(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } -#if LOG_TRACE - return p_asst->append_debug(); -#else - return false; -#endif // LOG_TRACE -} +#include "AsstCaller.h" + +#include +#include + +#include + +#include "Assistant.h" +#include "AsstDef.h" +#include "AsstUtils.hpp" +#include "Version.h" + +#if 0 +#if _MSC_VER +// Win32平台下Dll的入口 +BOOL APIENTRY DllMain(HINSTANCE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved +) +{ + UNREFERENCED_PARAMETER(hModule); + UNREFERENCED_PARAMETER(lpReserved); + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#elif VA_GNUC + +#endif +#endif + +AsstCallback _callback = nullptr; + +void CallbackTrans(asst::AsstMsg msg, const json::value& json, void* custom_arg) +{ + _callback(static_cast(msg), asst::utils::utf8_to_gbk(json.to_string()).c_str(), custom_arg); +} + +asst::Assistant* AsstCreate(const char* dirname) +{ + try { + return new asst::Assistant(dirname); + } + catch (std::exception& e) { + std::cerr << "create failed: " << e.what() << std::endl; + } + catch (...) { + std::cerr << "create failed: unknown exception" << std::endl; + } + return nullptr; +} + +asst::Assistant* AsstCreateEx(const char* dirname, AsstCallback callback, void* custom_arg) +{ + try { + // 创建多实例回调会有问题,有空再慢慢整 + _callback = callback; + return new asst::Assistant(dirname, CallbackTrans, custom_arg); + } + catch (...) { + return nullptr; + } +} + +void AsstDestroy(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return; + } + + delete p_asst; + p_asst = nullptr; +} + +bool AsstCatchDefault(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return ((asst::Assistant*)p_asst)->catch_default(); +} + +bool AsstCatchEmulator(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return ((asst::Assistant*)p_asst)->catch_emulator(); +} + +bool AsstCatchCustom(asst::Assistant* p_asst, const char* address) +{ + if (p_asst == nullptr) { + return false; + } + + return ((asst::Assistant*)p_asst)->catch_custom(address); +} + +bool AsstCatchFake(asst::Assistant* p_asst) +{ +#ifdef LOG_TRACE + if (p_asst == nullptr) { + return false; + } + + return p_asst->catch_fake(); +#else + return false; +#endif // LOG_TRACE +} + +bool ASSTAPI AsstAppendStartUp(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_start_up(); +} + +bool AsstAppendFight(asst::Assistant* p_asst, const char* stage, int max_mecidine, int max_stone, int max_times) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_fight(stage, max_mecidine, max_stone, max_times); +} + +bool AsstAppendAward(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_award(); +} + +bool AsstAppendVisit(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_visit(); +} + +bool AsstAppendMall(asst::Assistant* p_asst, bool with_shopping) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_mall(with_shopping); +} + +//bool AsstAppendProcessTask(asst::Assistant* p_asst, const char* task) +//{ +// if (p_asst == nullptr) { +// return false; +// } +// +// return p_asst->append_process_task(task); +//} + +bool AsstStartRecruitCalc(asst::Assistant* p_asst, const int select_level[], int required_len, bool set_time) +{ + if (p_asst == nullptr) { + return false; + } + std::vector level_vector; + level_vector.assign(select_level, select_level + required_len); + return p_asst->start_recruit_calc(level_vector, set_time); +} + +bool AsstAppendInfrast(asst::Assistant* p_asst, int work_mode, const char** order, int order_size, const char* uses_of_drones, double dorm_threshold) +{ + if (p_asst == nullptr) { + return false; + } + std::vector order_vector; + order_vector.assign(order, order + order_size); + + return p_asst->append_infrast( + static_cast(work_mode), + order_vector, + uses_of_drones, + dorm_threshold); +} + +bool AsstAppendRecruit(asst::Assistant* p_asst, int max_times, const int select_level[], int select_len, const int confirm_level[], int confirm_len, bool need_refresh) +{ + if (p_asst == nullptr) { + return false; + } + std::vector required_vector; + required_vector.assign(select_level, select_level + select_len); + std::vector confirm_vector; + confirm_vector.assign(confirm_level, confirm_level + confirm_len); + + return p_asst->append_recruit(max_times, required_vector, confirm_vector, need_refresh); +} + +bool AsstStart(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->start(); +} + +bool AsstStop(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->stop(); +} + +bool AsstSetPenguinId(asst::Assistant* p_asst, const char* id) +{ + if (p_asst == nullptr) { + return false; + } + + p_asst->set_penguin_id(id); + return true; +} + +//bool AsstSetParam(asst::Assistant* p_asst, const char* type, const char* param, const char* value) +//{ +// if (p_asst == nullptr) { +// return false; +// } +// +// return p_asst->set_param(type, param, value); +//} + +const char* AsstGetVersion() +{ + return asst::Version; +} + +bool AsstAppendDebug(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } +#if LOG_TRACE + return p_asst->append_debug(); +#else + return false; +#endif // LOG_TRACE +} diff --git a/src/MeoAssistant/AsstDef.h b/src/MeoAssistant/AsstDef.h index 48d91ef472..cfcbe29a9c 100644 --- a/src/MeoAssistant/AsstDef.h +++ b/src/MeoAssistant/AsstDef.h @@ -1,258 +1,259 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace json -{ - class value; -} - -namespace asst -{ - constexpr double DoubleDiff = 1e-12; - - constexpr static int WindowWidthDefault = 1280; - constexpr static int WindowHeightDefault = 720; - - constexpr static double TemplThresholdDefault = 0.8; - - struct Point - { - Point() = default; - Point(const Point&) noexcept = default; - Point(Point&&) noexcept = default; - Point(int x, int y) : x(x), y(y) {} - Point& operator=(const Point&) noexcept = default; - Point& operator=(Point&&) noexcept = default; - int x = 0; - int y = 0; - }; - - struct Rect - { - Rect() = default; - Rect(const Rect&) noexcept = default; - Rect(Rect&&) noexcept = default; - Rect(int x, int y, int width, int height) - : x(x), y(y), width(width), height(height) - {} - Rect operator*(double rhs) const - { - return { x, y, static_cast(width * rhs), static_cast(height * rhs) }; - } - int area() const noexcept - { - return width * height; - } - Rect center_zoom(double scale, int max_width = INT_MAX, int max_height = INT_MAX) const - { - int half_width_scale = static_cast(width * (1 - scale) / 2); - int half_hight_scale = static_cast(height * (1 - scale) / 2); - Rect dst(x + half_width_scale, y + half_hight_scale, - static_cast(width * scale), static_cast(height * scale)); - if (dst.x < 0) { - dst.x = 0; - } - if (dst.y < 0) { - dst.y = 0; - } - if (dst.width + dst.x >= max_width) { - dst.width = max_width - dst.x; - } - if (dst.height + dst.y >= max_height) { - dst.height = max_height - dst.y; - } - return dst; - } - Rect& operator=(const Rect&) noexcept = default; - Rect& operator=(Rect&&) noexcept = default; - bool empty() const noexcept { return width == 0 || height == 0; } - bool operator==(const Rect& rhs) const noexcept - { - return x == rhs.x && y == rhs.y && width == rhs.width && height == rhs.height; - } - bool include(const Rect& rhs) const noexcept - { - return x <= rhs.x && y <= rhs.y && (x + width) >= (rhs.x + rhs.width) && (y + height) >= (rhs.y + rhs.height); - } - std::string to_string() const - { - return "[ " + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(width) + ", " + std::to_string(height) + " ]"; - } - - int x = 0; - int y = 0; - int width = 0; - int height = 0; - }; - - struct TextRect - { - TextRect() = default; - TextRect(const TextRect&) = default; - TextRect(TextRect&&) noexcept = default; - - explicit operator std::string() const noexcept { return text; } - explicit operator Rect() const noexcept { return rect; } - std::string to_string() const - { - return text + " : " + rect.to_string() + ", score: " + std::to_string(score); - } - TextRect& operator=(const TextRect&) = default; - TextRect& operator=(TextRect&&) noexcept = default; - bool operator==(const TextRect& rhs) const noexcept - { - return text == rhs.text && rect == rhs.rect; - } - - std::string text; - Rect rect; - float score; - }; - using TextRectProc = std::function; - - enum class AlgorithmType - { - Invaild = -1, - JustReturn, - MatchTemplate, - CompareHist, - OcrDetect - }; - - struct MatchRect - { - MatchRect() = default; - MatchRect(const MatchRect&) = default; - MatchRect(MatchRect&&) noexcept = default; - - explicit operator Rect() const noexcept { return rect; } - MatchRect& operator=(const MatchRect&) = default; - MatchRect& operator=(MatchRect&&) noexcept = default; - - AlgorithmType algorithm = AlgorithmType::Invaild; - double score = 0.0; - Rect rect; - }; -} - -namespace std -{ - template <> - class hash - { - public: - size_t operator()(const asst::Rect& rect) const - { - return std::hash()(rect.x) ^ std::hash()(rect.y) ^ std::hash()(rect.width) ^ std::hash()(rect.height); - } - }; - template <> - class hash - { - public: - size_t operator()(const asst::TextRect& tr) const - { - return std::hash()(tr.text) ^ std::hash()(tr.rect); - } - }; -} - -namespace asst -{ - enum class ProcessTaskAction - { - Invalid = 0, - BasicClick = 0x100, - ClickSelf = BasicClick | 1, // 点击自身位置 - ClickRect = BasicClick | 2, // 点击指定区域 - ClickRand = BasicClick | 4, // 点击随机区域 - DoNothing = 0x200, // 什么都不做 - Stop = 0x400, // 停止当前Task - StageDrops = 0x800, // 关卡结束,特化动作 - BasicSwipe = 0x1000, - SwipeToTheLeft = BasicSwipe | 1, // 往左划一下 - SwipeToTheRight = BasicSwipe | 2, // 往右划一下 - }; - - // 任务信息 - struct TaskInfo - { - virtual ~TaskInfo() = default; - std::string name; // 任务名 - AlgorithmType algorithm = // 图像算法类型 - AlgorithmType::Invaild; - ProcessTaskAction action = // 要进行的操作 - ProcessTaskAction::Invalid; - std::vector next; // 下一个可能的任务(列表) - [[ deprecated ]] int exec_times = 0; // [已废弃] 任务已执行了多少次 - int max_times = INT_MAX; // 任务最多执行多少次 - std::vector exceeded_next; // 达到最多次数了之后,下一个可能的任务(列表) - std::vector reduce_other_times; // 执行了该任务后,需要减少别的任务的执行次数。例如执行了吃理智药,则说明上一次点击蓝色开始行动按钮没生效,所以蓝色开始行动要-1 - asst::Rect specific_rect; // 指定区域,目前仅针对ClickRect任务有用,会点这个区域 - int pre_delay = 0; // 执行该任务前的延时 - int rear_delay = 0; // 执行该任务后的延时 - int retry_times = INT_MAX; // 未找到图像时的重试次数 - Rect roi; // 要识别的区域,若为0则全图识别 - Rect rect_move; // 识别结果移动:有些结果识别到的,和要点击的不是同一个位置。即识别到了res,点击res + result_move的位置 - bool cache = false; // 是否使用缓存区域 - Rect region_of_appeared; // 缓存区域:上次识别成功过,本次只在这个rect里识别,省点性能 - }; - - // 文字识别任务的信息 - struct OcrTaskInfo : public TaskInfo - { - virtual ~OcrTaskInfo() = default; - std::vector text; // 文字的容器,匹配到这里面任一个,就算匹配上了 - bool need_full_match = false; // 是否需要全匹配,否则搜索到子串就算匹配上了 - std::unordered_map - replace_map; // 部分文字容易识别错,字符串强制replace之后,再进行匹配 - }; - - // 图片匹配任务的信息 - struct MatchTaskInfo : public TaskInfo - { - virtual ~MatchTaskInfo() = default; - std::string templ_name; // 匹配模板图片文件名 - double templ_threshold = 0; // 模板匹配阈值 - double special_threshold = 0; // 某些任务使用的特殊的阈值 - std::pair mask_range; // 掩码的二值化范围 - }; - - struct HandleInfo - { - std::string class_name; - std::string window_name; - }; - - struct AdbCmd - { - std::string path; - std::vector addresses; // 会优先尝试连接addresses中的地址,若均失败,则会使用devices获取地址 - std::string devices; - std::string address_regex; - std::string connect; - std::string click; - std::string swipe; - std::string display; - std::string display_format; - std::string screencap; - std::string release; - //std::string pullscreen; - int display_width = 0; - int display_height = 0; - }; - - struct EmulatorInfo - { - std::string name; - HandleInfo handle; - AdbCmd adb; - std::string path; - }; -} +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace json +{ + class value; +} + +namespace asst +{ + constexpr double DoubleDiff = 1e-12; + + constexpr static int WindowWidthDefault = 1280; + constexpr static int WindowHeightDefault = 720; + + constexpr static double TemplThresholdDefault = 0.8; + + struct Point + { + Point() = default; + Point(const Point&) noexcept = default; + Point(Point&&) noexcept = default; + Point(int x, int y) : x(x), y(y) {} + Point& operator=(const Point&) noexcept = default; + Point& operator=(Point&&) noexcept = default; + int x = 0; + int y = 0; + }; + + struct Rect + { + Rect() = default; + Rect(const Rect&) noexcept = default; + Rect(Rect&&) noexcept = default; + Rect(int x, int y, int width, int height) + : x(x), y(y), width(width), height(height) + {} + Rect operator*(double rhs) const + { + return { x, y, static_cast(width * rhs), static_cast(height * rhs) }; + } + int area() const noexcept + { + return width * height; + } + Rect center_zoom(double scale, int max_width = INT_MAX, int max_height = INT_MAX) const + { + int half_width_scale = static_cast(width * (1 - scale) / 2); + int half_hight_scale = static_cast(height * (1 - scale) / 2); + Rect dst(x + half_width_scale, y + half_hight_scale, + static_cast(width * scale), static_cast(height * scale)); + if (dst.x < 0) { + dst.x = 0; + } + if (dst.y < 0) { + dst.y = 0; + } + if (dst.width + dst.x >= max_width) { + dst.width = max_width - dst.x; + } + if (dst.height + dst.y >= max_height) { + dst.height = max_height - dst.y; + } + return dst; + } + Rect& operator=(const Rect&) noexcept = default; + Rect& operator=(Rect&&) noexcept = default; + bool empty() const noexcept { return width == 0 || height == 0; } + bool operator==(const Rect& rhs) const noexcept + { + return x == rhs.x && y == rhs.y && width == rhs.width && height == rhs.height; + } + bool include(const Rect& rhs) const noexcept + { + return x <= rhs.x && y <= rhs.y && (x + width) >= (rhs.x + rhs.width) && (y + height) >= (rhs.y + rhs.height); + } + std::string to_string() const + { + return "[ " + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(width) + ", " + std::to_string(height) + " ]"; + } + + int x = 0; + int y = 0; + int width = 0; + int height = 0; + }; + + struct TextRect + { + TextRect() = default; + TextRect(const TextRect&) = default; + TextRect(TextRect&&) noexcept = default; + + explicit operator std::string() const noexcept { return text; } + explicit operator Rect() const noexcept { return rect; } + std::string to_string() const + { + return text + " : " + rect.to_string() + ", score: " + std::to_string(score); + } + TextRect& operator=(const TextRect&) = default; + TextRect& operator=(TextRect&&) noexcept = default; + bool operator==(const TextRect& rhs) const noexcept + { + return text == rhs.text && rect == rhs.rect; + } + + std::string text; + Rect rect; + float score; + }; + using TextRectProc = std::function; + + enum class AlgorithmType + { + Invaild = -1, + JustReturn, + MatchTemplate, + CompareHist, + OcrDetect + }; + + struct MatchRect + { + MatchRect() = default; + MatchRect(const MatchRect&) = default; + MatchRect(MatchRect&&) noexcept = default; + + explicit operator Rect() const noexcept { return rect; } + MatchRect& operator=(const MatchRect&) = default; + MatchRect& operator=(MatchRect&&) noexcept = default; + + AlgorithmType algorithm = AlgorithmType::Invaild; + double score = 0.0; + Rect rect; + }; +} + +namespace std +{ + template <> + class hash + { + public: + size_t operator()(const asst::Rect& rect) const + { + return std::hash()(rect.x) ^ std::hash()(rect.y) ^ std::hash()(rect.width) ^ std::hash()(rect.height); + } + }; + template <> + class hash + { + public: + size_t operator()(const asst::TextRect& tr) const + { + return std::hash()(tr.text) ^ std::hash()(tr.rect); + } + }; +} + +namespace asst +{ + enum class ProcessTaskAction + { + Invalid = 0, + BasicClick = 0x100, + ClickSelf = BasicClick | 1, // 点击自身位置 + ClickRect = BasicClick | 2, // 点击指定区域 + ClickRand = BasicClick | 4, // 点击随机区域 + DoNothing = 0x200, // 什么都不做 + Stop = 0x400, // 停止当前Task + StageDrops = 0x800, // 关卡结束,特化动作 + BasicSwipe = 0x1000, + SwipeToTheLeft = BasicSwipe | 1, // 往左划一下 + SwipeToTheRight = BasicSwipe | 2, // 往右划一下 + }; + + // 任务信息 + struct TaskInfo + { + virtual ~TaskInfo() = default; + std::string name; // 任务名 + AlgorithmType algorithm = // 图像算法类型 + AlgorithmType::Invaild; + ProcessTaskAction action = // 要进行的操作 + ProcessTaskAction::Invalid; + std::vector next; // 下一个可能的任务(列表) + [[ deprecated ]] int exec_times = 0; // [已废弃] 任务已执行了多少次 + int max_times = INT_MAX; // 任务最多执行多少次 + std::vector exceeded_next; // 达到最多次数了之后,下一个可能的任务(列表) + std::vector reduce_other_times; // 执行了该任务后,需要减少别的任务的执行次数。例如执行了吃理智药,则说明上一次点击蓝色开始行动按钮没生效,所以蓝色开始行动要-1 + asst::Rect specific_rect; // 指定区域,目前仅针对ClickRect任务有用,会点这个区域 + int pre_delay = 0; // 执行该任务前的延时 + int rear_delay = 0; // 执行该任务后的延时 + int retry_times = INT_MAX; // 未找到图像时的重试次数 + Rect roi; // 要识别的区域,若为0则全图识别 + Rect rect_move; // 识别结果移动:有些结果识别到的,和要点击的不是同一个位置。即识别到了res,点击res + result_move的位置 + bool cache = false; // 是否使用缓存区域 + Rect region_of_appeared; // 缓存区域:上次识别成功过,本次只在这个rect里识别,省点性能 + }; + + // 文字识别任务的信息 + struct OcrTaskInfo : public TaskInfo + { + virtual ~OcrTaskInfo() = default; + std::vector text; // 文字的容器,匹配到这里面任一个,就算匹配上了 + bool need_full_match = false; // 是否需要全匹配,否则搜索到子串就算匹配上了 + std::unordered_map + replace_map; // 部分文字容易识别错,字符串强制replace之后,再进行匹配 + }; + + // 图片匹配任务的信息 + struct MatchTaskInfo : public TaskInfo + { + virtual ~MatchTaskInfo() = default; + std::string templ_name; // 匹配模板图片文件名 + double templ_threshold = 0; // 模板匹配阈值 + double special_threshold = 0; // 某些任务使用的特殊的阈值 + std::pair mask_range; // 掩码的二值化范围 + }; + + struct HandleInfo + { + std::string class_name; + std::string window_name; + }; + + struct AdbCmd + { + std::string path; + std::vector addresses; // 会优先尝试连接addresses中的地址,若均失败,则会使用devices获取地址 + std::string devices; + std::string address_regex; + std::string connect; + std::string click; + std::string swipe; + std::string display; + std::string display_format; + std::string screencap; + std::string release; + //std::string pullscreen; + int display_width = 0; + int display_height = 0; + }; + + struct EmulatorInfo + { + std::string name; + HandleInfo handle; + AdbCmd adb; + std::string path; + }; +} diff --git a/src/MeoAssistant/AsstUtils.hpp b/src/MeoAssistant/AsstUtils.hpp index 71ffabcca3..fa915320fb 100644 --- a/src/MeoAssistant/AsstUtils.hpp +++ b/src/MeoAssistant/AsstUtils.hpp @@ -1,229 +1,270 @@ -#pragma once - -#include -#include -#include - -#include - -namespace asst -{ - namespace utils - { - static std::string string_replace_all(const std::string& src, const std::string& old_value, const std::string& new_value) - { - std::string str = src; - for (std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) { - if ((pos = str.find(old_value, pos)) != std::string::npos) - str.replace(pos, old_value.length(), new_value); - else - break; - } - return str; - } - - static std::vector string_split(const std::string& str, const std::string& delimiter) - { - std::string::size_type pos1 = 0; - std::string::size_type pos2 = str.find(delimiter); - std::vector result; - - while (std::string::npos != pos2) { - result.emplace_back(str.substr(pos1, pos2 - pos1)); - - pos1 = pos2 + delimiter.size(); - pos2 = str.find(delimiter, pos1); - } - if (pos1 != str.length()) - result.emplace_back(str.substr(pos1)); - - return result; - } - - static std::string get_format_time() - { - SYSTEMTIME curtime; - GetLocalTime(&curtime); - char buff[64] = { 0 }; - sprintf_s(buff, "%04d-%02d-%02d %02d:%02d:%02d.%03d", - curtime.wYear, curtime.wMonth, curtime.wDay, - curtime.wHour, curtime.wMinute, curtime.wSecond, curtime.wMilliseconds); - return buff; - } - - static std::string gbk_2_utf8(const std::string& gbk_str) - { - const char* src_str = gbk_str.c_str(); - int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0); - wchar_t* wstr = new wchar_t[len + 1]; - memset(wstr, 0, len + 1); - MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len); - len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); - char* str = new char[len + 1]; - memset(str, 0, len + 1); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL); - std::string strTemp = str; - if (wstr) - delete[] wstr; - if (str) - delete[] str; - return strTemp; - } - - static std::string utf8_to_gbk(const std::string& utf8_str) - { - const char* src_str = utf8_str.c_str(); - int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0); - wchar_t* wszGBK = new wchar_t[len + 1]; - memset(wszGBK, 0, len * 2 + 2); - MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len); - len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL); - char* szGBK = new char[len + 1]; - memset(szGBK, 0, len + 1); - WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL); - std::string strTemp(szGBK); - if (wszGBK) - delete[] wszGBK; - if (szGBK) - delete[] szGBK; - return strTemp; - } - - template - constexpr inline RetTy make_rect(const ArgType& rect) - { - return RetTy{ rect.x, rect.y, rect.width, rect.height }; - } - - static std::string load_file_without_bom(const std::string& filename) - { - std::ifstream ifs(filename, std::ios::in); - if (!ifs.is_open()) { - return std::string(); - } - std::stringstream iss; - iss << ifs.rdbuf(); - ifs.close(); - std::string str = iss.str(); - - using uchar = unsigned char; - constexpr static uchar Bom_0 = 0xEF; - constexpr static uchar Bom_1 = 0xBB; - constexpr static uchar Bom_2 = 0xBF; - - if (str.size() >= 3 && static_cast(str.at(0)) == Bom_0 && static_cast(str.at(1)) == Bom_1 && static_cast(str.at(2)) == Bom_2) { - str.assign(str.begin() + 3, str.end()); - return str; - } - return str; - } - - static int hamming(std::string hash1, std::string hash2) - { - constexpr static int HammingFlags = 64; - - hash1.insert(hash1.begin(), HammingFlags - hash1.size(), '0'); - hash2.insert(hash2.begin(), HammingFlags - hash2.size(), '0'); - int dist = 0; - for (int i = 0; i < HammingFlags; i = i + 16) { - unsigned long long x = strtoull(hash1.substr(i, 16).c_str(), NULL, 16) ^ strtoull(hash2.substr(i, 16).c_str(), NULL, 16); - while (x) { - dist++; - x = x & (x - 1); - } - } - return dist; - } - - static std::string callcmd(const std::string& cmdline) - { - SECURITY_ATTRIBUTES pipe_sec_attr = { 0 }; - pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - pipe_sec_attr.lpSecurityDescriptor = nullptr; - pipe_sec_attr.bInheritHandle = TRUE; - HANDLE pipe_read = nullptr; - HANDLE pipe_child_write = nullptr; - CreatePipe(&pipe_read, &pipe_child_write, &pipe_sec_attr, 4096); - - STARTUPINFOA si = { 0 }; - si.cb = sizeof(STARTUPINFO); - si.dwFlags = STARTF_USESTDHANDLES; - si.wShowWindow = SW_HIDE; - si.hStdOutput = pipe_child_write; - si.hStdError = pipe_child_write; - - PROCESS_INFORMATION pi = { 0 }; - - BOOL p_ret = CreateProcessA(NULL, const_cast(cmdline.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi); - std::string pipe_str; - if (p_ret) { - DWORD read_num = 0; - DWORD std_num = 0; - do { - while (::PeekNamedPipe(pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) { - char* pipe_buffer = new char[read_num]; - BOOL read_ret = ::ReadFile(pipe_read, pipe_buffer, read_num, &std_num, NULL); - if (read_ret) { - pipe_str.append(pipe_buffer, pipe_buffer + std_num); - } - if (pipe_buffer != nullptr) { - delete[] pipe_buffer; - pipe_buffer = nullptr; - } - } - } while (::WaitForSingleObject(pi.hProcess, 0) == WAIT_TIMEOUT); - - DWORD exit_ret = -1; - ::GetExitCodeProcess(pi.hProcess, &exit_ret); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - - ::CloseHandle(pipe_read); - ::CloseHandle(pipe_child_write); - - return pipe_str; - } - - //template::value>::type> - // std::string VectorToString(const std::vector& vector, bool to_gbk = false) { - // if (vector.empty()) { - // return std::string(); - // } - // static const std::string inter = ", "; - // std::string str; - // for (const T& ele : vector) { - // if (to_gbk) { - // str += utils::utf8_to_gbk(ele) + inter; - // } - // else { - // str += ele + inter; - // } - // } - - // if (!str.empty()) { - // str.erase(str.size() - inter.size(), inter.size()); - // } - // return str; - //} - - //template::value>::type> - // std::string VectorToString(const std::vector& vector) { - // if (vector.empty()) { - // return std::string(); - // } - // static const std::string inter = ", "; - // std::string str; - // for (const T& ele : vector) { - // str += std::to_string(ele) + inter; - // } - - // if (!str.empty()) { - // str.erase(str.size() - inter.size(), inter.size()); - // } - // return str; - //} - } -} +#pragma once + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +namespace asst +{ + namespace utils + { + static std::string string_replace_all(const std::string& src, const std::string& old_value, const std::string& new_value) + { + std::string str = src; + for (std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) { + if ((pos = str.find(old_value, pos)) != std::string::npos) + str.replace(pos, old_value.length(), new_value); + else + break; + } + return str; + } + + static std::vector string_split(const std::string& str, const std::string& delimiter) + { + std::string::size_type pos1 = 0; + std::string::size_type pos2 = str.find(delimiter); + std::vector result; + + while (std::string::npos != pos2) { + result.emplace_back(str.substr(pos1, pos2 - pos1)); + + pos1 = pos2 + delimiter.size(); + pos2 = str.find(delimiter, pos1); + } + if (pos1 != str.length()) + result.emplace_back(str.substr(pos1)); + + return result; + } + + static std::string get_format_time() + { + char buff[128] = { 0 }; +#ifdef _WIN32 + SYSTEMTIME curtime; + GetLocalTime(&curtime); +#ifdef _MSC_VER + sprintf_s(buff, sizeof(buff), +#else // ! _MSC_VER + sprintf(buff, +#endif // END _MSC_VER + "%04d-%02d-%02d %02d:%02d:%02d.%03d", + curtime.wYear, curtime.wMonth, curtime.wDay, + curtime.wHour, curtime.wMinute, curtime.wSecond, curtime.wMilliseconds); + +#else // ! _WIN32 + struct timeval tv = { 0 }; + gettimeofday(&tv, NULL); + time_t nowtime = tv.tv_sec; + struct tm* tm_info = localtime(&nowtime); + strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", tm_info); + sprintf(buff, "%s.%03d", buff, tv.tv_usec / 1000); +#endif // END _WIN32 + return buff; + } + + static std::string gbk_2_utf8(const std::string& gbk_str) + { +#ifdef _WIN32 + const char* src_str = gbk_str.c_str(); + int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0); + wchar_t* wstr = new wchar_t[len + 1]; + memset(wstr, 0, len + 1); + MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len); + len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + char* str = new char[len + 1]; + memset(str, 0, len + 1); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL); + std::string strTemp = str; + if (wstr) + delete[] wstr; + if (str) + delete[] str; + return strTemp; +#else // Don't fucking use gbk in linux! + return gbk_str; +#endif + } + + static std::string utf8_to_gbk(const std::string& utf8_str) + { +#ifdef _WIN32 + const char* src_str = utf8_str.c_str(); + int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0); + wchar_t* wszGBK = new wchar_t[len + 1]; + memset(wszGBK, 0, len * 2 + 2); + MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len); + len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL); + char* szGBK = new char[len + 1]; + memset(szGBK, 0, len + 1); + WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL); + std::string strTemp(szGBK); + if (wszGBK) + delete[] wszGBK; + if (szGBK) + delete[] szGBK; + return strTemp; +#else // Don't fucking use gbk in linux! + return utf8_str; +#endif + } + + template + constexpr inline RetTy make_rect(const ArgType& rect) + { + return RetTy{ rect.x, rect.y, rect.width, rect.height }; + } + + static std::string load_file_without_bom(const std::string& filename) + { + std::ifstream ifs(filename, std::ios::in); + if (!ifs.is_open()) { + return std::string(); + } + std::stringstream iss; + iss << ifs.rdbuf(); + ifs.close(); + std::string str = iss.str(); + + using uchar = unsigned char; + constexpr static uchar Bom_0 = 0xEF; + constexpr static uchar Bom_1 = 0xBB; + constexpr static uchar Bom_2 = 0xBF; + + if (str.size() >= 3 && static_cast(str.at(0)) == Bom_0 && static_cast(str.at(1)) == Bom_1 && static_cast(str.at(2)) == Bom_2) { + str.assign(str.begin() + 3, str.end()); + return str; + } + return str; + } + + static int hamming(std::string hash1, std::string hash2) + { + constexpr static int HammingFlags = 64; + + hash1.insert(hash1.begin(), HammingFlags - hash1.size(), '0'); + hash2.insert(hash2.begin(), HammingFlags - hash2.size(), '0'); + int dist = 0; + for (int i = 0; i < HammingFlags; i = i + 16) { + unsigned long long x = strtoull(hash1.substr(i, 16).c_str(), NULL, 16) ^ strtoull(hash2.substr(i, 16).c_str(), NULL, 16); + while (x) { + dist++; + x = x & (x - 1); + } + } + return dist; + } + + static std::string callcmd(const std::string& cmdline) + { + std::string pipe_str; +#ifdef _WIN32 + SECURITY_ATTRIBUTES pipe_sec_attr = { 0 }; + pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + pipe_sec_attr.lpSecurityDescriptor = nullptr; + pipe_sec_attr.bInheritHandle = TRUE; + HANDLE pipe_read = nullptr; + HANDLE pipe_child_write = nullptr; + CreatePipe(&pipe_read, &pipe_child_write, &pipe_sec_attr, 4096); + + STARTUPINFOA si = { 0 }; + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.wShowWindow = SW_HIDE; + si.hStdOutput = pipe_child_write; + si.hStdError = pipe_child_write; + + PROCESS_INFORMATION pi = { 0 }; + + BOOL p_ret = CreateProcessA(NULL, const_cast(cmdline.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi); + if (p_ret) { + DWORD read_num = 0; + DWORD std_num = 0; + do { + while (::PeekNamedPipe(pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) { + char* pipe_buffer = new char[read_num]; + BOOL read_ret = ::ReadFile(pipe_read, pipe_buffer, read_num, &std_num, NULL); + if (read_ret) { + pipe_str.append(pipe_buffer, pipe_buffer + std_num); + } + if (pipe_buffer != nullptr) { + delete[] pipe_buffer; + pipe_buffer = nullptr; + } + } + } while (::WaitForSingleObject(pi.hProcess, 0) == WAIT_TIMEOUT); + + DWORD exit_ret = -1; + ::GetExitCodeProcess(pi.hProcess, &exit_ret); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + + ::CloseHandle(pipe_read); + ::CloseHandle(pipe_child_write); + + return pipe_str; +#else + char buff[4096] = { 0 }; + std::unique_ptr pipe(popen(cmdline.c_str(), "r"), pclose); + if (!pipe) { + return std::string(); + } + while (fgets(buff, sizeof(buff), pipe.get()) != nullptr) { + pipe_str += buff; + } +#endif + return pipe_str; + } + + //template::value>::type> + // std::string VectorToString(const std::vector& vector, bool to_gbk = false) { + // if (vector.empty()) { + // return std::string(); + // } + // static const std::string inter = ", "; + // std::string str; + // for (const T& ele : vector) { + // if (to_gbk) { + // str += utils::utf8_to_gbk(ele) + inter; + // } + // else { + // str += ele + inter; + // } + // } + + // if (!str.empty()) { + // str.erase(str.size() - inter.size(), inter.size()); + // } + // return str; + //} + + //template::value>::type> + // std::string VectorToString(const std::vector& vector) { + // if (vector.empty()) { + // return std::string(); + // } + // static const std::string inter = ", "; + // std::string str; + // for (const T& ele : vector) { + // str += std::to_string(ele) + inter; + // } + + // if (!str.empty()) { + // str.erase(str.size() - inter.size(), inter.size()); + // } + // return str; + //} + } +} diff --git a/src/MeoAssistant/Controller.cpp b/src/MeoAssistant/Controller.cpp index 1e40807811..c0e8b3b8d9 100644 --- a/src/MeoAssistant/Controller.cpp +++ b/src/MeoAssistant/Controller.cpp @@ -1,648 +1,683 @@ -#include "Controller.h" - -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include "AsstDef.h" -#include "Logger.hpp" -#include "Resource.h" -#include "UserConfiger.h" - -asst::Controller::Controller() - : m_rand_engine(std::chrono::system_clock::now().time_since_epoch().count()) -{ - LogTraceFunction; - - // 安全属性描述符 - m_pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - m_pipe_sec_attr.lpSecurityDescriptor = nullptr; - m_pipe_sec_attr.bInheritHandle = TRUE; - - // 创建管道,本进程读-子进程写 - BOOL pipe_read_ret = ::CreatePipe(&m_pipe_read, &m_pipe_child_write, &m_pipe_sec_attr, PipeBuffSize); - // 创建管道,本进程写-子进程读 - BOOL pipe_write_ret = ::CreatePipe(&m_pipe_write, &m_pipe_child_read, &m_pipe_sec_attr, PipeBuffSize); - - if (!pipe_read_ret || !pipe_write_ret) { - throw "controller pipe created failed"; - } - - m_child_startup_info.cb = sizeof(STARTUPINFO); - m_child_startup_info.dwFlags = STARTF_USESTDHANDLES; - m_child_startup_info.wShowWindow = SW_HIDE; - // 重定向子进程的读写 - m_child_startup_info.hStdInput = m_pipe_child_read; - m_child_startup_info.hStdOutput = m_pipe_child_write; - m_child_startup_info.hStdError = m_pipe_child_write; - - auto bind_pipe_working_proc = std::bind(&Controller::pipe_working_proc, this); - m_cmd_thread = std::thread(bind_pipe_working_proc); -} - -bool asst::Controller::connect_adb(const std::string& address) -{ - LogTraceScope("connect_adb " + address); - - std::string connect_cmd = utils::string_replace_all( - utils::string_replace_all(m_emulator_info.adb.connect, "[Adb]", m_emulator_info.adb.path), - "[Address]", address); - auto&& [connect_ret, connect_result] = call_command(connect_cmd); - // 端口即使错误,命令仍然会返回0,TODO 对connect_result进行判断 - if (!connect_ret) { - return false; - } - - std::string display_cmd = utils::string_replace_all( - utils::string_replace_all(m_emulator_info.adb.display, "[Adb]", m_emulator_info.adb.path), - "[Address]", address); - auto&& [display_ret, display_result] = call_command(display_cmd); - if (!display_ret) { - return false; - } - - std::string display_pipe_str( - std::make_move_iterator(display_result.begin()), - std::make_move_iterator(display_result.end())); - int size_value1 = 0; - int size_value2 = 0; - sscanf_s(display_pipe_str.c_str(), m_emulator_info.adb.display_format.c_str(), &size_value1, &size_value2); - // 为了防止抓取句柄的时候手机是竖屏的(还没进游戏),这里取大的值为宽,小的为高 - // 总不能有人竖屏玩明日方舟吧(? - m_emulator_info.adb.display_width = (std::max)(size_value1, size_value2); - m_emulator_info.adb.display_height = (std::min)(size_value1, size_value2); - - constexpr double DefaultRatio = - static_cast(WindowWidthDefault) / static_cast(WindowHeightDefault); - double cur_ratio = static_cast(m_emulator_info.adb.display_width) / static_cast(m_emulator_info.adb.display_height); - - if (cur_ratio >= DefaultRatio // 说明是宽屏或默认16:9,按照高度计算缩放 - || std::fabs(cur_ratio - DefaultRatio) < DoubleDiff) { - int scale_width = cur_ratio * WindowHeightDefault; - m_scale_size = std::make_pair(scale_width, WindowHeightDefault); - m_control_scale = static_cast(m_emulator_info.adb.display_height) / static_cast(WindowHeightDefault); - } - else { // 否则可能是偏正方形的屏幕,按宽度计算 - int scale_height = WindowWidthDefault / cur_ratio; - m_scale_size = std::make_pair(WindowWidthDefault, scale_height); - m_control_scale = static_cast(m_emulator_info.adb.display_width) / static_cast(WindowWidthDefault); - } - - m_emulator_info.adb.click = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.click, "[Adb]", m_emulator_info.adb.path), "[Address]", address); - m_emulator_info.adb.swipe = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.swipe, "[Adb]", m_emulator_info.adb.path), "[Address]", address); - m_emulator_info.adb.screencap = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.screencap, "[Adb]", m_emulator_info.adb.path), "[Address]", address); - m_emulator_info.adb.release = utils::string_replace_all(m_emulator_info.adb.release, "[Adb]", m_emulator_info.adb.path); - - return true; -} - -asst::Controller::~Controller() -{ - LogTraceFunction; - - m_thread_exit = true; - //m_thread_idle = true; - m_cmd_condvar.notify_all(); - m_completed_id = UINT_MAX; // make all WinMacor::wait to exit - - if (m_cmd_thread.joinable()) { - m_cmd_thread.join(); - } - - if (!m_emulator_info.adb.release.empty()) { - call_command(m_emulator_info.adb.release); - } - - ::CloseHandle(m_pipe_read); - ::CloseHandle(m_pipe_write); - ::CloseHandle(m_pipe_child_read); - ::CloseHandle(m_pipe_child_write); -} - -asst::Rect asst::Controller::shaped_correct(const Rect& rect) const -{ - if (rect.empty()) { - return rect; - } - // 明日方舟在异形屏上,有的地方是按比例缩放的,有的地方又是直接位移。没法整,这里简单粗暴一点截一个长条 - Rect dst = rect; - if (m_scale_size.first != WindowWidthDefault) { // 说明是宽屏 - if (rect.width <= WindowWidthDefault / 2) { - if (rect.x + rect.width <= WindowWidthDefault / 2) { // 整个矩形都在左半边 - dst.x = 0; - dst.width = m_scale_size.first / 2; - } - else if (rect.x >= WindowWidthDefault / 2) { // 整个矩形都在右半边 - dst.x = m_scale_size.first / 2; - dst.width = m_scale_size.first / 2; - } - else { // 整个矩形横跨了中线 - dst.x = 0; - dst.width = m_scale_size.first; - } - } - else { - dst.x = 0; - dst.width = m_scale_size.first; - } - } - else if (m_scale_size.second != WindowHeightDefault) { // 说明是偏方形屏 - if (rect.height <= WindowHeightDefault / 2) { - if (rect.y + rect.height <= WindowHeightDefault / 2) { // 整个矩形都在上半边 - dst.y = 0; - dst.height = m_scale_size.second / 2; - } - else if (rect.y >= WindowHeightDefault / 2) { // 整个矩形都在下半边 - dst.y = m_scale_size.second / 2; - dst.height = m_scale_size.second / 2; // 整个矩形横跨了中线 - } - else { - dst.y = 0; - dst.height = m_scale_size.second; - } - } - - else { - dst.y = 0; - dst.height = m_scale_size.second; - } - } - return dst; -} - -std::pair asst::Controller::get_scale_size() const noexcept -{ - return m_scale_size; -} - -void asst::Controller::pipe_working_proc() -{ - LogTraceFunction; - - while (!m_thread_exit) { - std::unique_lock cmd_queue_lock(m_cmd_queue_mutex); - - if (!m_cmd_queue.empty()) { // 队列中有任务就执行任务 - std::string cmd = m_cmd_queue.front(); - m_cmd_queue.pop(); - cmd_queue_lock.unlock(); - // todo 判断命令是否执行成功 - call_command(cmd); - ++m_completed_id; - } - //else if (!m_thread_idle) { // 队列中没有任务,又不是闲置的时候,就去截图 - // cmd_queue_lock.unlock(); - // auto start_time = std::chrono::steady_clock::now(); - // screencap(); - // cmd_queue_lock.lock(); - // if (!m_cmd_queue.empty()) { - // continue; - // } - // m_cmd_condvar.wait_until( - // cmd_queue_lock, - // start_time + std::chrono::milliseconds(1000)); // todo 时间写到配置文件里 - //} - else { - //m_cmd_condvar.wait(cmd_queue_lock, [&]() -> bool {return !m_thread_idle; }); - m_cmd_condvar.wait(cmd_queue_lock); - } - } -} - -void asst::Controller::set_dirname(std::string dirname) noexcept -{ - m_dirname = std::move(dirname); -} - -bool asst::Controller::try_capture(const EmulatorInfo& info, bool without_handle) -{ - LogTraceScope("try_capture | " + info.name); - - const HandleInfo& handle_info = info.handle; - - std::string adb_path; - if (!without_handle) { // 使用模拟器自带的adb - // 转成宽字符的 - wchar_t* class_wbuff = nullptr; - if (!handle_info.class_name.empty()) { - size_t class_len = (handle_info.class_name.size() + 1) * 2; - class_wbuff = new wchar_t[class_len]; - ::MultiByteToWideChar(CP_UTF8, 0, handle_info.class_name.c_str(), -1, class_wbuff, class_len); - } - wchar_t* window_wbuff = nullptr; - if (!handle_info.window_name.empty()) { - size_t window_len = (handle_info.window_name.size() + 1) * 2; - window_wbuff = new wchar_t[window_len]; - memset(window_wbuff, 0, window_len); - ::MultiByteToWideChar(CP_UTF8, 0, handle_info.window_name.c_str(), -1, window_wbuff, window_len); - } - // 查找窗口句柄 - HWND window_handle = nullptr; - window_handle = ::FindWindowExW(window_handle, nullptr, class_wbuff, window_wbuff); - - if (class_wbuff != nullptr) { - delete[] class_wbuff; - class_wbuff = nullptr; - } - if (window_wbuff != nullptr) { - delete[] window_wbuff; - window_wbuff = nullptr; - } - if (window_handle == nullptr) { - return false; - } - - std::string emulator_path; - if (info.path.empty()) { - // 获取模拟器窗口句柄对应的进程句柄 - DWORD process_id = 0; - ::GetWindowThreadProcessId(window_handle, &process_id); - HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id); - - // 获取模拟器程序所在路径 - LPSTR path_buff = new CHAR[MAX_PATH]; - memset(path_buff, 0, MAX_PATH); - DWORD path_size = MAX_PATH; - QueryFullProcessImageNameA(process_handle, 0, path_buff, &path_size); - emulator_path = std::string(path_buff); - if (path_buff != nullptr) { - delete[] path_buff; - path_buff = nullptr; - } - if (emulator_path.empty()) { - return false; - } - Resrc.cfg().set_emulator_path(info.name, emulator_path); - Resrc.user().set_emulator_path(info.name, emulator_path); - } - else { - emulator_path = info.path; - } - - // 到这一步说明句柄和权限没问题了,接下来就是adb的事情了 - m_emulator_info = info; - Log.trace("Handle:", window_handle, "Name:", m_emulator_info.name); - - adb_path = emulator_path.substr(0, emulator_path.find_last_of('\\') + 1); - adb_path = '"' + utils::string_replace_all(m_emulator_info.adb.path, "[EmulatorPath]", adb_path) + '"'; - adb_path = utils::string_replace_all(adb_path, "[ExecDir]", m_dirname); - } - else { // 使用辅助自带的标准adb - m_emulator_info = info; - adb_path = '"' + utils::string_replace_all(m_emulator_info.adb.path, "[ExecDir]", m_dirname) + '"'; - } - - m_emulator_info.adb.path = std::move(adb_path); - - // 优先使用addresses里指定的地址 - for (const std::string& address : info.adb.addresses) { - if (connect_adb(address)) { - return true; - } - } - - // 若指定地址都没连上,再尝试用devices查找地址 - std::string devices_cmd = utils::string_replace_all(m_emulator_info.adb.devices, "[Adb]", m_emulator_info.adb.path); - auto&& [devices_ret, devices_result] = call_command(devices_cmd); - if (!devices_ret) { - return false; - } - std::string devices_pipe_str( - std::make_move_iterator(devices_result.begin()), - std::make_move_iterator(devices_result.end())); - auto lines = utils::string_split(devices_pipe_str, "\r\n"); - - std::string address; - const std::regex address_regex(m_emulator_info.adb.address_regex); - for (const std::string& line : lines) { - std::smatch smatch; - if (std::regex_match(line, smatch, address_regex)) { - address = smatch[1]; - } - } - Log.trace("device address", address); - if (address.empty()) { - return false; - } - - return connect_adb(address); -} - -//void asst::Controller::set_idle(bool flag) -//{ -// LogTraceFunction; -// -// m_thread_idle = flag; -// if (!flag) { -// // 开始前,立即截一张图,保证第一张图片非空 -// //screencap(); -// -// m_cmd_condvar.notify_one(); -// } -//} - -std::pair> asst::Controller::call_command(const std::string& cmd) -{ - LogTraceFunction; - - static std::mutex pipe_mutex; - std::unique_lock pipe_lock(pipe_mutex); - - PROCESS_INFORMATION process_info = { 0 }; // 进程信息结构体 - ::CreateProcessA(NULL, const_cast(cmd.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &m_child_startup_info, &process_info); - - std::vector pipe_data; - do { - //DWORD write_num = 0; - //WriteFile(parent_write, cmd.c_str(), cmd.size(), &write_num, NULL); - - DWORD read_num = 0; - DWORD std_num = 0; - while (::PeekNamedPipe(m_pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) { - uchar* pipe_buffer = new uchar[read_num]; - BOOL read_ret = ::ReadFile(m_pipe_read, pipe_buffer, read_num, &std_num, NULL); - if (read_ret) { - pipe_data.insert(pipe_data.end(), pipe_buffer, pipe_buffer + std_num); - } - if (pipe_buffer != nullptr) { - delete[] pipe_buffer; - pipe_buffer = nullptr; - } - } - } while (::WaitForSingleObject(process_info.hProcess, 0) == WAIT_TIMEOUT); - - DWORD exit_ret = -1; - ::GetExitCodeProcess(process_info.hProcess, &exit_ret); - - ::CloseHandle(process_info.hProcess); - ::CloseHandle(process_info.hThread); - Log.trace("Call", cmd, "ret", exit_ret, ", output size:", pipe_data.size()); - if (!pipe_data.empty() && pipe_data.size() < 4096) { - Log.trace("output:", std::string(pipe_data.cbegin(), pipe_data.cend())); - } - - return make_pair(!exit_ret, std::move(pipe_data)); -} - -void asst::Controller::convert_lf(std::vector& data) -{ - LogTraceFunction; - - if (data.empty() || data.size() <= 1) { - return; - } - using Iter = std::vector::iterator; - auto pred = [](const Iter& cur) -> bool { - return *cur == '\r' && *(cur + 1) == '\n'; - }; - // find the first of "\r\n" - Iter first_iter; - for (Iter iter = data.begin(); iter != data.end() - 1; ++iter) { - if (pred(iter)) { - first_iter = iter; - break; - } - } - // move forward all non-crlf elements - Iter end_r1_iter = data.end() - 1; - Iter next_iter = first_iter; - while (++first_iter != end_r1_iter) { - if (!pred(first_iter)) { - *next_iter = std::move(*first_iter); - ++next_iter; - } - } - *next_iter = std::move(*end_r1_iter); - ++next_iter; - data.erase(next_iter, data.end()); -} - -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 Point(x, y); -} - -void asst::Controller::random_delay() const -{ - auto& opt = Resrc.cfg().get_options(); - if (opt.control_delay_upper != 0) { - LogTraceFunction; - static std::default_random_engine rand_engine( - std::chrono::steady_clock::now().time_since_epoch().count()); - static std::uniform_int_distribution rand_uni( - opt.control_delay_lower, - opt.control_delay_upper); - - unsigned rand_delay = rand_uni(rand_engine); - - Log.trace("random_delay |", rand_delay, "ms"); - std::this_thread::sleep_for(std::chrono::milliseconds(rand_delay)); - } -} - -int asst::Controller::push_cmd(const std::string& cmd) -{ - random_delay(); - - std::unique_lock lock(m_cmd_queue_mutex); - m_cmd_queue.emplace(cmd); - m_cmd_condvar.notify_one(); - return ++m_push_id; -} - -void asst::Controller::wait(unsigned id) const noexcept -{ - while (id > m_completed_id) { - std::this_thread::yield(); - } -} - -bool asst::Controller::screencap() -{ - LogTraceFunction; - - auto&& [ret, data] = call_command(m_emulator_info.adb.screencap); - if (ret && !data.empty()) { - if (m_image_convert_lf) { - convert_lf(data); - } - m_cache_image = cv::imdecode(data, cv::IMREAD_COLOR); - if (m_cache_image.empty()) { - Log.info("Data is not empty, but image is empty, try to convert lf"); - convert_lf(data); - m_cache_image = cv::imdecode(data, cv::IMREAD_COLOR); - if (m_cache_image.empty()) { - m_image_convert_lf = false; - Log.error("convert lf and retry decode falied!"); - return false; - } - m_image_convert_lf = true; - return true; - } - return true; - } - else { - Log.error("Data is empty!"); - return false; - } - - //cv::Mat temp_image = cv::imdecode(data, cv::IMREAD_COLOR); - ////std::unique_lock image_lock(m_image_mutex); - //m_cache_image = std::move(temp_image); -} - -int asst::Controller::click(const Point& p, bool block) -{ - int x = p.x * m_control_scale; - int y = p.y * m_control_scale; - //log.trace("Click, raw:", p.x, p.y, "corr:", x, y); - - return click_without_scale(Point(x, y), block); -} - -int asst::Controller::click(const Rect& rect, bool block) -{ - return click(rand_point_in_rect(rect), block); -} - -int asst::Controller::click_without_scale(const Point& p, bool block) -{ - if (p.x < 0 || p.x >= m_emulator_info.adb.display_width || p.y < 0 || p.y >= m_emulator_info.adb.display_height) { - Log.error("click point out of range"); - } - std::string cur_cmd = utils::string_replace_all(m_emulator_info.adb.click, "[x]", std::to_string(p.x)); - cur_cmd = utils::string_replace_all(cur_cmd, "[y]", std::to_string(p.y)); - int id = push_cmd(cur_cmd); - if (block) { - wait(id); - } - return id; -} - -int asst::Controller::click_without_scale(const Rect& rect, bool block) -{ - return click_without_scale(rand_point_in_rect(rect), block); -} - -int asst::Controller::swipe(const Point& p1, const Point& p2, int duration, bool block, int extra_delay, bool extra_swipe) -{ - int x1 = p1.x * m_control_scale; - int y1 = p1.y * m_control_scale; - int x2 = p2.x * m_control_scale; - int y2 = 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, block, extra_delay, extra_swipe); -} - -int asst::Controller::swipe(const Rect& r1, const Rect& r2, int duration, bool block, int extra_delay, bool extra_swipe) -{ - return swipe(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, block, extra_delay, extra_swipe); -} - -int asst::Controller::swipe_without_scale(const Point& p1, const Point& p2, int duration, bool block, int extra_delay, bool extra_swipe) -{ - if (p1.x < 0 || p1.x >= m_emulator_info.adb.display_width || p1.y < 0 || p1.y >= m_emulator_info.adb.display_height || p2.x < 0 || p2.x >= m_emulator_info.adb.display_width || p2.y < 0 || p2.y >= m_emulator_info.adb.display_height) { - Log.error("swipe point out of range"); - } - std::string cur_cmd = utils::string_replace_all(m_emulator_info.adb.swipe, "[x1]", std::to_string(p1.x)); - cur_cmd = utils::string_replace_all(cur_cmd, "[y1]", std::to_string(p1.y)); - cur_cmd = utils::string_replace_all(cur_cmd, "[x2]", std::to_string(p2.x)); - cur_cmd = utils::string_replace_all(cur_cmd, "[y2]", std::to_string(p2.y)); - if (duration <= 0) { - cur_cmd = utils::string_replace_all(cur_cmd, "[duration]", ""); - } - else { - cur_cmd = utils::string_replace_all(cur_cmd, "[duration]", std::to_string(duration)); - } - - int id = 0; - - int extra_swipe_dist = Resrc.cfg().get_options().adb_extra_swipe_dist /* * m_control_scale*/; - int extra_swipe_duration = Resrc.cfg().get_options().adb_extra_swipe_duration; - - // 额外的滑动:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 - if (extra_swipe && extra_swipe_duration >= 0) { - std::string extra_cmd = utils::string_replace_all(m_emulator_info.adb.swipe, "[x1]", std::to_string(p2.x)); - extra_cmd = utils::string_replace_all(extra_cmd, "[y1]", std::to_string(p2.y)); - int end_x = 0, end_y = 0; - if (p2.x != p1.x) { - double k = (double)(p2.y - p1.y) / (p2.x - p1.x); - double temp = extra_swipe_dist / std::sqrt(1 + k * k); - end_x = p2.x + (p2.x > p1.x ? -1 : 1) * temp; - end_y = p2.y + (p2.y > p1.y ? -1 : 1) * std::fabs(k) * temp; - } - else { - end_x = p2.x; - end_y = p2.y + (p2.y > p1.y ? -1 : 1) * extra_swipe_dist; - } - extra_cmd = utils::string_replace_all(extra_cmd, "[x2]", std::to_string(end_x)); - extra_cmd = utils::string_replace_all(extra_cmd, "[y2]", std::to_string(end_y)); - extra_cmd = utils::string_replace_all(extra_cmd, "[duration]", std::to_string(extra_swipe_duration)); - - push_cmd(cur_cmd); - id = push_cmd(extra_cmd); - } - else { - id = push_cmd(cur_cmd); - } - - if (block) { - wait(id); - std::this_thread::sleep_for(std::chrono::milliseconds(extra_delay)); - } - return id; -} - -int asst::Controller::swipe_without_scale(const Rect& r1, const Rect& r2, int duration, bool block, int extra_delay, bool extra_swipe) -{ - return swipe_without_scale(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, block, extra_delay, extra_swipe); -} - -cv::Mat asst::Controller::get_image(bool raw) -{ - // 有些模拟器adb偶尔会莫名其妙截图失败,多试几次 - for (int i = 0; i != 20; ++i) { - if (screencap()) { - break; - } - } - //std::shared_lock image_lock(m_image_mutex); - if (raw) { - return m_cache_image; - } - else { - if (m_cache_image.empty()) { - return m_cache_image; - } - const static cv::Size size(m_scale_size.first, m_scale_size.second); - cv::Mat resize_mat; - cv::resize(m_cache_image, resize_mat, size, cv::INPAINT_NS); - return resize_mat; - } -} +#include "Controller.h" + +#ifdef _WIN32 +#include +#endif +#include + +#include +#include +#include +#include +#include + +#include + +#include "AsstDef.h" +#include "Logger.hpp" +#include "Resource.h" +#include "UserConfiger.h" + +asst::Controller::Controller() + : m_rand_engine(std::chrono::system_clock::now().time_since_epoch().count()) +{ + LogTraceFunction; + +#ifdef _WIN32 + // 安全属性描述符 + m_pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + m_pipe_sec_attr.lpSecurityDescriptor = nullptr; + m_pipe_sec_attr.bInheritHandle = TRUE; + + // 创建管道,本进程读-子进程写 + BOOL pipe_read_ret = ::CreatePipe(&m_pipe_read, &m_pipe_child_write, &m_pipe_sec_attr, PipeBuffSize); + // 创建管道,本进程写-子进程读 + BOOL pipe_write_ret = ::CreatePipe(&m_pipe_write, &m_pipe_child_read, &m_pipe_sec_attr, PipeBuffSize); + + if (!pipe_read_ret || !pipe_write_ret) { + throw "controller pipe created failed"; + } + + m_child_startup_info.cb = sizeof(STARTUPINFO); + m_child_startup_info.dwFlags = STARTF_USESTDHANDLES; + m_child_startup_info.wShowWindow = SW_HIDE; + // 重定向子进程的读写 + m_child_startup_info.hStdInput = m_pipe_child_read; + m_child_startup_info.hStdOutput = m_pipe_child_write; + m_child_startup_info.hStdError = m_pipe_child_write; +#endif + + auto bind_pipe_working_proc = std::bind(&Controller::pipe_working_proc, this); + m_cmd_thread = std::thread(bind_pipe_working_proc); +} + +bool asst::Controller::connect_adb(const std::string& address) +{ + LogTraceScope("connect_adb " + address); + + std::string connect_cmd = utils::string_replace_all( + utils::string_replace_all(m_emulator_info.adb.connect, "[Adb]", m_emulator_info.adb.path), + "[Address]", address); + auto&& [connect_ret, connect_result] = call_command(connect_cmd); + // 端口即使错误,命令仍然会返回0,TODO 对connect_result进行判断 + if (!connect_ret) { + return false; + } + + std::string display_cmd = utils::string_replace_all( + utils::string_replace_all(m_emulator_info.adb.display, "[Adb]", m_emulator_info.adb.path), + "[Address]", address); + auto&& [display_ret, display_result] = call_command(display_cmd); + if (!display_ret) { + return false; + } + + std::string display_pipe_str( + std::make_move_iterator(display_result.begin()), + std::make_move_iterator(display_result.end())); + int size_value1 = 0; + int size_value2 = 0; +#ifdef _MSC_VER + sscanf_s(display_pipe_str.c_str(), m_emulator_info.adb.display_format.c_str(), &size_value1, &size_value2); +#else + sscanf(display_pipe_str.c_str(), m_emulator_info.adb.display_format.c_str(), &size_value1, &size_value2); +#endif + // 为了防止抓取句柄的时候手机是竖屏的(还没进游戏),这里取大的值为宽,小的为高 + // 总不能有人竖屏玩明日方舟吧(? + m_emulator_info.adb.display_width = (std::max)(size_value1, size_value2); + m_emulator_info.adb.display_height = (std::min)(size_value1, size_value2); + + constexpr double DefaultRatio = + static_cast(WindowWidthDefault) / static_cast(WindowHeightDefault); + double cur_ratio = static_cast(m_emulator_info.adb.display_width) / static_cast(m_emulator_info.adb.display_height); + + if (cur_ratio >= DefaultRatio // 说明是宽屏或默认16:9,按照高度计算缩放 + || std::fabs(cur_ratio - DefaultRatio) < DoubleDiff) { + int scale_width = cur_ratio * WindowHeightDefault; + m_scale_size = std::make_pair(scale_width, WindowHeightDefault); + m_control_scale = static_cast(m_emulator_info.adb.display_height) / static_cast(WindowHeightDefault); + } + else { // 否则可能是偏正方形的屏幕,按宽度计算 + int scale_height = WindowWidthDefault / cur_ratio; + m_scale_size = std::make_pair(WindowWidthDefault, scale_height); + m_control_scale = static_cast(m_emulator_info.adb.display_width) / static_cast(WindowWidthDefault); + } + + m_emulator_info.adb.click = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.click, "[Adb]", m_emulator_info.adb.path), "[Address]", address); + m_emulator_info.adb.swipe = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.swipe, "[Adb]", m_emulator_info.adb.path), "[Address]", address); + m_emulator_info.adb.screencap = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.screencap, "[Adb]", m_emulator_info.adb.path), "[Address]", address); + m_emulator_info.adb.release = utils::string_replace_all(m_emulator_info.adb.release, "[Adb]", m_emulator_info.adb.path); + + return true; +} + +asst::Controller::~Controller() +{ + LogTraceFunction; + + m_thread_exit = true; + //m_thread_idle = true; + m_cmd_condvar.notify_all(); + m_completed_id = UINT_MAX; // make all WinMacor::wait to exit + + if (m_cmd_thread.joinable()) { + m_cmd_thread.join(); + } + + if (!m_emulator_info.adb.release.empty()) { + call_command(m_emulator_info.adb.release); + } + +#ifdef _WIN32 + ::CloseHandle(m_pipe_read); + ::CloseHandle(m_pipe_write); + ::CloseHandle(m_pipe_child_read); + ::CloseHandle(m_pipe_child_write); +#endif +} + +asst::Rect asst::Controller::shaped_correct(const Rect& rect) const +{ + if (rect.empty()) { + return rect; + } + // 明日方舟在异形屏上,有的地方是按比例缩放的,有的地方又是直接位移。没法整,这里简单粗暴一点截一个长条 + Rect dst = rect; + if (m_scale_size.first != WindowWidthDefault) { // 说明是宽屏 + if (rect.width <= WindowWidthDefault / 2) { + if (rect.x + rect.width <= WindowWidthDefault / 2) { // 整个矩形都在左半边 + dst.x = 0; + dst.width = m_scale_size.first / 2; + } + else if (rect.x >= WindowWidthDefault / 2) { // 整个矩形都在右半边 + dst.x = m_scale_size.first / 2; + dst.width = m_scale_size.first / 2; + } + else { // 整个矩形横跨了中线 + dst.x = 0; + dst.width = m_scale_size.first; + } + } + else { + dst.x = 0; + dst.width = m_scale_size.first; + } + } + else if (m_scale_size.second != WindowHeightDefault) { // 说明是偏方形屏 + if (rect.height <= WindowHeightDefault / 2) { + if (rect.y + rect.height <= WindowHeightDefault / 2) { // 整个矩形都在上半边 + dst.y = 0; + dst.height = m_scale_size.second / 2; + } + else if (rect.y >= WindowHeightDefault / 2) { // 整个矩形都在下半边 + dst.y = m_scale_size.second / 2; + dst.height = m_scale_size.second / 2; // 整个矩形横跨了中线 + } + else { + dst.y = 0; + dst.height = m_scale_size.second; + } + } + + else { + dst.y = 0; + dst.height = m_scale_size.second; + } + } + return dst; +} + +std::pair asst::Controller::get_scale_size() const noexcept +{ + return m_scale_size; +} + +void asst::Controller::pipe_working_proc() +{ + LogTraceFunction; + + while (!m_thread_exit) { + std::unique_lock cmd_queue_lock(m_cmd_queue_mutex); + + if (!m_cmd_queue.empty()) { // 队列中有任务就执行任务 + std::string cmd = m_cmd_queue.front(); + m_cmd_queue.pop(); + cmd_queue_lock.unlock(); + // todo 判断命令是否执行成功 + call_command(cmd); + ++m_completed_id; + } + //else if (!m_thread_idle) { // 队列中没有任务,又不是闲置的时候,就去截图 + // cmd_queue_lock.unlock(); + // auto start_time = std::chrono::steady_clock::now(); + // screencap(); + // cmd_queue_lock.lock(); + // if (!m_cmd_queue.empty()) { + // continue; + // } + // m_cmd_condvar.wait_until( + // cmd_queue_lock, + // start_time + std::chrono::milliseconds(1000)); // todo 时间写到配置文件里 + //} + else { + //m_cmd_condvar.wait(cmd_queue_lock, [&]() -> bool {return !m_thread_idle; }); + m_cmd_condvar.wait(cmd_queue_lock); + } + } +} + +void asst::Controller::set_dirname(std::string dirname) noexcept +{ + m_dirname = std::move(dirname); +} + +bool asst::Controller::try_capture(const EmulatorInfo& info, bool without_handle) +{ + LogTraceScope("try_capture | " + info.name); + +#ifndef _WIN32 + if (without_handle) { + Log.error("Capture handle is only supported on Windows!"); + return false; + } +#endif + + const HandleInfo& handle_info = info.handle; + + std::string adb_path; + if (!without_handle) { // 使用模拟器自带的adb +#ifdef _WIN32 // Only support Windows + // 转成宽字符的 + wchar_t* class_wbuff = nullptr; + if (!handle_info.class_name.empty()) { + size_t class_len = (handle_info.class_name.size() + 1) * 2; + class_wbuff = new wchar_t[class_len]; + ::MultiByteToWideChar(CP_UTF8, 0, handle_info.class_name.c_str(), -1, class_wbuff, class_len); + } + wchar_t* window_wbuff = nullptr; + if (!handle_info.window_name.empty()) { + size_t window_len = (handle_info.window_name.size() + 1) * 2; + window_wbuff = new wchar_t[window_len]; + memset(window_wbuff, 0, window_len); + ::MultiByteToWideChar(CP_UTF8, 0, handle_info.window_name.c_str(), -1, window_wbuff, window_len); + } + // 查找窗口句柄 + HWND window_handle = nullptr; + window_handle = ::FindWindowExW(window_handle, nullptr, class_wbuff, window_wbuff); + + if (class_wbuff != nullptr) { + delete[] class_wbuff; + class_wbuff = nullptr; + } + if (window_wbuff != nullptr) { + delete[] window_wbuff; + window_wbuff = nullptr; + } + if (window_handle == nullptr) { + return false; + } + + std::string emulator_path; + if (info.path.empty()) { + // 获取模拟器窗口句柄对应的进程句柄 + DWORD process_id = 0; + ::GetWindowThreadProcessId(window_handle, &process_id); + HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id); + + // 获取模拟器程序所在路径 + LPSTR path_buff = new CHAR[MAX_PATH]; + memset(path_buff, 0, MAX_PATH); + DWORD path_size = MAX_PATH; + QueryFullProcessImageNameA(process_handle, 0, path_buff, &path_size); + emulator_path = std::string(path_buff); + if (path_buff != nullptr) { + delete[] path_buff; + path_buff = nullptr; + } + if (emulator_path.empty()) { + return false; + } + Resrc.cfg().set_emulator_path(info.name, emulator_path); + Resrc.user().set_emulator_path(info.name, emulator_path); + } + else { + emulator_path = info.path; + } + + // 到这一步说明句柄和权限没问题了,接下来就是adb的事情了 + m_emulator_info = info; + Log.trace("Handle:", window_handle, "Name:", m_emulator_info.name); + + adb_path = emulator_path.substr(0, emulator_path.find_last_of('\\') + 1); + adb_path = '"' + utils::string_replace_all(m_emulator_info.adb.path, "[EmulatorPath]", adb_path) + '"'; + adb_path = utils::string_replace_all(adb_path, "[ExecDir]", m_dirname); +#endif +} + else { // 使用辅助自带的标准adb + m_emulator_info = info; + adb_path = '"' + utils::string_replace_all(m_emulator_info.adb.path, "[ExecDir]", m_dirname) + '"'; + } + + m_emulator_info.adb.path = std::move(adb_path); + + // 优先使用addresses里指定的地址 + for (const std::string& address : info.adb.addresses) { + if (connect_adb(address)) { + return true; + } + } + + // 若指定地址都没连上,再尝试用devices查找地址 + std::string devices_cmd = utils::string_replace_all(m_emulator_info.adb.devices, "[Adb]", m_emulator_info.adb.path); + auto&& [devices_ret, devices_result] = call_command(devices_cmd); + if (!devices_ret) { + return false; + } + std::string devices_pipe_str( + std::make_move_iterator(devices_result.begin()), + std::make_move_iterator(devices_result.end())); + auto lines = utils::string_split(devices_pipe_str, "\r\n"); + + std::string address; + const std::regex address_regex(m_emulator_info.adb.address_regex); + for (const std::string& line : lines) { + std::smatch smatch; + if (std::regex_match(line, smatch, address_regex)) { + address = smatch[1]; + } + } + Log.trace("device address", address); + if (address.empty()) { + return false; + } + + return connect_adb(address); +} + +//void asst::Controller::set_idle(bool flag) +//{ +// LogTraceFunction; +// +// m_thread_idle = flag; +// if (!flag) { +// // 开始前,立即截一张图,保证第一张图片非空 +// //screencap(); +// +// m_cmd_condvar.notify_one(); +// } +//} + +std::pair> asst::Controller::call_command(const std::string& cmd) +{ + LogTraceFunction; + + static std::mutex pipe_mutex; + std::unique_lock pipe_lock(pipe_mutex); + + std::vector pipe_data; + +#ifdef _WIN32 + PROCESS_INFORMATION process_info = { 0 }; // 进程信息结构体 + ::CreateProcessA(NULL, const_cast(cmd.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &m_child_startup_info, &process_info); + + do { + //DWORD write_num = 0; + //WriteFile(parent_write, cmd.c_str(), cmd.size(), &write_num, NULL); + + DWORD read_num = 0; + DWORD std_num = 0; + while (::PeekNamedPipe(m_pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) { + uchar* pipe_buffer = new uchar[read_num]; + BOOL read_ret = ::ReadFile(m_pipe_read, pipe_buffer, read_num, &std_num, NULL); + if (read_ret) { + pipe_data.insert(pipe_data.end(), pipe_buffer, pipe_buffer + std_num); + } + if (pipe_buffer != nullptr) { + delete[] pipe_buffer; + pipe_buffer = nullptr; + } + } + } while (::WaitForSingleObject(process_info.hProcess, 0) == WAIT_TIMEOUT); + + DWORD exit_ret = -1; + ::GetExitCodeProcess(process_info.hProcess, &exit_ret); + + ::CloseHandle(process_info.hProcess); + ::CloseHandle(process_info.hThread); + +#else + int exit_ret = 1; + std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (!pipe) { + return make_pair(false, std::vector()); + } + char pipe_buffer[4096] = { 0 }; + while (fgets(pipe_buffer, sizeof(pipe_buffer), pipe.get()) != nullptr) { + pipe_data.insert(pipe_data.end(), pipe_buffer, pipe_buffer + sizeof(pipe_buffer)); + } + +#endif + + Log.trace("Call", cmd, "ret", exit_ret, ", output size:", pipe_data.size()); + if (!pipe_data.empty() && pipe_data.size() < 4096) { + Log.trace("output:", std::string(pipe_data.cbegin(), pipe_data.cend())); + } + + return make_pair(!exit_ret, std::move(pipe_data)); +} + +void asst::Controller::convert_lf(std::vector& data) +{ + LogTraceFunction; + + if (data.empty() || data.size() <= 1) { + return; + } + using Iter = std::vector::iterator; + auto pred = [](const Iter& cur) -> bool { + return *cur == '\r' && *(cur + 1) == '\n'; + }; + // find the first of "\r\n" + Iter first_iter; + for (Iter iter = data.begin(); iter != data.end() - 1; ++iter) { + if (pred(iter)) { + first_iter = iter; + break; + } + } + // move forward all non-crlf elements + Iter end_r1_iter = data.end() - 1; + Iter next_iter = first_iter; + while (++first_iter != end_r1_iter) { + if (!pred(first_iter)) { + *next_iter = std::move(*first_iter); + ++next_iter; + } + } + *next_iter = std::move(*end_r1_iter); + ++next_iter; + data.erase(next_iter, data.end()); +} + +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 Point(x, y); +} + +void asst::Controller::random_delay() const +{ + auto& opt = Resrc.cfg().get_options(); + if (opt.control_delay_upper != 0) { + LogTraceFunction; + static std::default_random_engine rand_engine( + std::chrono::steady_clock::now().time_since_epoch().count()); + static std::uniform_int_distribution rand_uni( + opt.control_delay_lower, + opt.control_delay_upper); + + unsigned rand_delay = rand_uni(rand_engine); + + Log.trace("random_delay |", rand_delay, "ms"); + std::this_thread::sleep_for(std::chrono::milliseconds(rand_delay)); + } +} + +int asst::Controller::push_cmd(const std::string& cmd) +{ + random_delay(); + + std::unique_lock lock(m_cmd_queue_mutex); + m_cmd_queue.emplace(cmd); + m_cmd_condvar.notify_one(); + return ++m_push_id; +} + +void asst::Controller::wait(unsigned id) const noexcept +{ + while (id > m_completed_id) { + std::this_thread::yield(); + } +} + +bool asst::Controller::screencap() +{ + LogTraceFunction; + + auto&& [ret, data] = call_command(m_emulator_info.adb.screencap); + if (ret && !data.empty()) { + if (m_image_convert_lf) { + convert_lf(data); + } + m_cache_image = cv::imdecode(data, cv::IMREAD_COLOR); + if (m_cache_image.empty()) { + Log.info("Data is not empty, but image is empty, try to convert lf"); + convert_lf(data); + m_cache_image = cv::imdecode(data, cv::IMREAD_COLOR); + if (m_cache_image.empty()) { + m_image_convert_lf = false; + Log.error("convert lf and retry decode falied!"); + return false; + } + m_image_convert_lf = true; + return true; + } + return true; + } + else { + Log.error("Data is empty!"); + return false; + } + + //cv::Mat temp_image = cv::imdecode(data, cv::IMREAD_COLOR); + ////std::unique_lock image_lock(m_image_mutex); + //m_cache_image = std::move(temp_image); +} + +int asst::Controller::click(const Point& p, bool block) +{ + int x = p.x * m_control_scale; + int y = p.y * m_control_scale; + //log.trace("Click, raw:", p.x, p.y, "corr:", x, y); + + return click_without_scale(Point(x, y), block); +} + +int asst::Controller::click(const Rect& rect, bool block) +{ + return click(rand_point_in_rect(rect), block); +} + +int asst::Controller::click_without_scale(const Point& p, bool block) +{ + if (p.x < 0 || p.x >= m_emulator_info.adb.display_width || p.y < 0 || p.y >= m_emulator_info.adb.display_height) { + Log.error("click point out of range"); + } + std::string cur_cmd = utils::string_replace_all(m_emulator_info.adb.click, "[x]", std::to_string(p.x)); + cur_cmd = utils::string_replace_all(cur_cmd, "[y]", std::to_string(p.y)); + int id = push_cmd(cur_cmd); + if (block) { + wait(id); + } + return id; +} + +int asst::Controller::click_without_scale(const Rect& rect, bool block) +{ + return click_without_scale(rand_point_in_rect(rect), block); +} + +int asst::Controller::swipe(const Point& p1, const Point& p2, int duration, bool block, int extra_delay, bool extra_swipe) +{ + int x1 = p1.x * m_control_scale; + int y1 = p1.y * m_control_scale; + int x2 = p2.x * m_control_scale; + int y2 = 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, block, extra_delay, extra_swipe); +} + +int asst::Controller::swipe(const Rect& r1, const Rect& r2, int duration, bool block, int extra_delay, bool extra_swipe) +{ + return swipe(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, block, extra_delay, extra_swipe); +} + +int asst::Controller::swipe_without_scale(const Point& p1, const Point& p2, int duration, bool block, int extra_delay, bool extra_swipe) +{ + if (p1.x < 0 || p1.x >= m_emulator_info.adb.display_width || p1.y < 0 || p1.y >= m_emulator_info.adb.display_height || p2.x < 0 || p2.x >= m_emulator_info.adb.display_width || p2.y < 0 || p2.y >= m_emulator_info.adb.display_height) { + Log.error("swipe point out of range"); + } + std::string cur_cmd = utils::string_replace_all(m_emulator_info.adb.swipe, "[x1]", std::to_string(p1.x)); + cur_cmd = utils::string_replace_all(cur_cmd, "[y1]", std::to_string(p1.y)); + cur_cmd = utils::string_replace_all(cur_cmd, "[x2]", std::to_string(p2.x)); + cur_cmd = utils::string_replace_all(cur_cmd, "[y2]", std::to_string(p2.y)); + if (duration <= 0) { + cur_cmd = utils::string_replace_all(cur_cmd, "[duration]", ""); + } + else { + cur_cmd = utils::string_replace_all(cur_cmd, "[duration]", std::to_string(duration)); + } + + int id = 0; + + int extra_swipe_dist = Resrc.cfg().get_options().adb_extra_swipe_dist /* * m_control_scale*/; + int extra_swipe_duration = Resrc.cfg().get_options().adb_extra_swipe_duration; + + // 额外的滑动:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 + if (extra_swipe && extra_swipe_duration >= 0) { + std::string extra_cmd = utils::string_replace_all(m_emulator_info.adb.swipe, "[x1]", std::to_string(p2.x)); + extra_cmd = utils::string_replace_all(extra_cmd, "[y1]", std::to_string(p2.y)); + int end_x = 0, end_y = 0; + if (p2.x != p1.x) { + double k = (double)(p2.y - p1.y) / (p2.x - p1.x); + double temp = extra_swipe_dist / std::sqrt(1 + k * k); + end_x = p2.x + (p2.x > p1.x ? -1 : 1) * temp; + end_y = p2.y + (p2.y > p1.y ? -1 : 1) * std::fabs(k) * temp; + } + else { + end_x = p2.x; + end_y = p2.y + (p2.y > p1.y ? -1 : 1) * extra_swipe_dist; + } + extra_cmd = utils::string_replace_all(extra_cmd, "[x2]", std::to_string(end_x)); + extra_cmd = utils::string_replace_all(extra_cmd, "[y2]", std::to_string(end_y)); + extra_cmd = utils::string_replace_all(extra_cmd, "[duration]", std::to_string(extra_swipe_duration)); + + push_cmd(cur_cmd); + id = push_cmd(extra_cmd); + } + else { + id = push_cmd(cur_cmd); + } + + if (block) { + wait(id); + std::this_thread::sleep_for(std::chrono::milliseconds(extra_delay)); + } + return id; +} + +int asst::Controller::swipe_without_scale(const Rect& r1, const Rect& r2, int duration, bool block, int extra_delay, bool extra_swipe) +{ + return swipe_without_scale(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, block, extra_delay, extra_swipe); +} + +cv::Mat asst::Controller::get_image(bool raw) +{ + // 有些模拟器adb偶尔会莫名其妙截图失败,多试几次 + for (int i = 0; i != 20; ++i) { + if (screencap()) { + break; + } + } + //std::shared_lock image_lock(m_image_mutex); + if (raw) { + return m_cache_image; + } + else { + if (m_cache_image.empty()) { + return m_cache_image; + } + const static cv::Size size(m_scale_size.first, m_scale_size.second); + cv::Mat resize_mat; + cv::resize(m_cache_image, resize_mat, size, cv::INPAINT_NS); + return resize_mat; + } +} diff --git a/src/MeoAssistant/Controller.h b/src/MeoAssistant/Controller.h index 12641e7b22..4ea2dc3a57 100644 --- a/src/MeoAssistant/Controller.h +++ b/src/MeoAssistant/Controller.h @@ -1,108 +1,113 @@ -#pragma once - -#include -#include -#include - -#include -#include // for pimpl -#include -#include -#include -#include - -#include - -#include - -#include "AsstDef.h" - -namespace asst -{ - class Controller - { - public: - Controller(const Controller&) = delete; - Controller(Controller&&) = delete; - ~Controller(); - - static Controller& get_instance() - { - static Controller unique_instance; - return unique_instance; - } - - static void set_dirname(std::string dirname) noexcept; - - bool try_capture(const EmulatorInfo& info, bool without_handle = false); - cv::Mat get_image(bool raw = false); - - // 点击和滑动都是异步执行,返回该任务的id - int click(const Point& p, bool block = true); - int click(const Rect& rect, bool block = true); - int click_without_scale(const Point& p, bool block = true); - int click_without_scale(const Rect& rect, bool block = true); - - constexpr static int SwipeExtraDelayDefault = 1000; - int swipe(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); - int swipe(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); - int swipe_without_scale(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); - int swipe_without_scale(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); - - void wait(unsigned id) const noexcept; - - // 异形屏矫正 - Rect shaped_correct(const Rect& rect) const; - std::pair get_scale_size() const noexcept; - - Controller& operator=(const Controller&) = delete; - Controller& operator=(Controller&&) = delete; - - private: - Controller(); - - bool connect_adb(const std::string& address); - - void pipe_working_proc(); - std::pair> call_command(const std::string& cmd); - int push_cmd(const std::string& cmd); - bool screencap(); - Point rand_point_in_rect(const Rect& rect); - - void random_delay() const; - - // 转换data中所有的crlf为lf:有些模拟器自带的adb,exec-out输出的\n,会被替换成\r\n,导致解码错误,所以这里转一下回来(点名批评mumu) - static void convert_lf(std::vector& data); - - inline static std::string m_dirname; - - bool m_thread_exit = false; - //bool m_thread_idle = true; - std::thread m_cmd_thread; - std::mutex m_cmd_queue_mutex; - std::condition_variable m_cmd_condvar; - std::queue m_cmd_queue; - std::atomic m_completed_id = 0; - unsigned m_push_id = 0; // push_id的自增总是伴随着queue的push,肯定是要上锁的,所以没必要原子 - - //std::shared_mutex m_image_mutex; - cv::Mat m_cache_image; - bool m_image_convert_lf = false; - - constexpr static int PipeBuffSize = 1048576; // 管道缓冲区大小 - HANDLE m_pipe_read = nullptr; // 读管道句柄 - HANDLE m_pipe_write = nullptr; // 写管道句柄 - HANDLE m_pipe_child_read = nullptr; // 子进程的读管道句柄 - HANDLE m_pipe_child_write = nullptr; // 子进程的写管道句柄 - SECURITY_ATTRIBUTES m_pipe_sec_attr = { 0 }; // 管道安全描述符 - STARTUPINFOA m_child_startup_info = { 0 }; // 子进程启动信息 - - EmulatorInfo m_emulator_info; - std::minstd_rand m_rand_engine; - std::pair m_scale_size; - double m_control_scale = 1.0; - }; - - //static auto& ctrler = Controller::get_instance(); -#define Ctrler Controller::get_instance() -} +#pragma once + +#include +#include +#include + +#include +#include // for pimpl +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include + +#include "AsstDef.h" + +namespace asst +{ + class Controller + { + public: + Controller(const Controller&) = delete; + Controller(Controller&&) = delete; + ~Controller(); + + static Controller& get_instance() + { + static Controller unique_instance; + return unique_instance; + } + + static void set_dirname(std::string dirname) noexcept; + + bool try_capture(const EmulatorInfo& info, bool without_handle = false); + cv::Mat get_image(bool raw = false); + + // 点击和滑动都是异步执行,返回该任务的id + int click(const Point& p, bool block = true); + int click(const Rect& rect, bool block = true); + int click_without_scale(const Point& p, bool block = true); + int click_without_scale(const Rect& rect, bool block = true); + + constexpr static int SwipeExtraDelayDefault = 1000; + int swipe(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); + int swipe(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); + int swipe_without_scale(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); + int swipe_without_scale(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); + + void wait(unsigned id) const noexcept; + + // 异形屏矫正 + Rect shaped_correct(const Rect& rect) const; + std::pair get_scale_size() const noexcept; + + Controller& operator=(const Controller&) = delete; + Controller& operator=(Controller&&) = delete; + + private: + Controller(); + + bool connect_adb(const std::string& address); + + void pipe_working_proc(); + std::pair> call_command(const std::string& cmd); + int push_cmd(const std::string& cmd); + bool screencap(); + Point rand_point_in_rect(const Rect& rect); + + void random_delay() const; + + // 转换data中所有的crlf为lf:有些模拟器自带的adb,exec-out输出的\n,会被替换成\r\n,导致解码错误,所以这里转一下回来(点名批评mumu) + static void convert_lf(std::vector& data); + + inline static std::string m_dirname; + + bool m_thread_exit = false; + //bool m_thread_idle = true; + std::thread m_cmd_thread; + std::mutex m_cmd_queue_mutex; + std::condition_variable m_cmd_condvar; + std::queue m_cmd_queue; + std::atomic m_completed_id = 0; + unsigned m_push_id = 0; // push_id的自增总是伴随着queue的push,肯定是要上锁的,所以没必要原子 + + //std::shared_mutex m_image_mutex; + cv::Mat m_cache_image; + bool m_image_convert_lf = false; + + constexpr static int PipeBuffSize = 1048576; // 管道缓冲区大小 +#ifdef _WIN32 + HANDLE m_pipe_read = nullptr; // 读管道句柄 + HANDLE m_pipe_write = nullptr; // 写管道句柄 + HANDLE m_pipe_child_read = nullptr; // 子进程的读管道句柄 + HANDLE m_pipe_child_write = nullptr; // 子进程的写管道句柄 + SECURITY_ATTRIBUTES m_pipe_sec_attr = { 0 }; // 管道安全描述符 + STARTUPINFOA m_child_startup_info = { 0 }; // 子进程启动信息 +#endif + + EmulatorInfo m_emulator_info; + std::minstd_rand m_rand_engine; + std::pair m_scale_size; + double m_control_scale = 1.0; + }; + + //static auto& ctrler = Controller::get_instance(); +#define Ctrler Controller::get_instance() +} diff --git a/src/MeoAssistant/GeneralConfiger.cpp b/src/MeoAssistant/GeneralConfiger.cpp index 36fa9fb09c..a81da41122 100644 --- a/src/MeoAssistant/GeneralConfiger.cpp +++ b/src/MeoAssistant/GeneralConfiger.cpp @@ -1,62 +1,62 @@ -#include "GeneralConfiger.h" - -#include - -bool asst::GeneralConfiger::parse(const json::value& json) -{ - m_version = json.at("version").as_string(); - - { - const json::value& options_json = json.at("options"); - m_options.connect_type = static_cast(options_json.at("connectType").as_integer()); - m_options.task_delay = options_json.at("taskDelay").as_integer(); - m_options.control_delay_lower = options_json.at("controlDelayRange")[0].as_integer(); - m_options.control_delay_upper = options_json.at("controlDelayRange")[1].as_integer(); - //m_options.print_window = options_json.at("printWindow").as_boolean(); - m_options.adb_extra_swipe_dist = options_json.get("adbExtraSwipeDist", 100); +#include "GeneralConfiger.h" + +#include + +bool asst::GeneralConfiger::parse(const json::value& json) +{ + m_version = json.at("version").as_string(); + + { + const json::value& options_json = json.at("options"); + m_options.connect_type = static_cast(options_json.at("connectType").as_integer()); + m_options.task_delay = options_json.at("taskDelay").as_integer(); + m_options.control_delay_lower = options_json.at("controlDelayRange")[0].as_integer(); + m_options.control_delay_upper = options_json.at("controlDelayRange")[1].as_integer(); + //m_options.print_window = options_json.at("printWindow").as_boolean(); + m_options.adb_extra_swipe_dist = options_json.get("adbExtraSwipeDist", 100); m_options.adb_extra_swipe_duration = options_json.get("adbExtraSwipeDuration", -1); - auto& penguin_report = options_json.at("penguinReport"); - m_options.penguin_report.enable = penguin_report.get("enable", true); - m_options.penguin_report.cmd_format = penguin_report.get("cmdFormat", std::string()); + auto& penguin_report = options_json.at("penguinReport"); + m_options.penguin_report.enable = penguin_report.get("enable", true); + m_options.penguin_report.cmd_format = penguin_report.get("cmdFormat", std::string()); m_options.penguin_report.server = penguin_report.get("server", "CN"); if (options_json.exist("aipOcr")) { - auto& aip_ocr = options_json.at("aipOcr"); + auto& aip_ocr = options_json.at("aipOcr"); m_options.aip_ocr.enable = aip_ocr.get("enable", false); m_options.aip_ocr.accurate = aip_ocr.get("accurate", false); m_options.aip_ocr.client_id = aip_ocr.get("clientId", std::string()); m_options.aip_ocr.client_secret = aip_ocr.get("clientSerect", std::string()); } - } - - for (const auto& [name, emulator_json] : json.at("emulator").as_object()) { - EmulatorInfo emulator_info; - emulator_info.name = name; - - const json::object& handle_json = emulator_json.at("handle").as_object(); - emulator_info.handle.class_name = handle_json.at("class").as_string(); - emulator_info.handle.window_name = handle_json.at("window").as_string(); - - const json::object& adb_json = emulator_json.at("adb").as_object(); - emulator_info.adb.path = adb_json.at("path").as_string(); - - for (const json::value& address_json : adb_json.at("addresses").as_array()) { - emulator_info.adb.addresses.emplace_back(address_json.as_string()); - } - emulator_info.adb.devices = adb_json.at("devices").as_string(); - emulator_info.adb.address_regex = adb_json.at("addressRegex").as_string(); - emulator_info.adb.connect = adb_json.at("connect").as_string(); - emulator_info.adb.click = adb_json.at("click").as_string(); - emulator_info.adb.swipe = adb_json.at("swipe").as_string(); - emulator_info.adb.display = adb_json.at("display").as_string(); - emulator_info.adb.display_format = adb_json.at("displayFormat").as_string(); - emulator_info.adb.screencap = adb_json.at("screencap").as_string(); - emulator_info.adb.release = adb_json.at("release").as_string(); - //emulator_info.adb.pullscreen = adb_json.at("pullscreen").as_string(); - - m_emulators_info.emplace(name, std::move(emulator_info)); - } - - return true; + } + + for (const auto& [name, emulator_json] : json.at("emulator").as_object()) { + EmulatorInfo emulator_info; + emulator_info.name = name; + + const json::object& handle_json = emulator_json.at("handle").as_object(); + emulator_info.handle.class_name = handle_json.at("class").as_string(); + emulator_info.handle.window_name = handle_json.at("window").as_string(); + + const json::object& adb_json = emulator_json.at("adb").as_object(); + emulator_info.adb.path = adb_json.at("path").as_string(); + + for (const json::value& address_json : adb_json.at("addresses").as_array()) { + emulator_info.adb.addresses.emplace_back(address_json.as_string()); + } + emulator_info.adb.devices = adb_json.at("devices").as_string(); + emulator_info.adb.address_regex = adb_json.at("addressRegex").as_string(); + emulator_info.adb.connect = adb_json.at("connect").as_string(); + emulator_info.adb.click = adb_json.at("click").as_string(); + emulator_info.adb.swipe = adb_json.at("swipe").as_string(); + emulator_info.adb.display = adb_json.at("display").as_string(); + emulator_info.adb.display_format = adb_json.at("displayFormat").as_string(); + emulator_info.adb.screencap = adb_json.at("screencap").as_string(); + emulator_info.adb.release = adb_json.at("release").as_string(); + //emulator_info.adb.pullscreen = adb_json.at("pullscreen").as_string(); + + m_emulators_info.emplace(name, std::move(emulator_info)); + } + + return true; } diff --git a/src/MeoAssistant/GeneralConfiger.h b/src/MeoAssistant/GeneralConfiger.h index 73573f894f..62332061b2 100644 --- a/src/MeoAssistant/GeneralConfiger.h +++ b/src/MeoAssistant/GeneralConfiger.h @@ -1,90 +1,90 @@ -#pragma once - -#include "AbstractConfiger.h" - -#include -#include -#include - -#include "AsstDef.h" - -namespace asst -{ - enum class ConnectType - { - Emulator, - Custom +#pragma once + +#include "AbstractConfiger.h" + +#include +#include +#include + +#include "AsstDef.h" + +namespace asst +{ + enum class ConnectType + { + Emulator, + Custom }; - struct AipOcrCfg // 百度 OCR API 的配置 - { + struct AipOcrCfg // 百度 OCR API 的配置 + { bool enable = false; bool accurate = false; // 是否使用高精度识别 std::string client_id; - std::string client_secret; + std::string client_secret; }; struct PenguinReportCfg // 企鹅物流数据汇报 的配置 - { + { bool enable = false; std::string cmd_format; // 命令格式 std::string extra_param; // 额外参数 - std::string server; // 上传参数中"server"字段,"CN", "US", "JP" and "KR". - }; - - struct Options - { - ConnectType connect_type = ConnectType::Emulator; // 连接类型 - int task_delay = 0; // 任务间延时:越快操作越快,但会增加CPU消耗 - int control_delay_lower = 0; // 点击随机延时下限:每次点击操作会进行随机延时 - int control_delay_upper = 0; // 点击随机延时上限:每次点击操作会进行随机延时 - //bool print_window = false; // 截图功能:开启后每次结算界面会截图到screenshot目录下 - int adb_extra_swipe_dist = 0; // 额外的滑动距离:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 + std::string server; // 上传参数中"server"字段,"CN", "US", "JP" and "KR". + }; + + struct Options + { + ConnectType connect_type = ConnectType::Emulator; // 连接类型 + int task_delay = 0; // 任务间延时:越快操作越快,但会增加CPU消耗 + int control_delay_lower = 0; // 点击随机延时下限:每次点击操作会进行随机延时 + int control_delay_upper = 0; // 点击随机延时上限:每次点击操作会进行随机延时 + //bool print_window = false; // 截图功能:开启后每次结算界面会截图到screenshot目录下 + int adb_extra_swipe_dist = 0; // 额外的滑动距离:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 int adb_extra_swipe_duration = -1; // 额外的滑动持续时间:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来。若小于0,则关闭额外滑动功能 PenguinReportCfg penguin_report; // 企鹅数据汇报:每次到结算界面,汇报掉落数据至企鹅数据 https://penguin-stats.cn/ - AipOcrCfg aip_ocr; // 百度 OCR API 的配置 - }; - - class GeneralConfiger : public AbstractConfiger - { - public: - virtual ~GeneralConfiger() = default; - - const std::string& get_version() const noexcept - { - return m_version; - } - const Options& get_options() const noexcept - { - return m_options; - } - Options& get_options() - { - return m_options; - } - const std::unordered_map& get_emulators_info() const noexcept - { - return m_emulators_info; - } - - void set_options(Options opt) noexcept - { - m_options = std::move(opt); - } - void set_emulator_info(std::string name, EmulatorInfo emu) - { - m_emulators_info.emplace(std::move(name), std::move(emu)); - } - void set_emulator_path(std::string name, std::string path) - { - m_emulators_info[name].path = path; - } - - protected: - virtual bool parse(const json::value& json) override; - - std::string m_version; - Options m_options; - std::unordered_map m_emulators_info; - }; -} + AipOcrCfg aip_ocr; // 百度 OCR API 的配置 + }; + + class GeneralConfiger : public AbstractConfiger + { + public: + virtual ~GeneralConfiger() = default; + + const std::string& get_version() const noexcept + { + return m_version; + } + const Options& get_options() const noexcept + { + return m_options; + } + Options& get_options() + { + return m_options; + } + const std::unordered_map& get_emulators_info() const noexcept + { + return m_emulators_info; + } + + void set_options(Options opt) noexcept + { + m_options = std::move(opt); + } + void set_emulator_info(std::string name, EmulatorInfo emu) + { + m_emulators_info.emplace(std::move(name), std::move(emu)); + } + void set_emulator_path(std::string name, std::string path) + { + m_emulators_info[name].path = path; + } + + protected: + virtual bool parse(const json::value& json) override; + + std::string m_version; + Options m_options; + std::unordered_map m_emulators_info; + }; +} diff --git a/src/MeoAssistant/Linux编译教程.md b/src/MeoAssistant/Linux编译教程.md new file mode 100644 index 0000000000..be199d06de --- /dev/null +++ b/src/MeoAssistant/Linux编译教程.md @@ -0,0 +1,37 @@ +# Linux 编译教程 + +**本教程需要读者有一定的 Linux 环境配置能力及编程基础!** + +虽然没想明白为什么 Linux 下需要用助手挂模拟器,嘛总之大家有这个需求还是弄一下_(:з」∠)_ + +作者对 Linux 一些环境问题也是半懂不懂,所以虽说是教程,也只是分享一下自己的踩坑经历,如果遇到其他问题欢迎提出 ISSUE 一起讨论下 orz + +## 下载编译第三方库 + +### Opencv + +请自行搜索教程安装,没什么特别的,作者当前成功验证过的版本为`4.5.3`版本,不需要`opencv_contrib` + +### PaddleOCR + +1. 使用我魔改了接口的版本:https://github.com/MistEO/PaddleOCR +2. 参考 [这个教程](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.3/deploy/cpp_infer#readme) +3. PaddlePaddle 直接[下载](https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0/guides/05_inference_deployment/inference/build_and_install_lib_cn.html)即可 + +编译选项参考 + +```bash +cmake ../ -DPADDLE_LIB=/your_path/paddle_inference/ -D OPENCV_DIR=/your_path_to_opencv/ -DBUILD_SHARED=ON -DWITH_MKL=OFF -DWITH_STATIC_LIB=OFF +``` + +### meojson + +https://github.com/MistEO/meojson + +```bash +make shared +``` + +### penguin-stats-recognize-v3 + +使用我魔改了接口的版本:https://github.com/MistEO/penguin-stats-recognize-v3 \ No newline at end of file diff --git a/src/MeoAssistant/Logger.hpp b/src/MeoAssistant/Logger.hpp index 55650b7ae8..34599b8ea1 100644 --- a/src/MeoAssistant/Logger.hpp +++ b/src/MeoAssistant/Logger.hpp @@ -1,182 +1,201 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "AsstUtils.hpp" -#include "Version.h" - -namespace asst -{ - class Logger - { - public: - ~Logger() - { - flush(); - } - - Logger(const Logger&) = delete; - Logger(Logger&&) = delete; - - static Logger& get_instance() - { - static Logger _unique_instance; - return _unique_instance; - } - - static void set_dirname(std::string dirname) noexcept - { - m_dirname = std::move(dirname); - } - - template - inline void trace(Args&&... args) - { - std::string_view level = "TRC"; - log(level, std::forward(args)...); - } - template - inline void info(Args&&... args) - { - std::string_view level = "INF"; - log(level, std::forward(args)...); - } - template - inline void error(Args&&... args) - { - std::string_view level = "ERR"; - log(level, std::forward(args)...); - } - void flush() - { - std::unique_lock trace_lock(m_trace_mutex); - if (m_ofs.is_open()) { - m_ofs.close(); - } - } - - const std::string m_log_filename = m_dirname + "asst.log"; - const std::string m_log_bak_filename = m_dirname + "asst.bak.log"; - - private: - Logger() - { - check_filesize_and_remove(); - log_init_info(); - } - - void check_filesize_and_remove() - { - constexpr uintmax_t MaxLogSize = 4 * 1024 * 1024; - try { - if (std::filesystem::exists(m_log_filename)) { - uintmax_t log_size = std::filesystem::file_size(m_log_filename); - if (log_size >= MaxLogSize) { - std::filesystem::rename(m_log_filename, m_log_bak_filename); - } - } - } - catch (...) { - ; - } - } - void log_init_info() - { - trace("-----------------------------"); - trace("MeoAssistant Process Start"); - trace("Version", asst::Version); - trace("Build DataTime", __DATE__, __TIME__); - trace("Working Path", m_dirname); - trace("-----------------------------"); - } - - template - void log(std::string_view level, Args&&... args) - { - std::unique_lock trace_lock(m_trace_mutex); - - constexpr int buff_len = 128; - char buff[buff_len] = { 0 }; - sprintf_s(buff, buff_len, "[%s][%s][Px%x][Tx%x]", - asst::utils::get_format_time().c_str(), - level.data(), _getpid(), ::GetCurrentThreadId()); - - if (!m_ofs.is_open()) { - m_ofs = std::ofstream(m_log_filename, std::ios::out | std::ios::app); - } -#ifdef LOG_TRACE - stream_args(m_ofs, buff, args...); -#else - stream_args(m_ofs, buff, std::forward(args)...); -#endif - -#ifdef LOG_TRACE - stream_args(std::cout, buff, std::forward(args)...); -#endif - } - - template - inline void stream_args(std::ostream& os, T&& first, Args&&... rest) - { - stream()(os, std::forward(first)); - stream_args(os, std::forward(rest)...); - } - template - inline void stream_args(std::ostream& os) - { - os << std::endl; - } - - template - struct stream - { - inline void operator()(std::ostream& os, T&& first) - { - os << first << " "; - } - }; - template - struct stream::value>::type> - { - inline void operator()(std::ostream& os, T&& first) - { - os << utils::utf8_to_gbk(first) << " "; - } - }; - - inline static std::string m_dirname; - std::mutex m_trace_mutex; - std::ofstream m_ofs; - }; - - class LoggerAux - { - public: - LoggerAux(const std::string& func_name) - : m_func_name(func_name), - m_start_time(std::chrono::steady_clock::now()) - { - Logger::get_instance().trace(m_func_name, " | enter"); - } - ~LoggerAux() - { - auto duration = std::chrono::steady_clock::now() - m_start_time; - Logger::get_instance().trace(m_func_name, " | leave,", - std::chrono::duration_cast(duration).count(), "ms"); - } - - private: - std::string m_func_name; - std::chrono::time_point m_start_time; - }; - - //static auto& log = Logger::get_instance(); -#define Log Logger::get_instance() -#define LogTraceFunction LoggerAux _func_aux(__FUNCTION__) -#define LogTraceScope LoggerAux _func_aux -} +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "AsstUtils.hpp" +#include "Version.h" + +namespace asst +{ + class Logger + { + public: + ~Logger() + { + flush(); + } + + Logger(const Logger&) = delete; + Logger(Logger&&) = delete; + + static Logger& get_instance() + { + static Logger _unique_instance; + return _unique_instance; + } + + static void set_dirname(std::string dirname) noexcept + { + m_dirname = std::move(dirname); + } + + template + inline void trace(Args&&... args) + { + std::string_view level = "TRC"; + log(level, std::forward(args)...); + } + template + inline void info(Args&&... args) + { + std::string_view level = "INF"; + log(level, std::forward(args)...); + } + template + inline void error(Args&&... args) + { + std::string_view level = "ERR"; + log(level, std::forward(args)...); + } + void flush() + { + std::unique_lock trace_lock(m_trace_mutex); + if (m_ofs.is_open()) { + m_ofs.close(); + } + } + + const std::string m_log_filename = m_dirname + "asst.log"; + const std::string m_log_bak_filename = m_dirname + "asst.bak.log"; + + private: + Logger() + { + check_filesize_and_remove(); + log_init_info(); + } + + void check_filesize_and_remove() + { + constexpr uintmax_t MaxLogSize = 4 * 1024 * 1024; + try { + if (std::filesystem::exists(m_log_filename)) { + uintmax_t log_size = std::filesystem::file_size(m_log_filename); + if (log_size >= MaxLogSize) { + std::filesystem::rename(m_log_filename, m_log_bak_filename); + } + } + } + catch (...) { + ; + } + } + void log_init_info() + { + trace("-----------------------------"); + trace("MeoAssistant Process Start"); + trace("Version", asst::Version); + trace("Build DataTime", __DATE__, __TIME__); + trace("Working Path", m_dirname); + trace("-----------------------------"); + } + + template + void log(std::string_view level, Args&&... args) + { + std::unique_lock trace_lock(m_trace_mutex); + + constexpr int buff_len = 128; + char buff[buff_len] = { 0 }; +#ifdef _WIN32 +#ifdef _MSC_VER + sprintf_s(buff, buff_len, +#else // ! _MSC_VER + sprintf(buff, +#endif // END _MSC_VER + "[%s][%s][Px%x][Tx%x]", + asst::utils::get_format_time().c_str(), + level.data(), _getpid(), ::GetCurrentThreadId() + ); +#else // ! _WIN32 + sprintf(buff, "[%s][%s][Px%x][Tx%x]", + asst::utils::get_format_time().c_str(), + level.data(), getpid(), gettid() + ); +#endif // END _WIN32 + + if (!m_ofs.is_open()) { + m_ofs = std::ofstream(m_log_filename, std::ios::out | std::ios::app); + } +#ifdef LOG_TRACE + stream_args(m_ofs, buff, args...); +#else + stream_args(m_ofs, buff, std::forward(args)...); +#endif + +#ifdef LOG_TRACE + stream_args(std::cout, buff, std::forward(args)...); +#endif + } + + template + inline void stream_args(std::ostream& os, T&& first, Args&&... rest) + { + stream()(os, std::forward(first)); + stream_args(os, std::forward(rest)...); + } + template + inline void stream_args(std::ostream& os) + { + os << std::endl; + } + + template + struct stream + { + inline void operator()(std::ostream& os, T&& first) + { + os << first << " "; + } + }; + template + struct stream::value>::type> + { + inline void operator()(std::ostream& os, T&& first) + { +#ifdef _WIN32 + os << utils::utf8_to_gbk(first) << " "; +#else + os << first << " "; // Don't fucking use gbk in linux +#endif + } + }; + + inline static std::string m_dirname; + std::mutex m_trace_mutex; + std::ofstream m_ofs; + }; + + class LoggerAux + { + public: + LoggerAux(const std::string& func_name) + : m_func_name(func_name), + m_start_time(std::chrono::steady_clock::now()) + { + Logger::get_instance().trace(m_func_name, " | enter"); + } + ~LoggerAux() + { + auto duration = std::chrono::steady_clock::now() - m_start_time; + Logger::get_instance().trace(m_func_name, " | leave,", + std::chrono::duration_cast(duration).count(), "ms"); + } + + private: + std::string m_func_name; + std::chrono::time_point m_start_time; + }; + + //static auto& log = Logger::get_instance(); +#define Log Logger::get_instance() +#define LogTraceFunction LoggerAux _func_aux(__FUNCTION__) +#define LogTraceScope LoggerAux _func_aux +} diff --git a/src/MeoAssistant/OcrPack.cpp b/src/MeoAssistant/OcrPack.cpp index d7274331ef..56359af8d4 100644 --- a/src/MeoAssistant/OcrPack.cpp +++ b/src/MeoAssistant/OcrPack.cpp @@ -1,115 +1,115 @@ -#include "OcrPack.h" - -#include -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" - -asst::OcrPack::~OcrPack() -{ - PaddleOcrDestroy(m_ocr); -} - -bool asst::OcrPack::load(const std::string& dir) -{ - constexpr static const char* DetName = "\\det"; - //constexpr static const char* ClsName = "\\cls"; - constexpr static const char* RecName = "\\rec"; - constexpr static const char* KeysName = "\\ppocr_keys_v1.txt"; - - const std::string dst_filename = dir + DetName; - //const std::string cls_filename = dir + ClsName; - const std::string rec_filename = dir + RecName; - const std::string keys_filename = dir + KeysName; - - if (m_ocr != nullptr) { - PaddleOcrDestroy(m_ocr); - } - m_ocr = PaddleOcrCreate(dst_filename.c_str(), rec_filename.c_str(), keys_filename.c_str(), nullptr); - - return m_ocr != nullptr; -} - -std::vector asst::OcrPack::recognize(const cv::Mat& image, const asst::TextRectProc& pred, bool without_det) -{ - LogTraceFunction; - - std::vector buf; - cv::imencode(".png", image, buf); - - constexpr static size_t MaxBoxSize = 128; - // each box has 8 value ( 4 points, x and y ) - int boxes[MaxBoxSize * 8] = { 0 }; - char* strs[MaxBoxSize] = { 0 }; - for (size_t i = 0; i != MaxBoxSize; ++i) { - constexpr static size_t MaxTextSize = 1024; - *(strs + i) = new char[MaxTextSize]; - memset(*(strs + i), 0, MaxTextSize); - } - float scores[MaxBoxSize] = { 0 }; - size_t size; - - Log.trace("Without Det:", without_det); - if (!without_det) { - PaddleOcrSystem(m_ocr, buf.data(), buf.size(), false, - boxes, strs, scores, &size, nullptr, nullptr); - } - else { - PaddleOcrRec(m_ocr, buf.data(), buf.size(), - strs, scores, &size, nullptr, nullptr); - } - - std::vector result; - std::string log_str_raw; - std::string log_str_proc; - for (size_t i = 0; i != size; ++i) { - // the box rect like ↓ - // 0 - 1 - // 3 - 2 - Rect rect; - if (!without_det) { - int* box = boxes + i * 8; - int x_collect[4] = { *(box + 0), *(box + 2), *(box + 4), *(box + 6) }; - int y_collect[4] = { *(box + 1), *(box + 3), *(box + 5), *(box + 7) }; - int left = int(*std::min_element(x_collect, x_collect + 4)); - int right = int(*std::max_element(x_collect, x_collect + 4)); - int top = int(*std::min_element(y_collect, y_collect + 4)); - int bottom = int(*std::max_element(y_collect, y_collect + 4)); - rect = Rect(left, top, right - left, bottom - top); - } - std::string text(*(strs + i)); - float score = *(scores + i); - - TextRect tr{ text, rect, score }; - - log_str_raw += tr.to_string() + ", "; - if (!pred || pred(tr)) { - log_str_proc += tr.to_string() + ", "; - result.emplace_back(std::move(tr)); - } - } - for (size_t i = 0; i != MaxBoxSize; ++i) { - delete[] * (strs + i); - } - - Log.trace("OcrPack::recognize | raw : ", log_str_raw); - Log.trace("OcrPack::recognize | proc : ", log_str_proc); - return result; -} - -std::vector asst::OcrPack::recognize(const cv::Mat& image, const asst::Rect& roi, const asst::TextRectProc& pred, bool without_det) -{ - auto rect_cor = [&roi, &pred, &without_det](TextRect& tr) -> bool { - if (without_det) { - tr.rect = roi; - } - else { - tr.rect.x += roi.x; - tr.rect.y += roi.y; - } - return pred(tr); - }; - Log.trace("OcrPack::recognize | roi : ", roi.to_string()); - return recognize(image(utils::make_rect(roi)), rect_cor, without_det); -} +#include "OcrPack.h" + +#include +#include + +#include "AsstUtils.hpp" +#include "Logger.hpp" + +asst::OcrPack::~OcrPack() +{ + PaddleOcrDestroy(m_ocr); +} + +bool asst::OcrPack::load(const std::string& dir) +{ + constexpr static const char* DetName = "/det"; + //constexpr static const char* ClsName = "/cls"; + constexpr static const char* RecName = "/rec"; + constexpr static const char* KeysName = "/ppocr_keys_v1.txt"; + + const std::string dst_filename = dir + DetName; + //const std::string cls_filename = dir + ClsName; + const std::string rec_filename = dir + RecName; + const std::string keys_filename = dir + KeysName; + + if (m_ocr != nullptr) { + PaddleOcrDestroy(m_ocr); + } + m_ocr = PaddleOcrCreate(dst_filename.c_str(), rec_filename.c_str(), keys_filename.c_str(), nullptr); + + return m_ocr != nullptr; +} + +std::vector asst::OcrPack::recognize(const cv::Mat& image, const asst::TextRectProc& pred, bool without_det) +{ + LogTraceFunction; + + std::vector buf; + cv::imencode(".png", image, buf); + + constexpr static size_t MaxBoxSize = 128; + // each box has 8 value ( 4 points, x and y ) + int boxes[MaxBoxSize * 8] = { 0 }; + char* strs[MaxBoxSize] = { 0 }; + for (size_t i = 0; i != MaxBoxSize; ++i) { + constexpr static size_t MaxTextSize = 1024; + *(strs + i) = new char[MaxTextSize]; + memset(*(strs + i), 0, MaxTextSize); + } + float scores[MaxBoxSize] = { 0 }; + size_t size; + + Log.trace("Without Det:", without_det); + if (!without_det) { + PaddleOcrSystem(m_ocr, buf.data(), buf.size(), false, + boxes, strs, scores, &size, nullptr, nullptr); + } + else { + PaddleOcrRec(m_ocr, buf.data(), buf.size(), + strs, scores, &size, nullptr, nullptr); + } + + std::vector result; + std::string log_str_raw; + std::string log_str_proc; + for (size_t i = 0; i != size; ++i) { + // the box rect like ↓ + // 0 - 1 + // 3 - 2 + Rect rect; + if (!without_det) { + int* box = boxes + i * 8; + int x_collect[4] = { *(box + 0), *(box + 2), *(box + 4), *(box + 6) }; + int y_collect[4] = { *(box + 1), *(box + 3), *(box + 5), *(box + 7) }; + int left = int(*std::min_element(x_collect, x_collect + 4)); + int right = int(*std::max_element(x_collect, x_collect + 4)); + int top = int(*std::min_element(y_collect, y_collect + 4)); + int bottom = int(*std::max_element(y_collect, y_collect + 4)); + rect = Rect(left, top, right - left, bottom - top); + } + std::string text(*(strs + i)); + float score = *(scores + i); + + TextRect tr{ text, rect, score }; + + log_str_raw += tr.to_string() + ", "; + if (!pred || pred(tr)) { + log_str_proc += tr.to_string() + ", "; + result.emplace_back(std::move(tr)); + } + } + for (size_t i = 0; i != MaxBoxSize; ++i) { + delete[] * (strs + i); + } + + Log.trace("OcrPack::recognize | raw : ", log_str_raw); + Log.trace("OcrPack::recognize | proc : ", log_str_proc); + return result; +} + +std::vector asst::OcrPack::recognize(const cv::Mat& image, const asst::Rect& roi, const asst::TextRectProc& pred, bool without_det) +{ + auto rect_cor = [&roi, &pred, &without_det](TextRect& tr) -> bool { + if (without_det) { + tr.rect = roi; + } + else { + tr.rect.x += roi.x; + tr.rect.y += roi.y; + } + return pred(tr); + }; + Log.trace("OcrPack::recognize | roi : ", roi.to_string()); + return recognize(image(utils::make_rect(roi)), rect_cor, without_det); +} diff --git a/src/MeoAssistant/PenguinPack.cpp b/src/MeoAssistant/PenguinPack.cpp index 395099af6a..4a6ee7ddbd 100644 --- a/src/MeoAssistant/PenguinPack.cpp +++ b/src/MeoAssistant/PenguinPack.cpp @@ -1,95 +1,95 @@ -#include "PenguinPack.h" - -#include - -#include -#include -namespace penguin -{ -#include -} - -#include "AsstUtils.hpp" - -bool asst::PenguinPack::load(const std::string& dir) -{ - bool ret = load_json(dir + "\\json\\stages.json", dir + "\\json\\hash_index.json"); - - for (const auto& file : std::filesystem::directory_iterator(dir + "\\items")) { - ret &= load_templ(file.path().stem().u8string(), file.path().u8string()); - } - return ret; -} - -void asst::PenguinPack::set_language(const std::string& server) -{ - m_language = server; - penguin::load_server(server.c_str()); -} - -std::string asst::PenguinPack::recognize(const cv::Mat& image) -{ - std::vector buf; - cv::imencode(".png", image, buf); - return penguin::recognize(buf.data(), buf.size()); -} - -bool asst::PenguinPack::load_json(const std::string& stage_path, const std::string& hash_path) -{ - auto stage_parse_ret = json::parse(utils::load_file_without_bom(stage_path)); - if (!stage_parse_ret) { - m_last_error = stage_path + " parsing failed"; - return false; - } - // stage_json 来自 https://penguin-stats.io/PenguinStats/api/v2/stages - // 和接口需要的json有点区别,这里做个转换 - json::value stage_json = std::move(stage_parse_ret.value()); - json::object cvt_stage_json; - try { - for (const json::value& stage_info : stage_json.as_array()) { - if (!stage_info.exist("dropInfos")) { // 这种一般是以前的活动关,现在已经关闭了的 - continue; - } - std::string key = stage_info.at("code").as_string(); - json::value stage_dst; - stage_dst["stageId"] = stage_info.at("stageId"); - std::vector drops_vector; - for (const json::value& drop_info : stage_info.at("dropInfos").as_array()) { - if (drop_info.exist("itemId")) { - // 幸运掉落,家具啥的,企鹅数据不接,忽略掉 - if (drop_info.at("dropType").as_string() == "FURNITURE") { - continue; - } - drops_vector.emplace_back(drop_info.at("itemId")); - } - } - stage_dst["drops"] = json::array(std::move(drops_vector)); - stage_dst["existence"] = stage_info.at("existence").at(m_language).at("exist"); - - cvt_stage_json.emplace(std::move(key), std::move(stage_dst)); - } - } - catch (json::exception& e) { - m_last_error = stage_path + " parsing error " + e.what(); - return false; - } - std::string cvt_stage_string = cvt_stage_json.to_string(); - penguin::load_json(cvt_stage_string.c_str(), utils::load_file_without_bom(hash_path).c_str()); - return true; -} - -bool asst::PenguinPack::load_templ(const std::string& item_id, const std::string& path) -{ - cv::Mat image = cv::imread(path); - if (image.empty()) { - m_last_error = "templ is empty"; - return false; - } - - std::vector buf; - cv::imencode(".png", image, buf); - - penguin::load_templ(item_id.c_str(), buf.data(), buf.size()); - - return true; +#include "PenguinPack.h" + +#include + +#include +#include +namespace penguin +{ +#include +} + +#include "AsstUtils.hpp" + +bool asst::PenguinPack::load(const std::string& dir) +{ + bool ret = load_json(dir + "/json/stages.json", dir + "/json/hash_index.json"); + + for (const auto& file : std::filesystem::directory_iterator(dir + "/items")) { + ret &= load_templ(file.path().stem().u8string(), file.path().u8string()); + } + return ret; +} + +void asst::PenguinPack::set_language(const std::string& server) +{ + m_language = server; + penguin::load_server(server.c_str()); +} + +std::string asst::PenguinPack::recognize(const cv::Mat& image) +{ + std::vector buf; + cv::imencode(".png", image, buf); + return penguin::recognize(buf.data(), buf.size()); +} + +bool asst::PenguinPack::load_json(const std::string& stage_path, const std::string& hash_path) +{ + auto stage_parse_ret = json::parse(utils::load_file_without_bom(stage_path)); + if (!stage_parse_ret) { + m_last_error = stage_path + " parsing failed"; + return false; + } + // stage_json 来自 https://penguin-stats.io/PenguinStats/api/v2/stages + // 和接口需要的json有点区别,这里做个转换 + json::value stage_json = std::move(stage_parse_ret.value()); + json::object cvt_stage_json; + try { + for (const json::value& stage_info : stage_json.as_array()) { + if (!stage_info.exist("dropInfos")) { // 这种一般是以前的活动关,现在已经关闭了的 + continue; + } + std::string key = stage_info.at("code").as_string(); + json::value stage_dst; + stage_dst["stageId"] = stage_info.at("stageId"); + std::vector drops_vector; + for (const json::value& drop_info : stage_info.at("dropInfos").as_array()) { + if (drop_info.exist("itemId")) { + // 幸运掉落,家具啥的,企鹅数据不接,忽略掉 + if (drop_info.at("dropType").as_string() == "FURNITURE") { + continue; + } + drops_vector.emplace_back(drop_info.at("itemId")); + } + } + stage_dst["drops"] = json::array(std::move(drops_vector)); + stage_dst["existence"] = stage_info.at("existence").at(m_language).at("exist"); + + cvt_stage_json.emplace(std::move(key), std::move(stage_dst)); + } + } + catch (json::exception& e) { + m_last_error = stage_path + " parsing error " + e.what(); + return false; + } + std::string cvt_stage_string = cvt_stage_json.to_string(); + penguin::load_json(cvt_stage_string.c_str(), utils::load_file_without_bom(hash_path).c_str()); + return true; +} + +bool asst::PenguinPack::load_templ(const std::string& item_id, const std::string& path) +{ + cv::Mat image = cv::imread(path); + if (image.empty()) { + m_last_error = "templ is empty"; + return false; + } + + std::vector buf; + cv::imencode(".png", image, buf); + + penguin::load_templ(item_id.c_str(), buf.data(), buf.size()); + + return true; } \ No newline at end of file diff --git a/src/MeoAssistant/PenguinUploader.cpp b/src/MeoAssistant/PenguinUploader.cpp index 75dd39365e..c762f78b0f 100644 --- a/src/MeoAssistant/PenguinUploader.cpp +++ b/src/MeoAssistant/PenguinUploader.cpp @@ -1,58 +1,58 @@ -#include "PenguinUploader.h" - -#include - -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" -#include "Resource.h" -#include "Version.h" - -bool asst::PenguinUploader::upload(const std::string& rec_res) -{ - std::string body = cvt_json(rec_res); - return request_penguin(body); -} - -std::string asst::PenguinUploader::cvt_json(const std::string& rec_res) -{ - auto parse_ret = json::parse(rec_res); - if (!parse_ret) { - return std::string(); - } - json::value rec = std::move(parse_ret.value()); - - // Doc: https://developer.penguin-stats.io/public-api/api-v2-instruction/report-api - json::value body; - auto& opt = Resrc.cfg().get_options(); - body["server"] = opt.penguin_report.server; - body["stageId"] = rec["stage"]["stageId"]; - // To fix: https://github.com/MistEO/MeoAssistantArknights/issues/40 - body["drops"] = json::array(); - for (auto&& drop : rec["drops"].as_array()) { - if (!drop["itemId"].as_string().empty()) { - body["drops"].as_array().emplace_back(drop); - } - } - body["source"] = "MeoAssistant"; - body["version"] = Version; - - return body.to_string(); -} - -bool asst::PenguinUploader::request_penguin(const std::string& body) -{ - auto& opt = Resrc.cfg().get_options(); - std::string body_escape = utils::string_replace_all(body, "\"", "\\\""); - std::string cmd_line = utils::string_replace_all(opt.penguin_report.cmd_format, "[body]", body_escape); - cmd_line = utils::string_replace_all(cmd_line, "[extra]", opt.penguin_report.extra_param); - - Log.trace("request_penguin |", cmd_line); - - std::string response = utils::callcmd(cmd_line); - - Log.trace("response:\n", response); - - return true; -} +#include "PenguinUploader.h" + +// #include + +#include + +#include "AsstUtils.hpp" +#include "Logger.hpp" +#include "Resource.h" +#include "Version.h" + +bool asst::PenguinUploader::upload(const std::string& rec_res) +{ + std::string body = cvt_json(rec_res); + return request_penguin(body); +} + +std::string asst::PenguinUploader::cvt_json(const std::string& rec_res) +{ + auto parse_ret = json::parse(rec_res); + if (!parse_ret) { + return std::string(); + } + json::value rec = std::move(parse_ret.value()); + + // Doc: https://developer.penguin-stats.io/public-api/api-v2-instruction/report-api + json::value body; + auto& opt = Resrc.cfg().get_options(); + body["server"] = opt.penguin_report.server; + body["stageId"] = rec["stage"]["stageId"]; + // To fix: https://github.com/MistEO/MeoAssistantArknights/issues/40 + body["drops"] = json::array(); + for (auto&& drop : rec["drops"].as_array()) { + if (!drop["itemId"].as_string().empty()) { + body["drops"].as_array().emplace_back(drop); + } + } + body["source"] = "MeoAssistant"; + body["version"] = Version; + + return body.to_string(); +} + +bool asst::PenguinUploader::request_penguin(const std::string& body) +{ + auto& opt = Resrc.cfg().get_options(); + std::string body_escape = utils::string_replace_all(body, "\"", "\\\""); + std::string cmd_line = utils::string_replace_all(opt.penguin_report.cmd_format, "[body]", body_escape); + cmd_line = utils::string_replace_all(cmd_line, "[extra]", opt.penguin_report.extra_param); + + Log.trace("request_penguin |", cmd_line); + + std::string response = utils::callcmd(cmd_line); + + Log.trace("response:\n", response); + + return true; +} diff --git a/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp b/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp index 9a64c3fb8f..9440cb143f 100644 --- a/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp +++ b/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp @@ -1,126 +1,126 @@ -#include "ProcessTaskImageAnalyzer.h" - -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" -#include "MatchImageAnalyzer.h" -#include "OcrImageAnalyzer.h" -#include "Resource.h" - -asst::ProcessTaskImageAnalyzer::ProcessTaskImageAnalyzer(const cv::Mat& image, std::vector tasks_name) - : AbstractImageAnalyzer(image), - m_tasks_name(std::move(tasks_name)) -{ - ; -} - -asst::ProcessTaskImageAnalyzer::~ProcessTaskImageAnalyzer() = default; - -bool asst::ProcessTaskImageAnalyzer::match_analyze(std::shared_ptr task_ptr) -{ - if (!m_match_analyzer) { - m_match_analyzer = std::make_unique(m_image); - } - const auto match_task_ptr = std::dynamic_pointer_cast(task_ptr); - m_match_analyzer->set_task_info(*match_task_ptr); - - if (m_match_analyzer->analyze()) { - m_result = match_task_ptr; - m_result_rect = m_match_analyzer->get_result().rect; - task_ptr->region_of_appeared = m_result_rect; - return true; - } - return false; -} - -bool asst::ProcessTaskImageAnalyzer::ocr_analyze(std::shared_ptr task_ptr) -{ - std::shared_ptr ocr_task_ptr = std::dynamic_pointer_cast(task_ptr); - - // 先尝试从缓存的结果里找 - //for (const TextRect& tr : m_ocr_cache) { - // TextRect temp = tr; - // for (const auto& [regex, new_str] : ocr_task_ptr->replace_map) { - // temp.text = std::regex_replace(temp.text, std::regex(regex), new_str); - // } - // for (const auto& text : ocr_task_ptr->text) { - // bool flag = false; - // if (ocr_task_ptr->need_full_match) { - // if (temp.text == text) { - // flag = true; - // } - // } - // else if (temp.text.find(text) != std::string::npos) { - // flag = true; - // } - // // 即使找到了,也得在ROI里才行,不然过滤掉 - // if (flag && ocr_task_ptr->roi.include(tr.rect)) { - // m_result = ocr_task_ptr; - // m_result_rect = tr.rect; - // ocr_task_ptr->region_of_appeared = m_result_rect; - // Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found in cache", tr.to_string()); - // return true; - // } - // } - //} - if (!m_ocr_analyzer) { - m_ocr_analyzer = std::make_unique(m_image); - } - m_ocr_analyzer->set_task_info(*ocr_task_ptr); - - bool ret = m_ocr_analyzer->analyze(); - - if (ret) { - const auto& ocr_result = m_ocr_analyzer->get_result(); - auto& res = ocr_result.front(); - m_result = ocr_task_ptr; - m_result_rect = res.rect; - ocr_task_ptr->region_of_appeared = res.rect; - //m_ocr_cache.insert(m_ocr_cache.end(), ocr_result.begin(), ocr_result.end()); - Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found", res.to_string()); - } - return ret; -} - -void asst::ProcessTaskImageAnalyzer::reset() noexcept -{ - //m_ocr_cache.clear(); - m_ocr_analyzer = nullptr; - m_match_analyzer = nullptr; -} - -bool asst::ProcessTaskImageAnalyzer::analyze() -{ - m_result = nullptr; - m_result_rect = Rect(); - - for (const std::string& task_name : m_tasks_name) { - auto task_ptr = task.get(task_name); - - switch (task_ptr->algorithm) { - case AlgorithmType::JustReturn: - m_result = task_ptr; - return true; - case AlgorithmType::MatchTemplate: - if (match_analyze(task_ptr)) { - return true; - } - break; - case AlgorithmType::OcrDetect: - if (ocr_analyze(task_ptr)) { - return true; - } - break; - default: - break; - } - } - return false; -} - -void asst::ProcessTaskImageAnalyzer::set_image(const cv::Mat & image) -{ - AbstractImageAnalyzer::set_image(image); - reset(); -} +#include "ProcessTaskImageAnalyzer.h" + +#include + +#include "AsstUtils.hpp" +#include "Logger.hpp" +#include "MatchImageAnalyzer.h" +#include "OcrImageAnalyzer.h" +#include "Resource.h" + +asst::ProcessTaskImageAnalyzer::ProcessTaskImageAnalyzer(const cv::Mat& image, std::vector tasks_name) + : AbstractImageAnalyzer(image), + m_tasks_name(std::move(tasks_name)) +{ + ; +} + +asst::ProcessTaskImageAnalyzer::~ProcessTaskImageAnalyzer() = default; + +bool asst::ProcessTaskImageAnalyzer::match_analyze(std::shared_ptr task_ptr) +{ + if (!m_match_analyzer) { + m_match_analyzer = std::make_unique(m_image); + } + const auto match_task_ptr = std::dynamic_pointer_cast(task_ptr); + m_match_analyzer->set_task_info(*match_task_ptr); + + if (m_match_analyzer->analyze()) { + m_result = match_task_ptr; + m_result_rect = m_match_analyzer->get_result().rect; + task_ptr->region_of_appeared = m_result_rect; + return true; + } + return false; +} + +bool asst::ProcessTaskImageAnalyzer::ocr_analyze(std::shared_ptr task_ptr) +{ + std::shared_ptr ocr_task_ptr = std::dynamic_pointer_cast(task_ptr); + + // 先尝试从缓存的结果里找 + //for (const TextRect& tr : m_ocr_cache) { + // TextRect temp = tr; + // for (const auto& [regex, new_str] : ocr_task_ptr->replace_map) { + // temp.text = std::regex_replace(temp.text, std::regex(regex), new_str); + // } + // for (const auto& text : ocr_task_ptr->text) { + // bool flag = false; + // if (ocr_task_ptr->need_full_match) { + // if (temp.text == text) { + // flag = true; + // } + // } + // else if (temp.text.find(text) != std::string::npos) { + // flag = true; + // } + // // 即使找到了,也得在ROI里才行,不然过滤掉 + // if (flag && ocr_task_ptr->roi.include(tr.rect)) { + // m_result = ocr_task_ptr; + // m_result_rect = tr.rect; + // ocr_task_ptr->region_of_appeared = m_result_rect; + // Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found in cache", tr.to_string()); + // return true; + // } + // } + //} + if (!m_ocr_analyzer) { + m_ocr_analyzer = std::make_unique(m_image); + } + m_ocr_analyzer->set_task_info(*ocr_task_ptr); + + bool ret = m_ocr_analyzer->analyze(); + + if (ret) { + const auto& ocr_result = m_ocr_analyzer->get_result(); + auto& res = ocr_result.front(); + m_result = ocr_task_ptr; + m_result_rect = res.rect; + ocr_task_ptr->region_of_appeared = res.rect; + //m_ocr_cache.insert(m_ocr_cache.end(), ocr_result.begin(), ocr_result.end()); + Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found", res.to_string()); + } + return ret; +} + +void asst::ProcessTaskImageAnalyzer::reset() noexcept +{ + //m_ocr_cache.clear(); + m_ocr_analyzer = nullptr; + m_match_analyzer = nullptr; +} + +bool asst::ProcessTaskImageAnalyzer::analyze() +{ + m_result = nullptr; + m_result_rect = Rect(); + + for (const std::string& task_name : m_tasks_name) { + auto task_ptr = task.get(task_name); + + switch (task_ptr->algorithm) { + case AlgorithmType::JustReturn: + m_result = task_ptr; + return true; + case AlgorithmType::MatchTemplate: + if (match_analyze(task_ptr)) { + return true; + } + break; + case AlgorithmType::OcrDetect: + if (ocr_analyze(task_ptr)) { + return true; + } + break; + default: + break; + } + } + return false; +} + +void asst::ProcessTaskImageAnalyzer::set_image(const cv::Mat & image) +{ + AbstractImageAnalyzer::set_image(image); + reset(); +} diff --git a/src/MeoAssistant/ProcessTaskImageAnalyzer.h b/src/MeoAssistant/ProcessTaskImageAnalyzer.h index 1d6b75b8ce..886533007e 100644 --- a/src/MeoAssistant/ProcessTaskImageAnalyzer.h +++ b/src/MeoAssistant/ProcessTaskImageAnalyzer.h @@ -1,60 +1,63 @@ -#pragma once -#include "AbstractImageAnalyzer.h" - -#include -#include -#include - -#include "AsstDef.h" - -namespace asst -{ - class OcrImageAnalyzer; - class MatchImageAnalyzer; - - class ProcessTaskImageAnalyzer final : public AbstractImageAnalyzer - { - public: - using AbstractImageAnalyzer::AbstractImageAnalyzer; - ProcessTaskImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; - ProcessTaskImageAnalyzer(const cv::Mat& image, std::vector tasks_name); - virtual ~ProcessTaskImageAnalyzer(); - - virtual bool analyze() override; - virtual void set_image(const cv::Mat& image) override; - - void set_tasks(std::vector tasks_name) - { - m_tasks_name = std::move(tasks_name); - } - std::shared_ptr get_result() const noexcept - { - return m_result; - } - const Rect& get_rect() const noexcept - { - return m_result_rect; - } - - private: - // 该分析器不支持外部设置ROI - virtual void set_roi(const Rect& roi) noexcept override - { - AbstractImageAnalyzer::set_roi(roi); - } - virtual void set_image(const cv::Mat& image, const Rect& roi) - { - AbstractImageAnalyzer::set_image(image, roi); - } - bool match_analyze(std::shared_ptr task_ptr); - bool ocr_analyze(std::shared_ptr task_ptr); - void reset() noexcept; - - std::unique_ptr m_ocr_analyzer = nullptr; - std::unique_ptr m_match_analyzer = nullptr; - std::vector m_tasks_name; - std::shared_ptr m_result; - Rect m_result_rect; - //std::vector m_ocr_cache; - }; -} +#pragma once +#include "AbstractImageAnalyzer.h" + +#include +#include +#include + +#include "AsstDef.h" + +namespace asst +{ + class OcrImageAnalyzer; + class MatchImageAnalyzer; + + class ProcessTaskImageAnalyzer final : public AbstractImageAnalyzer + { + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + ProcessTaskImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; + ProcessTaskImageAnalyzer(const cv::Mat& image, std::vector tasks_name); + virtual ~ProcessTaskImageAnalyzer(); + + virtual bool analyze() override; + virtual void set_image(const cv::Mat& image) override; + + void set_tasks(std::vector tasks_name) + { + m_tasks_name = std::move(tasks_name); + } + std::shared_ptr get_result() const noexcept + { + return m_result; + } + const Rect& get_rect() const noexcept + { + return m_result_rect; + } + + ProcessTaskImageAnalyzer& operator=(const ProcessTaskImageAnalyzer&) = delete; + ProcessTaskImageAnalyzer& operator=(ProcessTaskImageAnalyzer&&) = delete; + + private: + // 该分析器不支持外部设置ROI + virtual void set_roi(const Rect& roi) noexcept override + { + AbstractImageAnalyzer::set_roi(roi); + } + virtual void set_image(const cv::Mat& image, const Rect& roi) + { + AbstractImageAnalyzer::set_image(image, roi); + } + bool match_analyze(std::shared_ptr task_ptr); + bool ocr_analyze(std::shared_ptr task_ptr); + void reset() noexcept; + + std::unique_ptr m_ocr_analyzer; + std::unique_ptr m_match_analyzer; + std::vector m_tasks_name; + std::shared_ptr m_result; + Rect m_result_rect; + //std::vector m_ocr_cache; + }; +} diff --git a/src/MeoAssistant/Resource.cpp b/src/MeoAssistant/Resource.cpp index f0a05a630d..a06a04f168 100644 --- a/src/MeoAssistant/Resource.cpp +++ b/src/MeoAssistant/Resource.cpp @@ -1,81 +1,81 @@ -#include "Resource.h" - -#include -#include -#include - -#include - -#include "AsstDef.h" - -bool asst::Resource::load(const std::string& dir) -{ - constexpr static const char* TemplsFilename = "template"; - constexpr static const char* GeneralCfgFilename = "config.json"; - constexpr static const char* TaskDataFilename = "tasks.json"; - constexpr static const char* RecruitCfgFilename = "recruit.json"; - constexpr static const char* ItemCfgFilename = "item_index.json"; - constexpr static const char* InfrastCfgFilename = "infrast.json"; - constexpr static const char* UserCfgFilename = "..\\user.json"; - constexpr static const char* OcrResourceFilename = "PaddleOCR"; - constexpr static const char* PenguinResourceFilename = "penguin-stats-recognize"; - - /* 加载各个Json配置文件 */ - if (!m_general_cfg_unique_ins.load(dir + GeneralCfgFilename)) { - m_last_error = m_general_cfg_unique_ins.get_last_error(); - return false; - } - if (!task.load(dir + TaskDataFilename)) { - m_last_error = task.get_last_error(); - return false; - } - - if (!m_recruit_cfg_unique_ins.load(dir + RecruitCfgFilename)) { - m_last_error = m_recruit_cfg_unique_ins.get_last_error(); - return false; - } - if (!m_item_cfg_unique_ins.load(dir + ItemCfgFilename)) { - m_last_error = m_item_cfg_unique_ins.get_last_error(); - return false; - } - if (!m_infrast_cfg_unique_ins.load(dir + InfrastCfgFilename)) { - m_last_error = m_infrast_cfg_unique_ins.get_last_error(); - return false; - } - - if (!m_user_cfg_unique_ins.load(dir + UserCfgFilename)) { - m_last_error = m_user_cfg_unique_ins.get_last_error(); - return false; - } - /* 根据用户配置,覆盖原有的部分配置 */ - for (const auto& [name, path] : m_user_cfg_unique_ins.get_emulator_path()) { - m_general_cfg_unique_ins.set_emulator_path(name, path); - } - const auto& opt = m_general_cfg_unique_ins.get_options(); - - /* 加载模板图片资源 */ - // task所需要的模板资源 - m_templ_resource_unique_ins.append_load_required(task.get_templ_required()); - // 基建所需要的模板资源 - m_templ_resource_unique_ins.append_load_required(m_infrast_cfg_unique_ins.get_templ_required()); - - if (!m_templ_resource_unique_ins.load(dir + TemplsFilename)) { - m_last_error = m_templ_resource_unique_ins.get_last_error(); - return false; - } - - /* 加载OCR库所需要的资源 */ - //m_ocr_pack_unique_ins.set_param(opt.ocr_gpu_index, opt.ocr_thread_number); - if (!m_ocr_pack_unique_ins.load(dir + OcrResourceFilename)) { - m_last_error = m_ocr_pack_unique_ins.get_last_error(); - return false; - } - /* 加载企鹅数据识别库所需要的资源 */ - m_penguin_pack_unique_ins.set_language(opt.penguin_report.server); - if (!m_penguin_pack_unique_ins.load(dir + PenguinResourceFilename)) { - m_last_error = m_penguin_pack_unique_ins.get_last_error(); - return false; - } - - return true; -} +#include "Resource.h" + +#include +#include +#include + +#include + +#include "AsstDef.h" + +bool asst::Resource::load(const std::string& dir) +{ + constexpr static const char* TemplsFilename = "template"; + constexpr static const char* GeneralCfgFilename = "config.json"; + constexpr static const char* TaskDataFilename = "tasks.json"; + constexpr static const char* RecruitCfgFilename = "recruit.json"; + constexpr static const char* ItemCfgFilename = "item_index.json"; + constexpr static const char* InfrastCfgFilename = "infrast.json"; + constexpr static const char* UserCfgFilename = "..\\user.json"; + constexpr static const char* OcrResourceFilename = "PaddleOCR"; + constexpr static const char* PenguinResourceFilename = "penguin-stats-recognize"; + + /* 加载各个Json配置文件 */ + if (!m_general_cfg_unique_ins.load(dir + GeneralCfgFilename)) { + m_last_error = std::string(GeneralCfgFilename) + ": " + m_general_cfg_unique_ins.get_last_error(); + return false; + } + if (!task.load(dir + TaskDataFilename)) { + m_last_error = std::string(TaskDataFilename) + ": " + task.get_last_error(); + return false; + } + + if (!m_recruit_cfg_unique_ins.load(dir + RecruitCfgFilename)) { + m_last_error = std::string(RecruitCfgFilename) + ": " + m_recruit_cfg_unique_ins.get_last_error(); + return false; + } + if (!m_item_cfg_unique_ins.load(dir + ItemCfgFilename)) { + m_last_error = std::string(ItemCfgFilename) + ": " + m_item_cfg_unique_ins.get_last_error(); + return false; + } + if (!m_infrast_cfg_unique_ins.load(dir + InfrastCfgFilename)) { + m_last_error = std::string(InfrastCfgFilename) + ": " + m_infrast_cfg_unique_ins.get_last_error(); + return false; + } + + if (!m_user_cfg_unique_ins.load(dir + UserCfgFilename)) { + m_last_error = std::string(UserCfgFilename) + ": " + m_user_cfg_unique_ins.get_last_error(); + return false; + } + /* 根据用户配置,覆盖原有的部分配置 */ + for (const auto& [name, path] : m_user_cfg_unique_ins.get_emulator_path()) { + m_general_cfg_unique_ins.set_emulator_path(name, path); + } + const auto& opt = m_general_cfg_unique_ins.get_options(); + + /* 加载模板图片资源 */ + // task所需要的模板资源 + m_templ_resource_unique_ins.append_load_required(task.get_templ_required()); + // 基建所需要的模板资源 + m_templ_resource_unique_ins.append_load_required(m_infrast_cfg_unique_ins.get_templ_required()); + + if (!m_templ_resource_unique_ins.load(dir + TemplsFilename)) { + m_last_error = std::string(TemplsFilename) + ": " + m_templ_resource_unique_ins.get_last_error(); + return false; + } + + /* 加载OCR库所需要的资源 */ + //m_ocr_pack_unique_ins.set_param(opt.ocr_gpu_index, opt.ocr_thread_number); + if (!m_ocr_pack_unique_ins.load(dir + OcrResourceFilename)) { + m_last_error = std::string(OcrResourceFilename) + ": " + m_ocr_pack_unique_ins.get_last_error(); + return false; + } + /* 加载企鹅数据识别库所需要的资源 */ + m_penguin_pack_unique_ins.set_language(opt.penguin_report.server); + if (!m_penguin_pack_unique_ins.load(dir + PenguinResourceFilename)) { + m_last_error = std::string(PenguinResourceFilename) + ": " + m_penguin_pack_unique_ins.get_last_error(); + return false; + } + + return true; +} diff --git a/src/MeoAssistant/Resource.h b/src/MeoAssistant/Resource.h index 5b1d3b7ca5..cd3836781a 100644 --- a/src/MeoAssistant/Resource.h +++ b/src/MeoAssistant/Resource.h @@ -1,117 +1,117 @@ -#pragma once - -#include "AbstractResource.h" - -#include -#include - -#include "GeneralConfiger.h" -#include "InfrastConfiger.h" -#include "ItemConfiger.h" -#include "OcrPack.h" -#include "PenguinPack.h" -#include "RecruitConfiger.h" -#include "TaskData.h" -#include "TemplResource.h" -#include "UserConfiger.h" - -namespace asst -{ - class Resource : public AbstractResource - { - public: - virtual ~Resource() = default; - - static Resource& get_instance() - { - static Resource unique_instance; - return unique_instance; - } - - virtual bool load(const std::string& dir) override; - - TemplResource& templ() noexcept - { - return m_templ_resource_unique_ins; - } - GeneralConfiger& cfg() noexcept - { - return m_general_cfg_unique_ins; - } - RecruitConfiger& recruit() noexcept - { - return m_recruit_cfg_unique_ins; - } - ItemConfiger& item() noexcept - { - return m_item_cfg_unique_ins; - } - InfrastConfiger& infrast() noexcept - { - return m_infrast_cfg_unique_ins; - } - UserConfiger& user() noexcept - { - return m_user_cfg_unique_ins; - } - OcrPack& ocr() noexcept - { - return m_ocr_pack_unique_ins; - } - PenguinPack& penguin() noexcept - { - return m_penguin_pack_unique_ins; - } - - const TemplResource& templ() const noexcept - { - return m_templ_resource_unique_ins; - } - const GeneralConfiger& cfg() const noexcept - { - return m_general_cfg_unique_ins; - } - const RecruitConfiger& recruit() const noexcept - { - return m_recruit_cfg_unique_ins; - } - const ItemConfiger& item() const noexcept - { - return m_item_cfg_unique_ins; - } - const InfrastConfiger& infrast() const noexcept - { - return m_infrast_cfg_unique_ins; - } - const UserConfiger& user() const noexcept - { - return m_user_cfg_unique_ins; - } - const OcrPack& ocr() const noexcept - { - return m_ocr_pack_unique_ins; - } - const PenguinPack& penguin() const noexcept - { - return m_penguin_pack_unique_ins; - } - - Resource& operator=(const Resource&) = delete; - Resource& operator=(Resource&&) noexcept = delete; - - private: - Resource() = default; - - TemplResource m_templ_resource_unique_ins; - GeneralConfiger m_general_cfg_unique_ins; - RecruitConfiger m_recruit_cfg_unique_ins; - ItemConfiger m_item_cfg_unique_ins; - InfrastConfiger m_infrast_cfg_unique_ins; - UserConfiger m_user_cfg_unique_ins; - OcrPack m_ocr_pack_unique_ins; - PenguinPack m_penguin_pack_unique_ins; - }; - - //static auto& resource = Resource::get_instance(); -#define Resrc Resource::get_instance() -} +#pragma once + +#include "AbstractResource.h" + +#include +#include + +#include "GeneralConfiger.h" +#include "InfrastConfiger.h" +#include "ItemConfiger.h" +#include "OcrPack.h" +#include "PenguinPack.h" +#include "RecruitConfiger.h" +#include "TaskData.h" +#include "TemplResource.h" +#include "UserConfiger.h" + +namespace asst +{ + class Resource : public AbstractResource + { + public: + virtual ~Resource() = default; + + static Resource& get_instance() + { + static Resource unique_instance; + return unique_instance; + } + + virtual bool load(const std::string& dir) override; + + TemplResource& templ() noexcept + { + return m_templ_resource_unique_ins; + } + GeneralConfiger& cfg() noexcept + { + return m_general_cfg_unique_ins; + } + RecruitConfiger& recruit() noexcept + { + return m_recruit_cfg_unique_ins; + } + ItemConfiger& item() noexcept + { + return m_item_cfg_unique_ins; + } + InfrastConfiger& infrast() noexcept + { + return m_infrast_cfg_unique_ins; + } + UserConfiger& user() noexcept + { + return m_user_cfg_unique_ins; + } + OcrPack& ocr() noexcept + { + return m_ocr_pack_unique_ins; + } + PenguinPack& penguin() noexcept + { + return m_penguin_pack_unique_ins; + } + + const TemplResource& templ() const noexcept + { + return m_templ_resource_unique_ins; + } + const GeneralConfiger& cfg() const noexcept + { + return m_general_cfg_unique_ins; + } + const RecruitConfiger& recruit() const noexcept + { + return m_recruit_cfg_unique_ins; + } + const ItemConfiger& item() const noexcept + { + return m_item_cfg_unique_ins; + } + const InfrastConfiger& infrast() const noexcept + { + return m_infrast_cfg_unique_ins; + } + const UserConfiger& user() const noexcept + { + return m_user_cfg_unique_ins; + } + const OcrPack& ocr() const noexcept + { + return m_ocr_pack_unique_ins; + } + const PenguinPack& penguin() const noexcept + { + return m_penguin_pack_unique_ins; + } + + Resource& operator=(const Resource&) = delete; + Resource& operator=(Resource&&) noexcept = delete; + + private: + Resource() = default; + + TemplResource m_templ_resource_unique_ins; + GeneralConfiger m_general_cfg_unique_ins; + RecruitConfiger m_recruit_cfg_unique_ins; + ItemConfiger m_item_cfg_unique_ins; + InfrastConfiger m_infrast_cfg_unique_ins; + UserConfiger m_user_cfg_unique_ins; + OcrPack m_ocr_pack_unique_ins; + PenguinPack m_penguin_pack_unique_ins; + }; + + //static auto& resource = Resource::get_instance(); +#define Resrc Resource::get_instance() +} diff --git a/src/MeoAssistant/TemplResource.cpp b/src/MeoAssistant/TemplResource.cpp index 482d5afa3b..4d1c841600 100644 --- a/src/MeoAssistant/TemplResource.cpp +++ b/src/MeoAssistant/TemplResource.cpp @@ -1,51 +1,51 @@ -#include "TemplResource.h" - -#include -#include -#include - -void asst::TemplResource::append_load_required(std::unordered_set required) noexcept -{ - m_templs_filename.insert( - std::make_move_iterator(required.begin()), - std::make_move_iterator(required.end())); -} - -bool asst::TemplResource::load(const std::string& dir) -{ - for (const std::string& filename : m_templs_filename) { - std::string filepath = dir + "\\" + filename; - if (std::filesystem::exists(filepath)) { - cv::Mat templ = cv::imread(filepath); - emplace_templ(filename, std::move(templ)); - } - else { - m_last_error = filepath + " not exists"; - return false; - } - } - - return true; -} - -bool asst::TemplResource::exist_templ(const std::string& key) const noexcept -{ - return m_templs.find(key) != m_templs.cend(); -} - -const cv::Mat& asst::TemplResource::get_templ(const std::string& key) const noexcept -{ - if (auto iter = m_templs.find(key); - iter != m_templs.cend()) { - return iter->second; - } - else { - const static cv::Mat empty; - return empty; - } -} - -void asst::TemplResource::emplace_templ(std::string key, cv::Mat templ) -{ - m_templs.emplace(std::move(key), std::move(templ)); -} +#include "TemplResource.h" + +#include +#include +#include + +void asst::TemplResource::append_load_required(std::unordered_set required) noexcept +{ + m_templs_filename.insert( + std::make_move_iterator(required.begin()), + std::make_move_iterator(required.end())); +} + +bool asst::TemplResource::load(const std::string& dir) +{ + for (const std::string& filename : m_templs_filename) { + std::string filepath = dir + "/" + filename; + if (std::filesystem::exists(filepath)) { + cv::Mat templ = cv::imread(filepath); + emplace_templ(filename, std::move(templ)); + } + else { + m_last_error = filepath + " not exists"; + return false; + } + } + + return true; +} + +bool asst::TemplResource::exist_templ(const std::string& key) const noexcept +{ + return m_templs.find(key) != m_templs.cend(); +} + +const cv::Mat& asst::TemplResource::get_templ(const std::string& key) const noexcept +{ + if (auto iter = m_templs.find(key); + iter != m_templs.cend()) { + return iter->second; + } + else { + const static cv::Mat empty; + return empty; + } +} + +void asst::TemplResource::emplace_templ(std::string key, cv::Mat templ) +{ + m_templs.emplace(std::move(key), std::move(templ)); +} diff --git a/src/Python/interface.py b/src/Python/interface.py index 7d10eb4b94..763fdcd482 100644 --- a/src/Python/interface.py +++ b/src/Python/interface.py @@ -2,6 +2,8 @@ import ctypes import os import json import pathlib +import platform + from message import Message @@ -24,16 +26,20 @@ class Asst: ``callback``: 回调函数 ``arg``: 自定义参数 """ - self.__dllpath = pathlib.Path(dirname) / 'MeoAssistant.dll' - self.__dll = ctypes.WinDLL(str(self.__dllpath)) + if platform.system().lower() == 'windows': + self.__libpath = pathlib.Path(dirname) / 'MeoAssistant.dll' + self.__lib = ctypes.WinDLL(str(self.__libpath)) + else: + self.__libpath = pathlib.Path(dirname) / 'libMeoAssistant.so' + self.__lib = ctypes.CDLL(str(self.__libpath)) - self.__dll.AsstCreateEx.restype = ctypes.c_void_p - self.__dll.AsstCreateEx.argtypes = (ctypes.c_char_p, ctypes.c_void_p, ctypes.c_void_p,) - self.__ptr = self.__dll.AsstCreateEx(dirname.encode('utf-8'), callback, arg) + self.__lib.AsstCreateEx.restype = ctypes.c_void_p + self.__lib.AsstCreateEx.argtypes = (ctypes.c_char_p, ctypes.c_void_p, ctypes.c_void_p,) + self.__ptr = self.__lib.AsstCreateEx(dirname.encode('utf-8'), callback, arg) def __del__(self): - self.__dll.AsstDestroy.argtypes = (ctypes.c_void_p,) - self.__dll.AsstDestroy(self.__ptr) + self.__lib.AsstDestroy.argtypes = (ctypes.c_void_p,) + self.__lib.AsstDestroy(self.__ptr) def catch_default(self) -> bool: """ @@ -41,8 +47,8 @@ class Asst: :return: 是否连接成功 """ - self.__dll.AsstCatchDefault.argtypes = (ctypes.c_void_p,) - return self.__dll.AsstCatchDefault(self.__ptr) + self.__lib.AsstCatchDefault.argtypes = (ctypes.c_void_p,) + return self.__lib.AsstCatchDefault(self.__ptr) def catch_emulator(self) -> bool: """ @@ -50,8 +56,8 @@ class Asst: :return: 是否连接成功 """ - self.__dll.AsstCatchEmulator.argtypes = (ctypes.c_void_p,) - return self.__dll.AsstCatchEmulator(self.__ptr) + self.__lib.AsstCatchEmulator.argtypes = (ctypes.c_void_p,) + return self.__lib.AsstCatchEmulator(self.__ptr) def catch_custom(self, address: str) -> bool: """ @@ -59,9 +65,9 @@ class Asst: :return: 是否连接成功 """ - self.__dll.AsstCatchCustom.argtypes = ( + self.__lib.AsstCatchCustom.argtypes = ( ctypes.c_void_p, ctypes.c_char_p,) - return self.__dll.AsstCatchCustom(self.__ptr, address.encode('utf-8')) + return self.__lib.AsstCatchCustom(self.__ptr, address.encode('utf-8')) def append_fight(self, stage: str, max_medicine: int, max_stone: int, max_times: int) -> bool: """ @@ -74,23 +80,23 @@ class Asst: ``max_stone``: 最多吃多少源石 ``max_times``: 最多刷多少 """ - self.__dll.AsstAppendFight.argtypes = ( + self.__lib.AsstAppendFight.argtypes = ( ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int, ctypes.c_int,) - return self.__dll.AsstAppendFight(self.__ptr, stage.encode('utf-8'), max_medicine, max_stone, max_times) + return self.__lib.AsstAppendFight(self.__ptr, stage.encode('utf-8'), max_medicine, max_stone, max_times) def append_award(self) -> bool: """ 添加领取每日奖励任务 """ - self.__dll.AsstAppendAward.argtypes = (ctypes.c_void_p,) - return self.__dll.AsstAppendAward(self.__ptr) + self.__lib.AsstAppendAward.argtypes = (ctypes.c_void_p,) + return self.__lib.AsstAppendAward(self.__ptr) def append_visit(self) -> bool: """ 添加访问好友任务 """ - self.__dll.AsstAppendVisit.argtypes = (ctypes.c_void_p,) - return self.__dll.AsstAppendVisit(self.__ptr) + self.__lib.AsstAppendVisit.argtypes = (ctypes.c_void_p,) + return self.__lib.AsstAppendVisit(self.__ptr) def append_mall(self, with_shopping: bool) -> bool: """ @@ -99,8 +105,8 @@ class Asst: :params: ``with_shopping``: 是否信用商店购物 """ - self.__dll.AsstAppendMall.argtypes = (ctypes.c_void_p, ctypes.c_bool) - return self.__dll.AsstAppendMall(self.__ptr, with_shopping) + self.__lib.AsstAppendMall.argtypes = (ctypes.c_void_p, ctypes.c_bool) + return self.__lib.AsstAppendMall(self.__ptr, with_shopping) def append_infrast(self, work_mode: int, order_list: list, uses_of_drones: str, dorm_threshold: float) -> bool: """ @@ -121,11 +127,11 @@ class Asst: order_arr = (ctypes.c_char_p * order_len)() order_arr[:] = order_byte_list - self.__dll.AsstAppendInfrast.argtypes = ( + self.__lib.AsstAppendInfrast.argtypes = ( ctypes.c_void_p, ctypes.c_int, ctypes.POINTER(ctypes.c_char_p), ctypes.c_int, ctypes.c_char_p, ctypes.c_double,) - return self.__dll.AsstAppendInfrast(self.__ptr, work_mode, + return self.__lib.AsstAppendInfrast(self.__ptr, work_mode, order_arr, order_len, uses_of_drones.encode('utf-8'), dorm_threshold) @@ -147,11 +153,11 @@ class Asst: confirm_arr = (ctypes.c_int * confirm_len)() confirm_arr[:] = confirm_level - self.__dll.AsstAppendRecruit.argtypes = ( + self.__lib.AsstAppendRecruit.argtypes = ( ctypes.c_void_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_bool,) - return self.__dll.AsstAppendRecruit(self.__ptr, max_times, + return self.__lib.AsstAppendRecruit(self.__ptr, max_times, select_arr, select_len, confirm_arr, confirm_len, need_refresh) @@ -159,8 +165,8 @@ class Asst: """ 开始任务 """ - self.__dll.AsstStart.argtypes = (ctypes.c_void_p,) - return self.__dll.AsstStart(self.__ptr) + self.__lib.AsstStart.argtypes = (ctypes.c_void_p,) + return self.__lib.AsstStart(self.__ptr) def start_recurit_calc(self, select_level: list, set_time: bool) -> bool: """ @@ -174,16 +180,16 @@ class Asst: select_arr = (ctypes.c_int * select_len)() select_arr[:] = select_level - self.__dll.AsstStartRecruitCalc.argtypes = ( + self.__lib.AsstStartRecruitCalc.argtypes = ( ctypes.c_void_p, ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_bool,) - return self.__dll.AsstStartRecruitCalc(self.__ptr, select_arr, select_len, set_time) + return self.__lib.AsstStartRecruitCalc(self.__ptr, select_arr, select_len, set_time) def stop(self) -> bool: """ 停止并清空所有任务 """ - self.__dll.AsstStop.argtypes = (ctypes.c_void_p,) - return self.__dll.AsstStop(self.__ptr) + self.__lib.AsstStop.argtypes = (ctypes.c_void_p,) + return self.__lib.AsstStop(self.__ptr) def set_penguin_id(self, id: str) -> bool: """ @@ -192,9 +198,9 @@ class Asst: :params: ``id``: 企鹅物流ID,仅数字部分 """ - self.__dll.AsstSetPenguinId.argtypes = ( + self.__lib.AsstSetPenguinId.argtypes = ( ctypes.c_void_p, ctypes.c_char_p) - return self.__dll.AsstSetPenguinId(self.__ptr, id.encode('utf-8')) + return self.__lib.AsstSetPenguinId(self.__ptr, id.encode('utf-8')) def get_version(self) -> str: """ @@ -202,8 +208,8 @@ class Asst: :return: 版本号 """ - self.__dll.AsstGetVersion.restype = ctypes.c_char_p - return self.__dll.AsstGetVersion().decode('utf-8') + self.__lib.AsstGetVersion.restype = ctypes.c_char_p + return self.__lib.AsstGetVersion().decode('utf-8') if __name__ == "__main__": @@ -213,16 +219,20 @@ if __name__ == "__main__": d = json.loads(details.decode('gbk')) print(Message(msg), d, arg) - dirname: str = (pathlib.Path.cwd().parent.parent / 'x64' / 'Release').__str__() + if platform.system().lower() == 'windows': + dirname: str = (pathlib.Path.cwd().parent.parent / 'x64' / 'Release').__str__() + else: + dirname: str = (pathlib.Path.cwd().parent.parent / 'build').__str__() + asst = Asst(dirname=dirname, callback=my_callback) print('version', asst.get_version()) - if asst.catch_default(): + if asst.catch_custom('127.0.0.1:5555'): print('连接成功') else: print('连接失败') - os.system.exit() + exit() asst.append_fight("CE-5", 0, 0, 1) # asst.append_infrast(1, [ "Mfg", "Trade", "Control", "Power", "Reception", "Office", "Dorm"], "Money", 0.3) diff --git a/src/Python/message.py b/src/Python/message.py index d4d05a50ec..45344c4f1d 100644 --- a/src/Python/message.py +++ b/src/Python/message.py @@ -10,7 +10,7 @@ class Message(Enum): 注释部分仅展示常用消息的回调JSON格式,完全的回调JSON请参考C++侧实现代码 """ - PtrIsNull = auto() + PtrIsNull = 0 """ 指针为空 """ diff --git a/tools/TestCaller/main.cpp b/tools/TestCaller/main.cpp index 44ec4e1624..d23c9583b9 100644 --- a/tools/TestCaller/main.cpp +++ b/tools/TestCaller/main.cpp @@ -2,24 +2,23 @@ #include #include - -#include +#include +#include std::string get_cur_dir() { - char exepath_buff[_MAX_PATH] = { 0 }; - ::GetModuleFileNameA(NULL, exepath_buff, _MAX_PATH); - std::string exepath(exepath_buff); - std::string cur_dir = exepath.substr(0, exepath.find_last_of('\\') + 1); - return cur_dir; + return std::filesystem::current_path().u8string(); } int main(int argc, char** argv) { auto ptr = AsstCreate(get_cur_dir().c_str()); - auto ret = AsstCatchEmulator(ptr); + if (ptr == nullptr) { + return -1; + } + auto ret = AsstCatchCustom(ptr, "127.0.0.1:5555"); if (!ret) { - getchar(); + std::cout << "connect failed" << std::endl; if (ptr) { AsstDestroy(ptr); ptr = nullptr; @@ -29,24 +28,24 @@ int main(int argc, char** argv) char ch = 0; while (ch != 'q') { - //AsstAppendStartUp(ptr); + // AsstAppendStartUp(ptr); AsstAppendFight(ptr, "CE-5", 0, 0, 99999); - //AsstAppendVisit(ptr, true); + // AsstAppendVisit(ptr, true); //{ - // const int required[] = { 3, 4, 5, 6 }; - // AsstStartRecruitCalc(ptr, required, sizeof(required) / sizeof(int), true); - //} - //AsstAppendDebug(ptr); + // const int required[] = { 3, 4, 5, 6 }; + // AsstStartRecruitCalc(ptr, required, sizeof(required) / sizeof(int), true); + // } + // AsstAppendDebug(ptr); //{ - // const char* order[] = { "Trade", "Mfg", "Dorm" }; - // AsstAppendInfrast(ptr, 1, order, 3, 0, 0); - //} - //AsstAppendProcessTask(ptr, "AwardBegin"); + // const char* order[] = { "Trade", "Mfg", "Dorm" }; + // AsstAppendInfrast(ptr, 1, order, 3, 0, 0); + // } + // AsstAppendProcessTask(ptr, "AwardBegin"); //{ - // const int required[] = { 4 }; - // const int confirm[] = { 3, 4 }; - // AsstAppendRecruit(ptr, 2, required, sizeof(required) / sizeof(int), confirm, sizeof(confirm) / sizeof(int)); - //} + // const int required[] = { 4 }; + // const int confirm[] = { 3, 4 }; + // AsstAppendRecruit(ptr, 2, required, sizeof(required) / sizeof(int), confirm, sizeof(confirm) / sizeof(int)); + // } AsstStart(ptr); ch = getchar(); diff --git a/tools/update_resource.sh b/tools/update_resource.sh new file mode 100644 index 0000000000..1ae0edd08b --- /dev/null +++ b/tools/update_resource.sh @@ -0,0 +1,3 @@ +mkdir -p ../build/Resource +cp -r ../resource/* ../build/Resource +cp -r ../3rdparty/resource/* ../build/Resource \ No newline at end of file