diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dddbfe1798..52b8685b64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,7 @@ jobs: with: path: temp show-progress: false + submodules: recursive - name: Fetch history if: ${{ !startsWith(github.ref, 'refs/pull/') }} @@ -106,6 +107,7 @@ jobs: uses: actions/checkout@v4 with: show-progress: false + submodules: recursive - name: Cache .nuke/temp, ~/.nuget/packages id: cache-nuget @@ -300,6 +302,7 @@ jobs: uses: actions/checkout@v4 with: show-progress: false + submodules: recursive - name: Install Dependencies run: | diff --git a/.github/workflows/smoke-testing.yml b/.github/workflows/smoke-testing.yml index b81baee941..d2ae270921 100644 --- a/.github/workflows/smoke-testing.yml +++ b/.github/workflows/smoke-testing.yml @@ -33,6 +33,7 @@ jobs: uses: actions/checkout@v4 with: show-progress: false + submodules: recursive - name: Restore dependencies run: dotnet restore diff --git a/.gitmodules b/.gitmodules index 4d6c21baa7..cfaa44cf3f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ [submodule "src/maa-cli"] path = src/maa-cli url = https://github.com/MaaAssistantArknights/maa-cli.git +[submodule "3rdparty/EmulatorExtras"] + path = 3rdparty/EmulatorExtras + url = https://github.com/MaaXYZ/EmulatorExtras.git diff --git a/3rdparty/EmulatorExtras b/3rdparty/EmulatorExtras new file mode 160000 index 0000000000..42442b1183 --- /dev/null +++ b/3rdparty/EmulatorExtras @@ -0,0 +1 @@ +Subproject commit 42442b11835844ee1ff01371e6537d0291ca9505 diff --git a/CMakeLists.txt b/CMakeLists.txt index d7de4a3c8c..dba1520021 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ endif () add_library(header_only_libraries INTERFACE) -target_include_directories(header_only_libraries INTERFACE 3rdparty/include) +target_include_directories(header_only_libraries INTERFACE 3rdparty/include 3rdparty/EmulatorExtras) file(GLOB_RECURSE maa_src src/MaaCore/*.cpp) diff --git a/include/AsstCaller.h b/include/AsstCaller.h index ee4921713a..695f737312 100644 --- a/include/AsstCaller.h +++ b/include/AsstCaller.h @@ -32,12 +32,16 @@ extern "C" AsstHandle ASSTAPI AsstCreateEx(AsstApiCallback callback, void* custom_arg); void ASSTAPI AsstDestroy(AsstHandle handle); - AsstBool ASSTAPI AsstSetInstanceOption(AsstHandle handle, AsstInstanceOptionKey key, const char* value); + AsstBool ASSTAPI + AsstSetInstanceOption(AsstHandle handle, AsstInstanceOptionKey key, const char* value); // 同步连接,功能已完全被异步连接取代 // FIXME: 5.0 版本将废弃此接口 - /* deprecated */ AsstBool ASSTAPI AsstConnect(AsstHandle handle, const char* adb_path, const char* address, - const char* config); + /* deprecated */ AsstBool ASSTAPI AsstConnect( + AsstHandle handle, + const char* adb_path, + const char* address, + const char* config); AsstTaskId ASSTAPI AsstAppendTask(AsstHandle handle, const char* type, const char* params); AsstBool ASSTAPI AsstSetTaskParams(AsstHandle handle, AsstTaskId id, const char* params); @@ -49,8 +53,14 @@ extern "C" AsstBool ASSTAPI AsstBackToHome(AsstHandle handle); /* Async with AsstMsg::AsyncCallInfo Callback*/ - AsstAsyncCallId ASSTAPI AsstAsyncConnect(AsstHandle handle, const char* adb_path, const char* address, - const char* config, AsstBool block); + AsstAsyncCallId ASSTAPI AsstAsyncConnect( + AsstHandle handle, + const char* adb_path, + const char* address, + const char* config, + AsstBool block); + void ASSTAPI AsstSetConnectionExtras(const char* name, const char* extras); + AsstAsyncCallId ASSTAPI AsstAsyncClick(AsstHandle handle, int32_t x, int32_t y, AsstBool block); AsstAsyncCallId ASSTAPI AsstAsyncScreencap(AsstHandle handle, AsstBool block); diff --git a/src/MaaCore/AsstCaller.cpp b/src/MaaCore/AsstCaller.cpp index 9d85818237..ce27b2ae90 100644 --- a/src/MaaCore/AsstCaller.cpp +++ b/src/MaaCore/AsstCaller.cpp @@ -70,7 +70,9 @@ AsstBool AsstLoadResource(const char* path) AsstBool AsstSetStaticOption(AsstStaticOptionKey key, const char* value) { - return AsstExtAPI::set_static_option(static_cast(key), value) ? AsstTrue : AsstFalse; + return AsstExtAPI::set_static_option(static_cast(key), value) + ? AsstTrue + : AsstFalse; } AsstHandle AsstCreate() @@ -105,17 +107,25 @@ AsstBool AsstSetInstanceOption(AsstHandle handle, AsstInstanceOptionKey key, con return AsstFalse; } - return handle->set_instance_option(static_cast(key), value) ? AsstTrue : AsstFalse; + return handle->set_instance_option(static_cast(key), value) + ? AsstTrue + : AsstFalse; } -AsstBool AsstConnect(AsstHandle handle, const char* adb_path, const char* address, const char* config) +AsstBool + AsstConnect(AsstHandle handle, const char* adb_path, const char* address, const char* config) { if (!inited() || handle == nullptr) { - asst::Log.error(__FUNCTION__, "Cannot connect to device, asst not inited or handle is null", inited(), handle); + Log.error( + __FUNCTION__, + "Cannot connect to device, asst not inited or handle is null", + inited(), + handle); return AsstFalse; } - return handle->connect(adb_path, address, config ? config : std::string()) ? AsstTrue : AsstFalse; + return handle->connect(adb_path, address, config ? config : std::string()) ? AsstTrue + : AsstFalse; } AsstBool AsstStart(AsstHandle handle) @@ -163,8 +173,12 @@ AsstBool ASSTAPI AsstBackToHome(AsstHandle handle) return handle->back_to_home() ? AsstTrue : AsstFalse; } -AsstAsyncCallId AsstAsyncConnect(AsstHandle handle, const char* adb_path, const char* address, const char* config, - AsstBool block) +AsstAsyncCallId AsstAsyncConnect( + AsstHandle handle, + const char* adb_path, + const char* address, + const char* config, + AsstBool block) { if (!inited() || handle == nullptr) { return InvalidId; @@ -172,6 +186,17 @@ AsstAsyncCallId AsstAsyncConnect(AsstHandle handle, const char* adb_path, const return handle->async_connect(adb_path, address, config ? config : std::string(), block); } +void AsstSetConnectionExtras(const char* name, const char* extras) +{ + auto jopt = json::parse(extras); + if (!jopt) { + LogError << "failed to parse json" << extras; + return; + } + + asst::ResourceLoader::get_instance().set_connection_extras(name, jopt->as_object()); +} + AsstTaskId AsstAppendTask(AsstHandle handle, const char* type, const char* params) { if (!inited() || handle == nullptr) { @@ -264,5 +289,5 @@ void AsstLog(const char* level, const char* message) std::cerr << __FUNCTION__ << " | User Dir not set" << std::endl; return; } - asst::Log.log(asst::Logger::level(level), message); + Log.log(asst::Logger::level(level), message); } diff --git a/src/MaaCore/Config/AbstractResource.h b/src/MaaCore/Config/AbstractResource.h index c6f4cb09f2..16c1425e9e 100644 --- a/src/MaaCore/Config/AbstractResource.h +++ b/src/MaaCore/Config/AbstractResource.h @@ -1,25 +1,26 @@ #pragma once #include +#include #include "Utils/SingletonHolder.hpp" namespace asst { - class AbstractResource - { - public: - virtual ~AbstractResource() = default; - virtual bool load(const std::filesystem::path& path) = 0; +class AbstractResource +{ +public: + virtual ~AbstractResource() = default; + virtual bool load(const std::filesystem::path& path) = 0; - public: - AbstractResource(const AbstractResource& rhs) = delete; - AbstractResource(AbstractResource&& rhs) noexcept = delete; +public: + AbstractResource(const AbstractResource& rhs) = delete; + AbstractResource(AbstractResource&& rhs) noexcept = delete; - AbstractResource& operator=(const AbstractResource& rhs) = delete; - AbstractResource& operator=(AbstractResource&& rhs) noexcept = delete; + AbstractResource& operator=(const AbstractResource& rhs) = delete; + AbstractResource& operator=(AbstractResource&& rhs) noexcept = delete; - protected: - AbstractResource() = default; - }; +protected: + AbstractResource() = default; +}; } diff --git a/src/MaaCore/Config/GeneralConfig.cpp b/src/MaaCore/Config/GeneralConfig.cpp index 03f61581be..8b4be80b1e 100644 --- a/src/MaaCore/Config/GeneralConfig.cpp +++ b/src/MaaCore/Config/GeneralConfig.cpp @@ -3,6 +3,13 @@ #include "Utils/Logger.hpp" #include +void asst::GeneralConfig::set_connection_extras(const std::string& name, const json::object& extras) +{ + LogInfo << name << extras; + + m_adb_cfg[name].extras = extras; +} + bool asst::GeneralConfig::parse(const json::value& json) { LogTraceFunction; @@ -17,13 +24,19 @@ bool asst::GeneralConfig::parse(const json::value& json) // 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); - m_options.adb_swipe_duration_multiplier = options_json.get("adbSwipeDurationMultiplier", 10.0); - m_options.adb_swipe_x_distance_multiplier = options_json.get("adbSwipeXDistanceMultiplier", 0.8); + m_options.adb_swipe_duration_multiplier = + options_json.get("adbSwipeDurationMultiplier", 10.0); + m_options.adb_swipe_x_distance_multiplier = + options_json.get("adbSwipeXDistanceMultiplier", 0.8); m_options.minitouch_extra_swipe_dist = options_json.get("minitouchExtraSwipeDist", 100); - m_options.minitouch_extra_swipe_duration = options_json.get("minitouchExtraSwipeDuration", -1); - m_options.minitouch_swipe_default_duration = options_json.get("minitouchSwipeDefaultDuration", 200); - m_options.minitouch_swipe_extra_end_delay = options_json.get("minitouchSwipeExtraEndDelay", 150); - m_options.swipe_with_pause_required_distance = options_json.get("swipeWithPauseRequiredDistance", 50); + m_options.minitouch_extra_swipe_duration = + options_json.get("minitouchExtraSwipeDuration", -1); + m_options.minitouch_swipe_default_duration = + options_json.get("minitouchSwipeDefaultDuration", 200); + m_options.minitouch_swipe_extra_end_delay = + options_json.get("minitouchSwipeExtraEndDelay", 150); + m_options.swipe_with_pause_required_distance = + options_json.get("swipeWithPauseRequiredDistance", 50); if (auto order = options_json.find("minitouchProgramsOrder")) { m_options.minitouch_programs_order.clear(); for (const auto& type : *order) { @@ -66,7 +79,7 @@ bool asst::GeneralConfig::parse(const json::value& json) std::string base_name = cfg_json.get("baseConfig", std::string()); const AdbCfg& base_cfg = base_name.empty() ? AdbCfg() : m_adb_cfg.at(base_name); - AdbCfg adb; + AdbCfg& adb = m_adb_cfg[cfg_json.at("configName").as_string()]; adb.connect = cfg_json.get("connect", base_cfg.connect); adb.display_id = cfg_json.get("displayId", base_cfg.display_id); adb.uuid = cfg_json.get("uuid", base_cfg.uuid); @@ -74,7 +87,8 @@ bool asst::GeneralConfig::parse(const json::value& json) adb.swipe = cfg_json.get("swipe", base_cfg.swipe); adb.press_esc = cfg_json.get("pressEsc", base_cfg.press_esc); adb.display = cfg_json.get("display", base_cfg.display); - adb.screencap_raw_with_gzip = cfg_json.get("screencapRawWithGzip", base_cfg.screencap_raw_with_gzip); + adb.screencap_raw_with_gzip = + cfg_json.get("screencapRawWithGzip", base_cfg.screencap_raw_with_gzip); adb.screencap_raw_by_nc = cfg_json.get("screencapRawByNC", base_cfg.screencap_raw_by_nc); adb.nc_address = cfg_json.get("ncAddress", base_cfg.nc_address); adb.screencap_encode = cfg_json.get("screencapEncode", base_cfg.screencap_encode); @@ -88,8 +102,6 @@ bool asst::GeneralConfig::parse(const json::value& json) adb.call_minitouch = cfg_json.get("callMinitouch", base_cfg.call_minitouch); adb.call_maatouch = cfg_json.get("callMaatouch", base_cfg.call_maatouch); adb.back_to_home = cfg_json.get("back_to_home", base_cfg.back_to_home); - - m_adb_cfg[cfg_json.at("configName").as_string()] = std::move(adb); } return true; diff --git a/src/MaaCore/Config/GeneralConfig.h b/src/MaaCore/Config/GeneralConfig.h index e622a5bbdd..f04941d69a 100644 --- a/src/MaaCore/Config/GeneralConfig.h +++ b/src/MaaCore/Config/GeneralConfig.h @@ -11,116 +11,125 @@ namespace asst { - struct RequestInfo - { - std::string url; // 上传地址 - std::string drop_url; - std::string recruit_url; - std::unordered_map headers; // 请求头 - int timeout = 0; // 超时时间 - }; +struct RequestInfo +{ + std::string url; // 上传地址 + std::string drop_url; + std::string recruit_url; + std::unordered_map headers; // 请求头 + int timeout = 0; // 超时时间 +}; - struct DepotExportTemplate - { - std::string ark_planner; - }; +struct DepotExportTemplate +{ + std::string ark_planner; +}; - struct DebugConf - { - int clean_files_freq = 100; - int max_debug_file_num = 1000; - }; +struct DebugConf +{ + int clean_files_freq = 100; + int max_debug_file_num = 1000; +}; - struct Options - { - 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,则关闭额外滑动功能。 - double adb_swipe_duration_multiplier = 0; // adb 滑动持续时间倍数 - double adb_swipe_x_distance_multiplier = 0; // adb 滑动距离倍数 - int minitouch_extra_swipe_dist = 0; - int minitouch_extra_swipe_duration = -1; - int minitouch_swipe_default_duration = 0; - int minitouch_swipe_extra_end_delay = 0; - int swipe_with_pause_required_distance = 0; - std::vector minitouch_programs_order; - RequestInfo penguin_report; // 企鹅物流汇报: - // 每次到结算界面,汇报掉落数据至企鹅物流 https://penguin-stats.cn/ - DepotExportTemplate depot_export_template; // 仓库识别结果导出模板 - RequestInfo yituliu_report; // 一图流大数据汇报:目前只有公招功能,https://ark.yituliu.cn/survey/maarecruitdata - DebugConf debug; - }; +struct Options +{ + 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,则关闭额外滑动功能。 + double adb_swipe_duration_multiplier = 0; // adb 滑动持续时间倍数 + double adb_swipe_x_distance_multiplier = 0; // adb 滑动距离倍数 + int minitouch_extra_swipe_dist = 0; + int minitouch_extra_swipe_duration = -1; + int minitouch_swipe_default_duration = 0; + int minitouch_swipe_extra_end_delay = 0; + int swipe_with_pause_required_distance = 0; + std::vector minitouch_programs_order; + RequestInfo penguin_report; // 企鹅物流汇报: + // 每次到结算界面,汇报掉落数据至企鹅物流 https://penguin-stats.cn/ + DepotExportTemplate depot_export_template; // 仓库识别结果导出模板 + RequestInfo + yituliu_report; // 一图流大数据汇报:目前只有公招功能,https://ark.yituliu.cn/survey/maarecruitdata + DebugConf debug; +}; - struct AdbCfg - { - /* command */ - std::string connect; - std::string display_id; - std::string uuid; - std::string click; - std::string swipe; - std::string press_esc; - std::string display; - std::string screencap_raw_with_gzip; - std::string screencap_raw_by_nc; - std::string nc_address; - std::string screencap_encode; - std::string release; - std::string start; - std::string stop; - std::string abilist; - std::string orientation; - std::string push_minitouch; - std::string chmod_minitouch; - std::string call_minitouch; - std::string call_maatouch; - std::string back_to_home; - }; +struct AdbCfg +{ + /* command */ + std::string connect; + std::string display_id; + std::string uuid; + std::string click; + std::string swipe; + std::string press_esc; + std::string display; + std::string screencap_raw_with_gzip; + std::string screencap_raw_by_nc; + std::string nc_address; + std::string screencap_encode; + std::string release; + std::string start; + std::string stop; + std::string abilist; + std::string orientation; + std::string push_minitouch; + std::string chmod_minitouch; + std::string call_minitouch; + std::string call_maatouch; + std::string back_to_home; + json::object extras; +}; - class GeneralConfig final : public SingletonHolder, public AbstractConfig - { - public: - virtual ~GeneralConfig() override = default; +class GeneralConfig final + : public SingletonHolder + , public AbstractConfig +{ +public: + virtual ~GeneralConfig() override = 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; } - std::optional get_adb_cfg(const std::string& name) const - { - if (auto iter = m_adb_cfg.find(name); iter != m_adb_cfg.cend()) { - return iter->second; - } - else { - return std::nullopt; - } + void set_connection_extras(const std::string& name, const json::object& diff); + + 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; } + + std::optional get_adb_cfg(const std::string& name) const + { + if (auto iter = m_adb_cfg.find(name); iter != m_adb_cfg.cend()) { + return iter->second; } - - std::optional get_intent_name(const std::string& client_type) const - { - if (auto iter = m_intent_name.find(client_type); iter != m_intent_name.cend()) { - return iter->second; - } + else { return std::nullopt; } + } - void set_options(Options opt) noexcept { m_options = std::move(opt); } + std::optional get_intent_name(const std::string& client_type) const + { + if (auto iter = m_intent_name.find(client_type); iter != m_intent_name.cend()) { + return iter->second; + } + return std::nullopt; + } - protected: - virtual bool parse(const json::value& json) override; + void set_options(Options opt) noexcept { m_options = std::move(opt); } - std::string m_version; - Options m_options; - std::unordered_map m_adb_cfg; - std::unordered_map m_intent_name; - }; +protected: + virtual bool parse(const json::value& json) override; - inline static auto& Config = GeneralConfig::get_instance(); + std::string m_version; + Options m_options; + std::unordered_map m_adb_cfg; + std::unordered_map m_intent_name; +}; + +inline static auto& Config = GeneralConfig::get_instance(); } // namespace asst diff --git a/src/MaaCore/Config/ResourceLoader.cpp b/src/MaaCore/Config/ResourceLoader.cpp index c1e39ff230..54eb915c18 100644 --- a/src/MaaCore/Config/ResourceLoader.cpp +++ b/src/MaaCore/Config/ResourceLoader.cpp @@ -160,7 +160,8 @@ bool asst::ResourceLoader::load(const std::filesystem::path& path) LoadResourceAndCheckRet(StageDropsConfig, "stages.json"_p); LoadResourceAndCheckRet(TilePack, "Arknights-Tile-Pos"_p / "overview.json"_p); - // fix #6188 https://github.com/MaaAssistantArknights/MaaAssistantArknights/issues/6188#issuecomment-1703705568 + // fix #6188 + // https://github.com/MaaAssistantArknights/MaaAssistantArknights/issues/6188#issuecomment-1703705568 // 没什么头绪,但凑合修掉了 // 原来这后面是用 AsyncLoadConfig 的,以下是原来的注释: //// 不太重要又加载的慢的资源,但不怎么占内存的,实时异步加载 @@ -169,23 +170,46 @@ bool asst::ResourceLoader::load(const std::filesystem::path& path) LoadResourceAndCheckRet(RoguelikeCopilotConfig, "roguelike"_p / "Mizuki"_p / "autopilot"_p); LoadResourceAndCheckRet(RoguelikeCopilotConfig, "roguelike"_p / "Sami"_p / "autopilot"_p); - LoadResourceAndCheckRet(RoguelikeRecruitConfig, "roguelike"_p / "Phantom"_p / "recruitment.json"_p); - LoadResourceAndCheckRet(RoguelikeRecruitConfig, "roguelike"_p / "Mizuki"_p / "recruitment.json"_p); - LoadResourceAndCheckRet(RoguelikeRecruitConfig, "roguelike"_p / "Sami"_p / "recruitment.json"_p); + LoadResourceAndCheckRet( + RoguelikeRecruitConfig, + "roguelike"_p / "Phantom"_p / "recruitment.json"_p); + LoadResourceAndCheckRet( + RoguelikeRecruitConfig, + "roguelike"_p / "Mizuki"_p / "recruitment.json"_p); + LoadResourceAndCheckRet( + RoguelikeRecruitConfig, + "roguelike"_p / "Sami"_p / "recruitment.json"_p); - LoadResourceAndCheckRet(RoguelikeShoppingConfig, "roguelike"_p / "Phantom"_p / "shopping.json"_p); - LoadResourceAndCheckRet(RoguelikeShoppingConfig, "roguelike"_p / "Mizuki"_p / "shopping.json"_p); + LoadResourceAndCheckRet( + RoguelikeShoppingConfig, + "roguelike"_p / "Phantom"_p / "shopping.json"_p); + LoadResourceAndCheckRet( + RoguelikeShoppingConfig, + "roguelike"_p / "Mizuki"_p / "shopping.json"_p); LoadResourceAndCheckRet(RoguelikeShoppingConfig, "roguelike"_p / "Sami"_p / "shopping.json"_p); - LoadResourceAndCheckRet(RoguelikeStageEncounterConfig, "roguelike"_p / "Phantom"_p / "encounter.json"_p); - LoadResourceAndCheckRet(RoguelikeStageEncounterConfig, "roguelike"_p / "Mizuki"_p / "encounter.json"_p); - LoadResourceAndCheckRet(RoguelikeStageEncounterConfig, "roguelike"_p / "Sami"_p / "encounter.json"_p); - LoadResourceAndCheckRet(RoguelikeStageEncounterConfig, - "roguelike"_p / "Phantom"_p / "encounter_for_deposit.json"_p); - LoadResourceAndCheckRet(RoguelikeStageEncounterConfig, "roguelike"_p / "Mizuki"_p / "encounter_for_deposit.json"_p); - LoadResourceAndCheckRet(RoguelikeStageEncounterConfig, "roguelike"_p / "Sami"_p / "encounter_for_deposit.json"_p); + LoadResourceAndCheckRet( + RoguelikeStageEncounterConfig, + "roguelike"_p / "Phantom"_p / "encounter.json"_p); + LoadResourceAndCheckRet( + RoguelikeStageEncounterConfig, + "roguelike"_p / "Mizuki"_p / "encounter.json"_p); + LoadResourceAndCheckRet( + RoguelikeStageEncounterConfig, + "roguelike"_p / "Sami"_p / "encounter.json"_p); + LoadResourceAndCheckRet( + RoguelikeStageEncounterConfig, + "roguelike"_p / "Phantom"_p / "encounter_for_deposit.json"_p); + LoadResourceAndCheckRet( + RoguelikeStageEncounterConfig, + "roguelike"_p / "Mizuki"_p / "encounter_for_deposit.json"_p); + LoadResourceAndCheckRet( + RoguelikeStageEncounterConfig, + "roguelike"_p / "Sami"_p / "encounter_for_deposit.json"_p); - LoadResourceAndCheckRet(RoguelikeFoldartalConfig, "roguelike"_p / "Sami"_p / "foldartal.json"_p); + LoadResourceAndCheckRet( + RoguelikeFoldartalConfig, + "roguelike"_p / "Sami"_p / "foldartal.json"_p); #undef LoadTemplByConfigAndCheckRet #undef LoadResourceAndCheckRet @@ -197,6 +221,11 @@ bool asst::ResourceLoader::load(const std::filesystem::path& path) return m_loaded; } +void asst::ResourceLoader::set_connection_extras(const std::string& name, const json::object& diff) +{ + GeneralConfig::get_instance().set_connection_extras(name, diff); +} + bool asst::ResourceLoader::loaded() const noexcept { return m_loaded; diff --git a/src/MaaCore/Config/ResourceLoader.h b/src/MaaCore/Config/ResourceLoader.h index 010737797b..63597dee25 100644 --- a/src/MaaCore/Config/ResourceLoader.h +++ b/src/MaaCore/Config/ResourceLoader.h @@ -12,57 +12,63 @@ namespace asst { - class ResourceLoader final : public SingletonHolder, public AbstractResource +class ResourceLoader final + : public SingletonHolder + , public AbstractResource +{ +public: + virtual ~ResourceLoader() override; + + virtual bool load(const std::filesystem::path& path) override; + + void set_connection_extras(const std::string& name, const json::object& diff); + bool loaded() const noexcept; + +public: + ResourceLoader(); + + void cancel(); + +private: + void load_thread_func(); + + template + requires std::is_base_of_v + bool load_resource(const std::filesystem::path& path) { - public: - virtual ~ResourceLoader() override; - - virtual bool load(const std::filesystem::path& path) override; - bool loaded() const noexcept; - - public: - ResourceLoader(); - - void cancel(); - - private: - void load_thread_func(); - - template - requires std::is_base_of_v - bool load_resource(const std::filesystem::path& path) - { - if (!std::filesystem::exists(path)) { - return m_loaded; - } - return SingletonHolder::get_instance().load(path); + if (!std::filesystem::exists(path)) { + return m_loaded; } + return SingletonHolder::get_instance().load(path); + } - template - requires std::is_base_of_v - bool load_resource_with_templ(const std::filesystem::path& path, const std::filesystem::path& templ_dir) - { - if (!load_resource(path)) { - return false; - } - static auto& templ_ins = SingletonHolder::get_instance(); - const auto& required = SingletonHolder::get_instance().get_templ_required(); - templ_ins.set_load_required(required); - - return load_resource(templ_dir); + template + requires std::is_base_of_v + bool load_resource_with_templ( + const std::filesystem::path& path, + const std::filesystem::path& templ_dir) + { + if (!load_resource(path)) { + return false; } + static auto& templ_ins = SingletonHolder::get_instance(); + const auto& required = SingletonHolder::get_instance().get_templ_required(); + templ_ins.set_load_required(required); - void add_load_queue(AbstractResource& res, const std::filesystem::path& path); + return load_resource(templ_dir); + } - private: - bool m_loaded = false; - std::mutex m_entry_mutex; + void add_load_queue(AbstractResource& res, const std::filesystem::path& path); - // only for async load - bool m_load_thread_exit = false; - std::deque> m_load_queue; - std::mutex m_load_mutex; - std::condition_variable m_load_cv; - std::thread m_load_thread; - }; +private: + bool m_loaded = false; + std::mutex m_entry_mutex; + + // only for async load + bool m_load_thread_exit = false; + std::deque> m_load_queue; + std::mutex m_load_mutex; + std::condition_variable m_load_cv; + std::thread m_load_thread; +}; } // namespace asst diff --git a/src/MaaCore/Controller/AdbController.cpp b/src/MaaCore/Controller/AdbController.cpp index 9c09e5789f..eb6afcd2c3 100644 --- a/src/MaaCore/Controller/AdbController.cpp +++ b/src/MaaCore/Controller/AdbController.cpp @@ -8,7 +8,7 @@ #ifdef _MSC_VER #pragma warning(push) -#pragma warning(disable : 4068) +#pragma warning(disable: 4068) #endif #include #ifdef _MSC_VER @@ -18,10 +18,12 @@ #include "Common/AsstTypes.h" #include "Config/GeneralConfig.h" #include "Utils/Logger.hpp" +#include "Utils/Platform.hpp" #include "Utils/StringMisc.hpp" asst::AdbController::AdbController(const AsstCallback& callback, Assistant* inst, PlatformType type) - : InstHelper(inst), m_callback(callback) + : InstHelper(inst) + , m_callback(callback) { LogTraceFunction; @@ -47,7 +49,8 @@ asst::AdbController::~AdbController() release(); } -std::optional asst::AdbController::reconnect(const std::string& cmd, int64_t timeout, bool recv_by_socket) +std::optional + asst::AdbController::reconnect(const std::string& cmd, int64_t timeout, bool recv_by_socket) { LogTraceFunction; @@ -73,7 +76,8 @@ std::optional asst::AdbController::reconnect(const std::string& cmd if (need_exit()) { break; } - auto reconnect_ret = call_command(m_adb.connect, 60LL * 1000, false /* 禁止重连避免无限递归 */); + auto reconnect_ret = + call_command(m_adb.connect, 60LL * 1000, false /* 禁止重连避免无限递归 */); if (need_exit()) { break; } @@ -83,7 +87,8 @@ std::optional asst::AdbController::reconnect(const std::string& cmd is_reconnect_success = reconnect_str.find("error") == std::string::npos; } if (is_reconnect_success) { - auto recall_ret = call_command(cmd, timeout, false /* 禁止重连避免无限递归 */, recv_by_socket); + auto recall_ret = + call_command(cmd, timeout, false /* 禁止重连避免无限递归 */, recv_by_socket); if (recall_ret) { // 重连并成功执行了 reconnect_info["what"] = "Reconnected"; @@ -108,8 +113,11 @@ std::optional asst::AdbController::reconnect(const std::string& cmd return std::nullopt; } -std::optional asst::AdbController::call_command(const std::string& cmd, int64_t timeout, - bool allow_reconnect, bool recv_by_socket) +std::optional asst::AdbController::call_command( + const std::string& cmd, + int64_t timeout, + bool allow_reconnect, + bool recv_by_socket) { using namespace std::chrono_literals; using namespace std::chrono; @@ -125,7 +133,8 @@ std::optional asst::AdbController::call_command(const std::string& std::optional exit_res; - exit_res = m_platform_io->call_command(cmd, recv_by_socket, pipe_data, sock_data, timeout, start_time); + exit_res = + m_platform_io->call_command(cmd, recv_by_socket, pipe_data, sock_data, timeout, start_time); if (!exit_res) { return std::nullopt; @@ -135,8 +144,17 @@ std::optional asst::AdbController::call_command(const std::string& callcmd_lock.unlock(); m_last_command_duration = duration_cast(steady_clock::now() - start_time).count(); - Log.info("Call `", cmd, "` ret", exit_ret, ", cost", m_last_command_duration, "ms , stdout size:", pipe_data.size(), - ", socket size:", sock_data.size()); + Log.info( + "Call `", + cmd, + "` ret", + exit_ret, + ", cost", + m_last_command_duration, + "ms , stdout size:", + pipe_data.size(), + ", socket size:", + sock_data.size()); if (!pipe_data.empty() && pipe_data.size() < 4096) { Log.trace("stdout output:", Logger::separator::newline, pipe_data); } @@ -167,6 +185,19 @@ void asst::AdbController::callback(AsstMsg msg, const json::value& details) } } +void asst::AdbController::init_mumu_extras(const AdbCfg& adb_cfg) +{ + if (adb_cfg.extras.empty()) { + LogWarn << "adb_cfg.extras is empty"; + return; + } + + std::filesystem::path mumu_path = utils::path(adb_cfg.extras.get("path", "")); + int mumu_index = adb_cfg.extras.get("index", 0); + int mumu_display = adb_cfg.extras.get("display", 0); + m_mumu_extras.init(mumu_path, mumu_index, mumu_display); +} + void asst::AdbController::close_socket() noexcept { m_platform_io->close_socket(); @@ -216,14 +247,20 @@ bool asst::AdbController::click(const Point& p) Log.error("click point out of range"); } - std::string cur_cmd = - utils::string_replace_all(m_adb.click, { { "[x]", std::to_string(p.x) }, { "[y]", std::to_string(p.y) } }); + std::string cur_cmd = utils::string_replace_all( + m_adb.click, + { { "[x]", std::to_string(p.x) }, { "[y]", std::to_string(p.y) } }); return call_command(cur_cmd).has_value(); } -bool asst::AdbController::swipe(const Point& p1, const Point& p2, int duration, bool extra_swipe, - [[maybe_unused]] double slope_in, [[maybe_unused]] double slope_out, - [[maybe_unused]] bool with_pause) +bool asst::AdbController::swipe( + const Point& p1, + const Point& p2, + int duration, + bool extra_swipe, + [[maybe_unused]] double slope_in, + [[maybe_unused]] double slope_out, + [[maybe_unused]] bool with_pause) { int x1 = p1.x, y1 = p1.y; int x2 = p2.x, y2 = p2.y; @@ -238,26 +275,31 @@ bool asst::AdbController::swipe(const Point& p1, const Point& p2, int duration, const auto& opt = Config.get_options(); std::string duration_str = - duration <= 0 ? "" : std::to_string(static_cast(duration * opt.adb_swipe_duration_multiplier)); - std::string cur_cmd = utils::string_replace_all(m_adb.swipe, { - { "[x1]", std::to_string(x1) }, - { "[y1]", std::to_string(y1) }, - { "[x2]", std::to_string(x2) }, - { "[y2]", std::to_string(y2) }, - { "[duration]", duration_str }, - }); + duration <= 0 + ? "" + : std::to_string(static_cast(duration * opt.adb_swipe_duration_multiplier)); + std::string cur_cmd = utils::string_replace_all( + m_adb.swipe, + { + { "[x1]", std::to_string(x1) }, + { "[y1]", std::to_string(y1) }, + { "[x2]", std::to_string(x2) }, + { "[y2]", std::to_string(y2) }, + { "[duration]", duration_str }, + }); bool ret = call_command(cur_cmd).has_value(); // 额外的滑动:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 if (extra_swipe && opt.adb_extra_swipe_duration > 0) { std::string extra_cmd = utils::string_replace_all( - m_adb.swipe, { - { "[x1]", std::to_string(x2) }, - { "[y1]", std::to_string(y2) }, - { "[x2]", std::to_string(x2) }, - { "[y2]", std::to_string(y2 - opt.adb_extra_swipe_dist /* * m_control_scale*/) }, - { "[duration]", std::to_string(opt.adb_extra_swipe_duration) }, - }); + m_adb.swipe, + { + { "[x1]", std::to_string(x2) }, + { "[y1]", std::to_string(y2) }, + { "[x2]", std::to_string(x2) }, + { "[y2]", std::to_string(y2 - opt.adb_extra_swipe_dist /* * m_control_scale*/) }, + { "[duration]", std::to_string(opt.adb_extra_swipe_duration) }, + }); ret &= call_command(extra_cmd).has_value(); } return ret; @@ -294,7 +336,9 @@ bool asst::AdbController::convert_lf(std::string& data) if (data.empty() || data.size() < 2) { return false; } - auto pred = [](const std::string::iterator& cur) -> bool { return *cur == '\r' && *(cur + 1) == '\n'; }; + auto pred = [](const std::string::iterator& cur) -> bool { + return *cur == '\r' && *(cur + 1) == '\n'; + }; // find the first of "\r\n" auto first_iter = data.end(); for (auto iter = data.begin(); iter != data.end() - 1; ++iter) { @@ -323,23 +367,34 @@ bool asst::AdbController::convert_lf(std::string& data) bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect) { + using namespace std::chrono; DecodeFunc decode_raw = [&](const std::string& data) -> bool { - if (data.size() < 8) return false; + if (data.size() < 8) { + return false; + } // assuming little endian - uint32_t w = static_cast(static_cast(data[0])) << 0 | - static_cast(static_cast(data[1])) << 8 | - static_cast(static_cast(data[2])) << 16 | - static_cast(static_cast(data[3])) << 24; - uint32_t h = static_cast(static_cast(data[4])) << 0 | - static_cast(static_cast(data[5])) << 8 | - static_cast(static_cast(data[6])) << 16 | - static_cast(static_cast(data[7])) << 24; + uint32_t w = static_cast(static_cast(data[0])) << 0 + | static_cast(static_cast(data[1])) << 8 + | static_cast(static_cast(data[2])) << 16 + | static_cast(static_cast(data[3])) << 24; + uint32_t h = static_cast(static_cast(data[4])) << 0 + | static_cast(static_cast(data[5])) << 8 + | static_cast(static_cast(data[6])) << 16 + | static_cast(static_cast(data[7])) << 24; if (int(w) != m_width || int(h) != m_height) { - Log.error("Size from image header", w, h, "does not match the size of screen", m_width, m_height); + Log.error( + "Size from image header", + w, + h, + "does not match the size of screen", + m_width, + m_height); return false; } size_t std_size = 4ULL * m_width * m_height; - if (data.size() < std_size) return false; + if (data.size() < std_size) { + return false; + } const size_t header_size = data.size() - std_size; // 12 or 16. ref: // https://android.googlesource.com/platform/frameworks/base/+/26a2b97dbe48ee45e9ae70110714048f2f360f97%5E%21/cmds/screencap/screencap.cpp auto img_data_beg = data.cbegin() + header_size; @@ -371,14 +426,13 @@ bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect }; if (m_adb.screencap_method == AdbProperty::ScreencapMethod::UnknownYet) { - using namespace std::chrono; Log.info("Try to find the fastest way to screencap"); auto min_cost = milliseconds(LLONG_MAX); clear_lf_info(); auto start_time = high_resolution_clock::now(); - if (m_support_socket && m_server_started && - screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true, 5000)) { + if (m_support_socket && m_server_started + && screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true, 5000)) { auto duration = duration_cast(high_resolution_clock::now() - start_time); if (duration < min_cost) { m_adb.screencap_method = AdbProperty::ScreencapMethod::RawByNc; @@ -420,13 +474,37 @@ bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect else { Log.info("Encode is not supported"); } + + if (m_mumu_extras.inited()) { + start_time = high_resolution_clock::now(); + if (m_mumu_extras.screencap()) { + auto duration = + duration_cast(high_resolution_clock::now() - start_time); + if (duration < min_cost) { + m_adb.screencap_method = AdbProperty::ScreencapMethod::MumuExtras; + m_inited = true; + min_cost = duration; + } + Log.info("MumuExtras cost", duration.count(), "ms"); + } + else { + Log.info("MumuExtras is not supported"); + } + } + static const std::unordered_map MethodName = { { AdbProperty::ScreencapMethod::UnknownYet, "UnknownYet" }, { AdbProperty::ScreencapMethod::RawByNc, "RawByNc" }, { AdbProperty::ScreencapMethod::RawWithGzip, "RawWithGzip" }, { AdbProperty::ScreencapMethod::Encode, "Encode" }, + { AdbProperty::ScreencapMethod::MumuExtras, "MumuExtras" }, }; - Log.info("The fastest way is", MethodName.at(m_adb.screencap_method), ", cost:", min_cost.count(), "ms"); + Log.info( + "The fastest way is", + MethodName.at(m_adb.screencap_method), + ", cost:", + min_cost.count(), + "ms"); if (m_adb.screencap_method != AdbProperty::ScreencapMethod::UnknownYet) { json::value info = json::object { { "uuid", m_uuid }, @@ -443,34 +521,43 @@ bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect return m_adb.screencap_method != AdbProperty::ScreencapMethod::UnknownYet; } else { + auto start_time = high_resolution_clock::now(); bool screencap_ret = false; switch (m_adb.screencap_method) { case AdbProperty::ScreencapMethod::RawByNc: screencap_ret = screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true); break; case AdbProperty::ScreencapMethod::RawWithGzip: - screencap_ret = screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect); + screencap_ret = + screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect); break; case AdbProperty::ScreencapMethod::Encode: screencap_ret = screencap(m_adb.screencap_encode, decode_encode, allow_reconnect); break; + case AdbProperty::ScreencapMethod::MumuExtras: { + auto img_opt = m_mumu_extras.screencap(); + screencap_ret = img_opt.has_value(); + if (screencap_ret) { + image_payload = img_opt.value(); + } + } break; default: break; } + auto duration = duration_cast(high_resolution_clock::now() - start_time); // 记录截图耗时,每10次截图回传一次最值+平均值 - m_screencap_duration.emplace_back(screencap_ret ? m_last_command_duration : LLONG_MAX); // 记录截图耗时 - ++m_screencap_time; + m_screencap_cost.emplace_back(screencap_ret ? duration.count() : -1); // 记录截图耗时 + ++m_screencap_times; - if (m_screencap_duration.size() > 30) { - m_screencap_duration.pop_front(); + if (m_screencap_cost.size() > 30) { + m_screencap_cost.pop_front(); } - if (m_screencap_time > 9) { // 每 10 次截图计算一次平均耗时 - m_screencap_time = 0; - auto filtered_duration = - m_screencap_duration | views::filter([](long long num) { return num < LLONG_MAX; }); + if (m_screencap_times > 9) { // 每 10 次截图计算一次平均耗时 + m_screencap_times = 0; + auto filtered_cost = m_screencap_cost | views::filter([](auto num) { return num > 0; }); // 过滤后的有效截图用时次数 - auto filtered_count = m_screencap_duration.size() - ranges::count(m_screencap_duration, LLONG_MAX); - auto [screencap_cost_min, screencap_cost_max] = ranges::minmax(filtered_duration); + auto filtered_count = m_screencap_cost.size() - ranges::count(m_screencap_cost, -1); + auto [screencap_cost_min, screencap_cost_max] = ranges::minmax(filtered_cost); json::value info = json::object { { "uuid", m_uuid }, { "what", "ScreencapCost" }, @@ -480,12 +567,13 @@ bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect { "max", screencap_cost_max }, { "avg", filtered_count > 0 - ? std::accumulate(filtered_duration.begin(), filtered_duration.end(), 0ll) / filtered_count + ? std::accumulate(filtered_cost.begin(), filtered_cost.end(), 0ll) + / filtered_count : -1 }, } }, }; - if (m_screencap_duration.size() - filtered_count > 0) { - info["details"]["fault_times"] = m_screencap_duration.size() - filtered_count; + if (m_screencap_cost.size() - filtered_count > 0) { + info["details"]["fault_times"] = m_screencap_cost.size() - filtered_count; } callback(AsstMsg::ConnectionInfo, info); } @@ -493,8 +581,12 @@ bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect } } -bool asst::AdbController::screencap(const std::string& cmd, const DecodeFunc& decode_func, bool allow_reconnect, - bool by_socket, int timeout) +bool asst::AdbController::screencap( + const std::string& cmd, + const DecodeFunc& decode_func, + bool allow_reconnect, + bool by_socket, + int timeout) { if ((!m_support_socket || !m_server_started) && by_socket) [[unlikely]] { return false; @@ -517,7 +609,8 @@ bool asst::AdbController::screencap(const std::string& cmd, const DecodeFunc& de } if (decode_func(data)) [[likely]] { - if (m_adb.screencap_end_of_line == AdbProperty::ScreencapEndOfLine::UnknownYet) [[unlikely]] { + if (m_adb.screencap_end_of_line == AdbProperty::ScreencapEndOfLine::UnknownYet) + [[unlikely]] { Log.info("screencap_end_of_line is LF"); m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::LF; } @@ -551,7 +644,10 @@ bool asst::AdbController::screencap(const std::string& cmd, const DecodeFunc& de return true; } -bool asst::AdbController::connect(const std::string& adb_path, const std::string& address, const std::string& config) +bool asst::AdbController::connect( + const std::string& adb_path, + const std::string& address, + const std::string& config) { LogTraceFunction; @@ -578,10 +674,11 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string auto adb_ret = Config.get_adb_cfg(config); if (!adb_ret) { - json::value info = get_info_json() | json::object { - { "what", "ConnectFailed" }, - { "why", "ConfigNotFound" }, - }; + json::value info = get_info_json() + | json::object { + { "what", "ConnectFailed" }, + { "why", "ConfigNotFound" }, + }; callback(AsstMsg::ConnectionInfo, info); return false; } @@ -593,13 +690,15 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string // 里面的值每次执行命令后可能更新,所以要用 lambda 拿最新的 auto cmd_replace = [&](const std::string& cfg_cmd) -> std::string { - return utils::string_replace_all(cfg_cmd, { - { "[Adb]", adb_path }, - { "[AdbSerial]", address }, - { "[DisplayId]", display_id }, - { "[NcPort]", std::to_string(nc_port) }, - { "[NcAddress]", nc_address }, - }); + return utils::string_replace_all( + cfg_cmd, + { + { "[Adb]", adb_path }, + { "[AdbSerial]", address }, + { "[DisplayId]", display_id }, + { "[NcPort]", std::to_string(nc_port) }, + { "[NcAddress]", nc_address }, + }); }; if (need_exit()) { @@ -610,19 +709,22 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string { m_adb.connect = cmd_replace(adb_cfg.connect); m_adb.release = cmd_replace(adb_cfg.release); - auto connect_ret = call_command(m_adb.connect, 60LL * 1000, false /* adb 连接时不允许重试 */); + auto connect_ret = + call_command(m_adb.connect, 60LL * 1000, false /* adb 连接时不允许重试 */); bool is_connect_success = false; if (connect_ret) { auto& connect_str = connect_ret.value(); is_connect_success = connect_str.find("error") == std::string::npos; - if (connect_str.find("daemon started successfully") != std::string::npos && - connect_str.find("daemon still not running") == std::string::npos) {} + if (connect_str.find("daemon started successfully") != std::string::npos + && connect_str.find("daemon still not running") == std::string::npos) { + } } if (!is_connect_success) { - json::value info = get_info_json() | json::object { - { "what", "ConnectFailed" }, - { "why", "Connection command failed to exec" }, - }; + json::value info = get_info_json() + | json::object { + { "what", "ConnectFailed" }, + { "why", "Connection command failed to exec" }, + }; callback(AsstMsg::ConnectionInfo, info); return false; } @@ -634,12 +736,14 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string /* get uuid (imei) */ { - auto uuid_ret = call_command(cmd_replace(adb_cfg.uuid), 20000, false /* adb 连接时不允许重试 */); + auto uuid_ret = + call_command(cmd_replace(adb_cfg.uuid), 20000, false /* adb 连接时不允许重试 */); if (!uuid_ret) { - json::value info = get_info_json() | json::object { - { "what", "ConnectFailed" }, - { "why", "Uuid command failed to exec" }, - }; + json::value info = get_info_json() + | json::object { + { "what", "ConnectFailed" }, + { "why", "Uuid command failed to exec" }, + }; callback(AsstMsg::ConnectionInfo, info); return false; } @@ -648,10 +752,11 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string std::erase_if(uuid_str, [](char c) { return !std::isdigit(c) && !std::isalpha(c); }); m_uuid = std::move(uuid_str); - json::value info = get_info_json() | json::object { - { "what", "UuidGot" }, - { "why", "" }, - }; + json::value info = get_info_json() + | json::object { + { "what", "UuidGot" }, + { "why", "" }, + }; info["details"]["uuid"] = m_uuid; callback(AsstMsg::ConnectionInfo, info); } @@ -687,10 +792,11 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string { auto display_ret = call_command(cmd_replace(adb_cfg.display)); if (!display_ret) { - json::value info = get_info_json() | json::object { - { "what", "ConnectFailed" }, - { "why", "Display command failed to exec" }, - }; + json::value info = get_info_json() + | json::object { + { "what", "ConnectFailed" }, + { "why", "Display command failed to exec" }, + }; callback(AsstMsg::ConnectionInfo, info); return false; } @@ -702,10 +808,11 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string m_width = (std::max)(size_value1, size_value2); m_height = (std::min)(size_value1, size_value2); - json::value info = get_info_json() | json::object { - { "what", "ResolutionGot" }, - { "why", "" }, - }; + json::value info = get_info_json() + | json::object { + { "what", "ResolutionGot" }, + { "why", "" }, + }; info["details"] |= json::object { { "width", m_width }, @@ -728,10 +835,11 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string } { - json::value info = get_info_json() | json::object { - { "what", "Connected" }, - { "why", "" }, - }; + json::value info = get_info_json() + | json::object { + { "what", "Connected" }, + { "why", "" }, + }; callback(AsstMsg::ConnectionInfo, info); } @@ -777,6 +885,15 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string if (need_exit()) { return false; } + + if (config == "MuMuEmulator12") { + init_mumu_extras(adb_cfg); + } + + if (need_exit()) { + return false; + } + return true; } diff --git a/src/MaaCore/Controller/AdbController.h b/src/MaaCore/Controller/AdbController.h index c1c2fb774d..c8d92c440a 100644 --- a/src/MaaCore/Controller/AdbController.h +++ b/src/MaaCore/Controller/AdbController.h @@ -2,135 +2,166 @@ #include "ControllerAPI.h" -#include #include +#include #include "Platform/PlatformFactory.h" #include "Common/AsstMsg.h" +#include "Config/GeneralConfig.h" #include "InstHelper.h" +#include "MumuExtras.h" namespace asst { - class AdbController : public ControllerAPI, protected InstHelper +class AdbController + : public ControllerAPI + , protected InstHelper +{ +public: + AdbController(const AsstCallback& callback, Assistant* inst, PlatformType type); + AdbController(const AdbController&) = delete; + AdbController(AdbController&&) = delete; + virtual ~AdbController(); + + virtual bool connect( + const std::string& adb_path, + const std::string& address, + const std::string& config) override; + + virtual void set_kill_adb_on_exit(bool enable) noexcept override; + + virtual bool inited() const noexcept override; + + virtual const std::string& get_uuid() const override; + + virtual bool screencap(cv::Mat& image_payload, bool allow_reconnect = false) override; + + virtual bool start_game(const std::string& client_type) override; + virtual bool stop_game() override; + + virtual bool click(const Point& p) override; + + virtual bool swipe( + const Point& p1, + const Point& p2, + int duration = 0, + bool extra_swipe = false, + double slope_in = 1, + double slope_out = 1, + bool with_pause = false) override; + + virtual bool inject_input_event([[maybe_unused]] const InputEvent& event) override { - public: - AdbController(const AsstCallback& callback, Assistant* inst, PlatformType type); - AdbController(const AdbController&) = delete; - AdbController(AdbController&&) = delete; - virtual ~AdbController(); + return false; + } - virtual bool connect(const std::string& adb_path, const std::string& address, - const std::string& config) override; + virtual bool press_esc() override; - virtual void set_kill_adb_on_exit(bool enable) noexcept override; + virtual ControlFeat::Feat support_features() const noexcept override + { + return ControlFeat::NONE; + } - virtual bool inited() const noexcept override; + virtual std::pair get_screen_res() const noexcept override; - virtual const std::string& get_uuid() const override; + AdbController& operator=(const AdbController&) = delete; + AdbController& operator=(AdbController&&) = delete; - virtual bool screencap(cv::Mat& image_payload, bool allow_reconnect = false) override; + virtual void back_to_home() noexcept override; - virtual bool start_game(const std::string& client_type) override; - virtual bool stop_game() override; +protected: + std::optional call_command( + const std::string& cmd, + int64_t timeout = 20000, + bool allow_reconnect = true, + bool recv_by_socket = false); - virtual bool click(const Point& p) override; + virtual std::optional + reconnect(const std::string& cmd, int64_t timeout, bool recv_by_socket); - virtual bool swipe(const Point& p1, const Point& p2, int duration = 0, bool extra_swipe = false, - double slope_in = 1, double slope_out = 1, bool with_pause = false) override; + void release(); - virtual bool inject_input_event([[maybe_unused]] const InputEvent& event) override { return false; } + void close_socket() noexcept; + std::optional init_socket(const std::string& local_address); - virtual bool press_esc() override; - virtual ControlFeat::Feat support_features() const noexcept override { return ControlFeat::NONE; } + using DecodeFunc = std::function; + bool screencap( + const std::string& cmd, + const DecodeFunc& decode_func, + bool allow_reconnect = false, + bool by_socket = false, + int max_timeout = 20000); + void clear_lf_info(); - virtual std::pair get_screen_res() const noexcept override; + virtual void clear_info() noexcept; + void callback(AsstMsg msg, const json::value& details); + void init_mumu_extras(const AdbCfg& adb_cfg); - AdbController& operator=(const AdbController&) = delete; - AdbController& operator=(AdbController&&) = delete; + // 转换 data 中的 CRLF 为 LF:有些模拟器自带的 adb,exec-out 输出的 \n 会被替换成 \r\n, + // 导致解码错误,所以这里转一下回来(点名批评 mumu 和雷电) + static bool convert_lf(std::string& data); - virtual void back_to_home() noexcept override; + AsstCallback m_callback; - protected: - std::optional call_command(const std::string& cmd, int64_t timeout = 20000, - bool allow_reconnect = true, bool recv_by_socket = false); + std::minstd_rand m_rand_engine; - virtual std::optional reconnect(const std::string& cmd, int64_t timeout, bool recv_by_socket); + std::mutex m_callcmd_mutex; - void release(); + std::shared_ptr m_platform_io = nullptr; - void close_socket() noexcept; - std::optional init_socket(const std::string& local_address); + struct AdbProperty + { + /* command */ + std::string connect; + std::string call_minitouch; + std::string call_maatouch; + std::string click; + std::string swipe; + std::string press_esc; - using DecodeFunc = std::function; - bool screencap(const std::string& cmd, const DecodeFunc& decode_func, bool allow_reconnect = false, - bool by_socket = false, int max_timeout = 20000); - void clear_lf_info(); + std::string screencap_raw_by_nc; + std::string screencap_raw_with_gzip; + std::string screencap_encode; + std::string release; - virtual void clear_info() noexcept; - void callback(AsstMsg msg, const json::value& details); + std::string start; + std::string stop; - // 转换 data 中的 CRLF 为 LF:有些模拟器自带的 adb,exec-out 输出的 \n 会被替换成 \r\n, - // 导致解码错误,所以这里转一下回来(点名批评 mumu 和雷电) - static bool convert_lf(std::string& data); + std::string back_to_home; - AsstCallback m_callback; - - std::minstd_rand m_rand_engine; - - std::mutex m_callcmd_mutex; - - std::shared_ptr m_platform_io = nullptr; - - struct AdbProperty + /* properties */ + enum class ScreencapEndOfLine { - /* command */ - std::string connect; - std::string call_minitouch; - std::string call_maatouch; - std::string click; - std::string swipe; - std::string press_esc; + UnknownYet, + CRLF, + LF, + CR + } screencap_end_of_line = ScreencapEndOfLine::UnknownYet; - std::string screencap_raw_by_nc; - std::string screencap_raw_with_gzip; - std::string screencap_encode; - std::string release; + enum class ScreencapMethod + { + UnknownYet, + // Default, + RawByNc, + RawWithGzip, + Encode, + MumuExtras, + } screencap_method = ScreencapMethod::UnknownYet; + } m_adb; - std::string start; - std::string stop; + std::string m_uuid; + std::pair m_screen_size = { 0, 0 }; + int m_width = 0; + int m_height = 0; + bool m_support_socket = false; + bool m_server_started = false; + bool m_inited = false; + bool m_kill_adb_on_exit = false; + long long m_last_command_duration = 0; // 上次命令执行用时 + std::deque m_screencap_cost; // 截图用时 + int m_screencap_times = 0; // 截图次数 - std::string back_to_home; - - /* properties */ - enum class ScreencapEndOfLine - { - UnknownYet, - CRLF, - LF, - CR - } screencap_end_of_line = ScreencapEndOfLine::UnknownYet; - - enum class ScreencapMethod - { - UnknownYet, - // Default, - RawByNc, - RawWithGzip, - Encode - } screencap_method = ScreencapMethod::UnknownYet; - } m_adb; - - std::string m_uuid; - std::pair m_screen_size = { 0, 0 }; - int m_width = 0; - int m_height = 0; - bool m_support_socket = false; - bool m_server_started = false; - bool m_inited = false; - bool m_kill_adb_on_exit = false; - long long m_last_command_duration = 0; // 上次命令执行用时 - std::deque m_screencap_duration; // 截图用时 - int m_screencap_time = 0; // 截图次数 - }; + MumuExtras m_mumu_extras; +}; } // namespace asst diff --git a/src/MaaCore/Controller/MumuExtras.cpp b/src/MaaCore/Controller/MumuExtras.cpp new file mode 100644 index 0000000000..9ae7c5fded --- /dev/null +++ b/src/MaaCore/Controller/MumuExtras.cpp @@ -0,0 +1,196 @@ +#include "MumuExtras.h" + +#include "Utils/Logger.hpp" +#include "Utils/NoWarningCV.h" + +namespace asst +{ +MumuExtras::~MumuExtras() +{ + uninit(); +} + +bool MumuExtras::init( + const std::filesystem::path& mumu_path, + int mumu_inst_index, + int mumu_display_id) +{ + bool same = mumu_path == mumu_path_ && mumu_inst_index == mumu_inst_index_ + && mumu_display_id == mumu_display_id_; + + if (same && inited_) { + return true; + } + + mumu_path_ = mumu_path; + mumu_inst_index_ = mumu_inst_index; + mumu_display_id_ = mumu_display_id; + + inited_ = load_mumu_library() && connect_mumu() && init_screencap(); + + return inited_; +} + +void MumuExtras::uninit() +{ + inited_ = false; + disconnect_mumu(); +} + +std::optional MumuExtras::screencap() +{ + if (!capture_display_func_) { + LogError << "capture_display_func_ is null"; + return std::nullopt; + } + + int ret = capture_display_func_( + mumu_handle_, + mumu_display_id_, + static_cast(display_buffer_.size()), + &display_width_, + &display_height_, + display_buffer_.data()); + + if (ret) { + LogError << "Failed to capture display" << VAR(ret) << VAR(mumu_handle_) + << VAR(mumu_display_id_) << VAR(display_buffer_.size()) << VAR(display_width_) + << VAR(display_height_); + return std::nullopt; + } + + cv::Mat raw(display_height_, display_width_, CV_8UC4, display_buffer_.data()); + cv::Mat bgr; + cv::cvtColor(raw, bgr, cv::COLOR_RGBA2BGR); + cv::Mat dst; + cv::flip(bgr, dst, 0); + + return dst; +} + +bool MumuExtras::load_mumu_library() +{ + auto lib_path = mumu_path_ / "shell/sdk/external_renderer_ipc"; + + if (!load_library(lib_path)) { + LogError << "Failed to load library" << VAR(lib_path); + return false; + } + + connect_func_ = get_function(kConnectFuncName); + if (!connect_func_) { + LogError << "Failed to get function" << VAR(kConnectFuncName); + return false; + } + + disconnect_func_ = get_function(kDisconnectFuncName); + if (!disconnect_func_) { + LogError << "Failed to get function" << VAR(kDisconnectFuncName); + return false; + } + + capture_display_func_ = get_function(kCaptureDisplayFuncName); + if (!capture_display_func_) { + LogError << "Failed to get function" << VAR(kCaptureDisplayFuncName); + return false; + } + + input_text_func_ = get_function(kInputTextFuncName); + if (!input_text_func_) { + LogError << "Failed to get function" << VAR(kInputTextFuncName); + return false; + } + + input_event_touch_down_func_ = + get_function(kInputEventTouchDownFuncName); + if (!input_event_touch_down_func_) { + LogError << "Failed to get function" << VAR(kInputEventTouchDownFuncName); + return false; + } + + input_event_touch_up_func_ = + get_function(kInputEventTouchUpFuncName); + if (!input_event_touch_up_func_) { + LogError << "Failed to get function" << VAR(kInputEventTouchUpFuncName); + return false; + } + + input_event_key_down_func_ = + get_function(kInputEventKeyDownFuncName); + if (!input_event_key_down_func_) { + LogError << "Failed to get function" << VAR(kInputEventKeyDownFuncName); + return false; + } + + input_event_key_up_func_ = + get_function(kInputEventKeyUpFuncName); + if (!input_event_key_up_func_) { + LogError << "Failed to get function" << VAR(kInputEventKeyUpFuncName); + return false; + } + + return true; +} + +bool MumuExtras::connect_mumu() +{ + LogInfo << VAR(mumu_path_) << VAR(mumu_inst_index_); + + if (!connect_func_) { + LogError << "connect_func_ is null"; + return false; + } + + std::u16string u16path = mumu_path_.u16string(); + std::wstring wpath( + std::make_move_iterator(u16path.begin()), + std::make_move_iterator(u16path.end())); + + mumu_handle_ = connect_func_(wpath.c_str(), mumu_inst_index_); + + if (mumu_handle_ == 0) { + LogError << "Failed to connect mumu" << VAR(mumu_path_) << VAR(mumu_inst_index_); + return false; + } + + return true; +} + +bool MumuExtras::init_screencap() +{ + if (!capture_display_func_) { + LogError << "capture_display_func_ is null"; + return false; + } + + int ret = capture_display_func_( + mumu_handle_, + mumu_display_id_, + 0, + &display_width_, + &display_height_, + nullptr); + + // mumu 的文档给错了,这里 0 才是成功 + if (ret) { + LogError << "Failed to capture display" << VAR(ret) << VAR(mumu_handle_) + << VAR(mumu_display_id_); + return false; + } + + display_buffer_.resize(display_width_ * display_height_ * 4); + + LogDebug << VAR(display_width_) << VAR(display_height_) << VAR(display_buffer_.size()); + return true; +} + +void MumuExtras::disconnect_mumu() +{ + LogInfo << VAR(mumu_handle_); + + if (mumu_handle_ != 0) { + disconnect_func_(mumu_handle_); + } +} + +} diff --git a/src/MaaCore/Controller/MumuExtras.h b/src/MaaCore/Controller/MumuExtras.h new file mode 100644 index 0000000000..81557943fd --- /dev/null +++ b/src/MaaCore/Controller/MumuExtras.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +#include "Mumu/external_renderer_ipc/external_renderer_ipc.h" + +#include "Utils/LibraryHolder.hpp" +#include "Utils/NoWarningCVMat.h" + +namespace asst +{ +class MumuExtras : public LibraryHolder +{ +public: + MumuExtras() = default; + + virtual ~MumuExtras(); + + bool inited() const { return inited_; } + + + bool init(const std::filesystem::path& mumu_path, int mumu_inst_index, int mumu_display_id); + void uninit(); + + std::optional screencap(); + +private: + bool load_mumu_library(); + bool connect_mumu(); + bool init_screencap(); + void disconnect_mumu(); + +private: + std::filesystem::path mumu_path_; + int mumu_inst_index_ = 0; + int mumu_display_id_ = 0; + + int mumu_handle_ = 0; + int display_width_ = 0; + int display_height_ = 0; + std::vector display_buffer_; + + bool inited_ = false; + +private: + inline static const std::string kConnectFuncName = "nemu_connect"; + inline static const std::string kDisconnectFuncName = "nemu_disconnect"; + inline static const std::string kCaptureDisplayFuncName = "nemu_capture_display"; + inline static const std::string kInputTextFuncName = "nemu_input_text"; + inline static const std::string kInputEventTouchDownFuncName = "nemu_input_event_touch_down"; + inline static const std::string kInputEventTouchUpFuncName = "nemu_input_event_touch_up"; + inline static const std::string kInputEventKeyDownFuncName = "nemu_input_event_key_down"; + inline static const std::string kInputEventKeyUpFuncName = "nemu_input_event_key_up"; + +private: + std::function connect_func_; + std::function disconnect_func_; + std::function capture_display_func_; + std::function input_text_func_; + std::function input_event_touch_down_func_; + std::function input_event_touch_up_func_; + std::function input_event_key_down_func_; + std::function input_event_key_up_func_; +}; +} diff --git a/src/MaaCore/MaaCore.vcxproj b/src/MaaCore/MaaCore.vcxproj index 98a2b31cec..c3f7cb6ff0 100644 --- a/src/MaaCore/MaaCore.vcxproj +++ b/src/MaaCore/MaaCore.vcxproj @@ -60,6 +60,7 @@ + @@ -106,6 +107,7 @@ + @@ -242,6 +244,7 @@ + @@ -462,37 +465,37 @@ false - $(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include + $(ProjectDir)..\..\3rdparty\EmulatorExtras;$(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include $(TargetDir);$(LibraryPath) $(ProjectDir)..\..\$(Platform)\$(Configuration)\ false - $(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include;$(ProjectDir) + $(ProjectDir)..\..\3rdparty\EmulatorExtras;$(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include;$(ProjectDir) $(TargetDir);$(LibraryPath) $(ProjectDir)..\..\$(Platform)\$(Configuration)\ false - $(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include + $(ProjectDir)..\..\3rdparty\EmulatorExtras;$(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include $(TargetDir);$(LibraryPath) $(ProjectDir)..\..\$(Platform)\$(Configuration)\ true - $(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include + $(ProjectDir)..\..\3rdparty\EmulatorExtras;$(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include $(TargetDir);$(LibraryPath) $(ProjectDir)..\..\$(Platform)\$(Configuration)\ false - $(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include;$(ProjectDir) + $(ProjectDir)..\..\3rdparty\EmulatorExtras;$(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include;$(ProjectDir) $(TargetDir);$(LibraryPath) $(ProjectDir)..\..\$(Platform)\$(Configuration)\ true - $(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include;$(ProjectDir) + $(ProjectDir)..\..\3rdparty\EmulatorExtras;$(ProjectDir)..\..\include;$(ProjectDir)..\..\3rdparty\include;$(ProjectDir) $(TargetDir);$(LibraryPath) $(ProjectDir)..\..\$(Platform)\$(Configuration)\ diff --git a/src/MaaCore/MaaCore.vcxproj.filters b/src/MaaCore/MaaCore.vcxproj.filters index 1d0ccc2368..d113d60c06 100644 --- a/src/MaaCore/MaaCore.vcxproj.filters +++ b/src/MaaCore/MaaCore.vcxproj.filters @@ -699,6 +699,12 @@ Source\Task\Roguelike + + Source\Controller + + + Source\Utils + @@ -1166,5 +1172,8 @@ Source\Task\Roguelike + + Source\Controller + \ No newline at end of file diff --git a/src/MaaCore/Utils/LibraryHolder.hpp b/src/MaaCore/Utils/LibraryHolder.hpp new file mode 100644 index 0000000000..b29cba036c --- /dev/null +++ b/src/MaaCore/Utils/LibraryHolder.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef _WIN32 +#include "Utils/Platform/SafeWindows.h" +#else +#include +#endif + +#include "Utils/Logger.hpp" + + +namespace asst +{ +// template for ref_count +template +class LibraryHolder +{ +public: + virtual ~LibraryHolder(); + + static bool load_library(const std::filesystem::path& libname); + static void unload_library(); + + template + static std::function get_function(const std::string& func_name); + +protected: + LibraryHolder() = default; + +private: + inline static std::filesystem::path libname_; + inline static int ref_count_ = 0; + inline static std::mutex mutex_; + +#ifdef _WIN32 + inline static HMODULE module_ = nullptr; +#else + inline static void* module_ = nullptr; +#endif +}; + +template +inline LibraryHolder::~LibraryHolder() +{ + unload_library(); +} + +template +inline bool LibraryHolder::load_library(const std::filesystem::path& libname) +{ + LogInfo << VAR(libname); + + std::unique_lock lock(mutex_); + + if (module_ != nullptr) { + if (libname_ != libname) { + LogError << "Already loaded with different library" << VAR(libname_) << VAR(libname); + return false; + } + + ++ref_count_; + LogDebug << "Already loaded" << VAR(ref_count_); + return true; + } + + LogInfo << "Loading library" << VAR(libname); + +#ifdef _WIN32 + module_ = LoadLibrary(libname.c_str()); +#else + module_ = dlopen(libname.c_str(), RTLD_LAZY); +#endif + + if (module_ == nullptr) { + LogError << "Failed to load library" << VAR(libname); + return false; + } + + libname_ = libname; + ++ref_count_; + return true; +} + +template +inline void LibraryHolder::unload_library() +{ + LogInfo << VAR(libname_); + + std::unique_lock lock(mutex_); + + if (module_ == nullptr) { + LogDebug << "LibraryHolder already unloaded"; + return; + } + + --ref_count_; + if (ref_count_ > 0) { + LogDebug << "LibraryHolder ref count" << VAR(ref_count_); + return; + } + + LogInfo << "Unloading library" << VAR(libname_); + +#ifdef _WIN32 + FreeLibrary(module_); +#else + dlclose(module_); +#endif + + module_ = nullptr; + libname_.clear(); + ref_count_ = 0; +} + +template +template +inline std::function LibraryHolder::get_function(const std::string& func_name) +{ + LogInfo << VAR(func_name); + + std::unique_lock lock(mutex_); + + if (module_ == nullptr) { + LogError << "LibraryHolder not loaded"; + return {}; + } + +#ifdef _WIN32 + auto func = GetProcAddress(module_, func_name.c_str()); +#else + auto func = dlsym(module_, func_name.c_str()); +#endif + + if (func == nullptr) { + LogError << "Failed to find exported function" << VAR(func_name); + return {}; + } + + return reinterpret_cast(func); +} + +} // namespace asst diff --git a/src/MaaCore/Utils/Logger.hpp b/src/MaaCore/Utils/Logger.hpp index 7eed776917..4b95ba1842 100644 --- a/src/MaaCore/Utils/Logger.hpp +++ b/src/MaaCore/Utils/Logger.hpp @@ -25,491 +25,571 @@ namespace asst { - template - concept has_stream_insertion_operator = requires { std::declval() << std::declval(); }; - template - concept enum_could_to_string = requires { asst::enum_to_string(std::declval()); }; +template +concept has_stream_insertion_operator = requires { std::declval() << std::declval(); }; +template +concept enum_could_to_string = requires { asst::enum_to_string(std::declval()); }; - // is_reference_wrapper - template - struct is_reference_wrapper : std::false_type - {}; - template - struct is_reference_wrapper> : std::true_type - {}; - template - struct is_reference_wrapper&> : std::true_type - {}; - template - struct is_reference_wrapper&&> : std::true_type - {}; - template - inline constexpr bool is_reference_wrapper_v = is_reference_wrapper::value; +// is_reference_wrapper +template +struct is_reference_wrapper : std::false_type +{ +}; - // from_reference_wrapper - template - struct from_reference_wrapper - { - typedef T type; - }; - template - struct from_reference_wrapper> - { - typedef typename from_reference_wrapper::type type; - }; - template - struct from_reference_wrapper&> - { - typedef typename from_reference_wrapper::type type; - }; - template - struct from_reference_wrapper&&> - { - typedef typename from_reference_wrapper::type type; - }; - template - using from_reference_wrapper_t = typename from_reference_wrapper::type; +template +struct is_reference_wrapper> : std::true_type +{ +}; - // to_reference_wrapper - template - struct to_reference_wrapper - { - typedef T type; - }; - template - struct to_reference_wrapper - { - typedef std::reference_wrapper type; - }; - template - struct to_reference_wrapper> - { - typedef typename to_reference_wrapper::type type; - }; - template - struct to_reference_wrapper&> - { - typedef typename to_reference_wrapper::type type; - }; - template - struct to_reference_wrapper&&> - { - typedef typename to_reference_wrapper::type type; - }; - template - using to_reference_wrapper_t = typename to_reference_wrapper::type; +template +struct is_reference_wrapper&> : std::true_type +{ +}; - template - struct remove_cvref - { - typedef std::remove_cvref_t>> type; - }; - template - using remove_cvref_t = typename remove_cvref::type; +template +struct is_reference_wrapper&&> : std::true_type +{ +}; - template - constexpr from_reference_wrapper_t convert_reference_wrapper(T&& x) +template +inline constexpr bool is_reference_wrapper_v = is_reference_wrapper::value; + +// from_reference_wrapper +template +struct from_reference_wrapper +{ + typedef T type; +}; + +template +struct from_reference_wrapper> +{ + typedef typename from_reference_wrapper::type type; +}; + +template +struct from_reference_wrapper&> +{ + typedef typename from_reference_wrapper::type type; +}; + +template +struct from_reference_wrapper&&> +{ + typedef typename from_reference_wrapper::type type; +}; + +template +using from_reference_wrapper_t = typename from_reference_wrapper::type; + +// to_reference_wrapper +template +struct to_reference_wrapper +{ + typedef T type; +}; + +template +struct to_reference_wrapper +{ + typedef std::reference_wrapper type; +}; + +template +struct to_reference_wrapper> +{ + typedef typename to_reference_wrapper::type type; +}; + +template +struct to_reference_wrapper&> +{ + typedef typename to_reference_wrapper::type type; +}; + +template +struct to_reference_wrapper&&> +{ + typedef typename to_reference_wrapper::type type; +}; + +template +using to_reference_wrapper_t = typename to_reference_wrapper::type; + +template +struct remove_cvref +{ + typedef std::remove_cvref_t>> type; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +template +constexpr from_reference_wrapper_t convert_reference_wrapper(T&& x) +{ + return x; +} + +namespace detail +{ +class scope_slice +{ +public: + using id = int; + + scope_slice() = default; + + std::string push(id& i) { - return x; + i = 0; + if (auto iter = + std::find_if(m_state.rbegin(), m_state.rend(), [](int e) { return e != -1; }); + iter != m_state.rend()) { + i = *iter + 1; + } + + std::string result; + result.reserve(m_state.size() * 3); + for (auto e : m_state) { + result += (e == -1 ? " " : "│"); + } + + m_state.push_back(i); + result += "┌"; + + return result; } - namespace detail + std::string pop(id i) { - class scope_slice - { - public: - using id = int; + std::string result; + result.reserve(m_state.size() * 3); + std::string sym = "│"; + std::string emp = " "; + for (auto& e : m_state) { + if (i != -1 && e == i) { + result += "└"; + e = -1; + sym = "┼"; + emp = "─"; + } + else { + result += (e == -1 ? emp : sym); + } + } - scope_slice() = default; + while (!m_state.empty() && m_state.back() == -1) { + m_state.pop_back(); + } + return result; + } - std::string push(id& i) - { - i = 0; - if (auto iter = std::find_if(m_state.rbegin(), m_state.rend(), [](int e) { return e != -1; }); - iter != m_state.rend()) { - i = *iter + 1; - } + std::string next() + { + while (!m_state.empty() && m_state.back() == -1) { + m_state.pop_back(); + } - std::string result; - result.reserve(m_state.size() * 3); - for (auto e : m_state) { - result += (e == -1 ? " " : "│"); - } - - m_state.push_back(i); - result += "┌"; - - return result; + std::string result; + result.reserve(m_state.size() * 3); + auto lhs = m_state.end(); + auto rhs = m_state.end(); + auto iter = m_state.begin(); + // | <- rhs + // ------ + // | <- lhs + for (; iter != m_state.end(); ++iter) { + if (*iter != -1) { + result += "│"; + continue; } - std::string pop(id i) - { - std::string result; - result.reserve(m_state.size() * 3); - std::string sym = "│"; - std::string emp = " "; - for (auto& e : m_state) { - if (i != -1 && e == i) { - result += "└"; - e = -1; - sym = "┼"; - emp = "─"; - } - else - result += (e == -1 ? emp : sym); + lhs = iter; + result += "┌"; + // if (iter != m_state.end()) + ++iter; + + for (; iter != m_state.end(); ++iter) { + if (*iter == -1) { + result += "─"; } - - while (!m_state.empty() && m_state.back() == -1) - m_state.pop_back(); - return result; - } - - std::string next() - { - while (!m_state.empty() && m_state.back() == -1) - m_state.pop_back(); - - std::string result; - result.reserve(m_state.size() * 3); - auto lhs = m_state.end(); - auto rhs = m_state.end(); - auto iter = m_state.begin(); - // | <- rhs - // ------ - // | <- lhs - for (; iter != m_state.end(); ++iter) { - if (*iter != -1) { - result += "│"; - continue; - } - - lhs = iter; - result += "┌"; - // if (iter != m_state.end()) - ++iter; - - for (; iter != m_state.end(); ++iter) { - if (*iter == -1) - result += "─"; - else - break; - } - rhs = iter; - result += "┘"; - - if (iter != m_state.end()) ++iter; - - for (; iter != m_state.end(); ++iter) { - result += (*iter == -1 ? " " : "│"); - } + else { break; } + } + rhs = iter; + result += "┘"; - if (lhs != m_state.end()) { - if (rhs == m_state.end()) - m_state.erase(lhs, rhs); - else - std::swap(*lhs, *rhs); - } - - return result; + if (iter != m_state.end()) { + ++iter; } - private: - std::vector m_state {}; - }; - } // namespace detail + for (; iter != m_state.end(); ++iter) { + result += (*iter == -1 ? " " : "│"); + } + break; + } - class toansi_ostream - { - std::reference_wrapper m_ofs; - - public: - toansi_ostream(toansi_ostream&&) = default; - toansi_ostream(const toansi_ostream&) = default; - toansi_ostream& operator=(toansi_ostream&&) = default; - toansi_ostream& operator=(const toansi_ostream&) = default; - - toansi_ostream(std::ostream& stream) : m_ofs(stream) {} - - toansi_ostream(std::reference_wrapper stream) : m_ofs(stream) {} - - template - requires has_stream_insertion_operator - toansi_ostream& operator<<(T&& v) - { - if constexpr (std::convertible_to) { - m_ofs.get() << utils::utf8_to_ansi(std::forward(v)); + if (lhs != m_state.end()) { + if (rhs == m_state.end()) { + m_state.erase(lhs, rhs); } else { - m_ofs.get() << std::forward(v); + std::swap(*lhs, *rhs); } + } + + return result; + } + +private: + std::vector m_state {}; +}; +} // namespace detail + +class toansi_ostream +{ + std::reference_wrapper m_ofs; + +public: + toansi_ostream(toansi_ostream&&) = default; + toansi_ostream(const toansi_ostream&) = default; + toansi_ostream& operator=(toansi_ostream&&) = default; + toansi_ostream& operator=(const toansi_ostream&) = default; + + toansi_ostream(std::ostream& stream) + : m_ofs(stream) + { + } + + toansi_ostream(std::reference_wrapper stream) + : m_ofs(stream) + { + } + + template + requires has_stream_insertion_operator + toansi_ostream& operator<<(T&& v) + { + if constexpr (std::convertible_to) { + m_ofs.get() << utils::utf8_to_ansi(std::forward(v)); + } + else { + m_ofs.get() << std::forward(v); + } + return *this; + } + + toansi_ostream& operator<<(std::ostream& (*pf)(std::ostream&)) + { + m_ofs.get() << pf; + return *this; + } +}; + +template +class ostreams +{ + std::tuple m_ofss; + +public: + ostreams(ostreams&&) = default; + ostreams(const ostreams&) = default; + ostreams& operator=(ostreams&&) = default; + ostreams& operator=(const ostreams&) = default; + + ostreams(Args&&... args) + : m_ofss(std::forward(args)...) + { + } + + template + requires has_stream_insertion_operator + ostreams& operator<<(T&& x) + { + streams_put(m_ofss, x, std::index_sequence_for {}); + return *this; + } + + template + static void streams_put(Tuple& t, T&& x, std::index_sequence) + { + ((convert_reference_wrapper(std::get(t)) << std::forward(x)), ...); + } + + ostreams& operator<<(std::ostream& (*pf)(std::ostream&)) + { + streams_put(m_ofss, pf, std::index_sequence_for {}); + return *this; + } + + template + static void + streams_put(Tuple& t, std::ostream& (*pf)(std::ostream&), std::index_sequence) + { + ((convert_reference_wrapper(std::get(t)) << pf), ...); + } +}; + +template +ostreams(Args&&...) -> ostreams...>; + +class Logger : public SingletonHolder +{ +public: + struct separator + { + constexpr separator() = default; + constexpr separator(const separator&) = default; + constexpr separator(separator&&) noexcept = default; + + constexpr explicit separator(std::string_view s) noexcept + : str(s) + { + } + + constexpr separator& operator=(const separator&) = default; + constexpr separator& operator=(separator&&) noexcept = default; + + constexpr separator& operator=(std::string_view s) noexcept + { + str = s; return *this; } - toansi_ostream& operator<<(std::ostream& (*pf)(std::ostream&)) - { - m_ofs.get() << pf; - return *this; - } + static const separator none; + static const separator space; + static const separator tab; + static const separator newline; + static const separator comma; + + std::string_view str; }; - template - class ostreams + struct level { - std::tuple m_ofss; + constexpr level(const level&) = default; + constexpr level(level&&) noexcept = default; + constexpr explicit level(std::string_view s) noexcept + : str(s) + { + } + + constexpr level& operator=(const level&) = default; + constexpr level& operator=(level&&) noexcept = default; + + constexpr level& operator=(std::string_view s) noexcept + { + str = s; + return *this; + } + + static const level debug; + static const level trace; + static const level info; + static const level warn; + static const level error; + + std::string_view str; + }; + + template + class LogStream + { public: - ostreams(ostreams&&) = default; - ostreams(const ostreams&) = default; - ostreams& operator=(ostreams&&) = default; - ostreams& operator=(const ostreams&) = default; - - ostreams(Args&&... args) : m_ofss(std::forward(args)...) {} - template - requires has_stream_insertion_operator - ostreams& operator<<(T&& x) + LogStream& operator<<(T&& arg) { - streams_put(m_ofss, x, std::index_sequence_for {}); + if constexpr (std::same_as>) { + m_sep = std::forward(arg); + } + else { + // 如果是 level,则不输出 separator + if constexpr (!std::same_as>) { + stream_put(m_ofs, m_sep.str); + } + stream_put(m_ofs, std::forward(arg)); + } return *this; } - template - static void streams_put(Tuple& t, T&& x, std::index_sequence) + + template + LogStream(std::mutex& mtx, _stream_t&& ofs, Logger::level lv) + : m_trace_lock(mtx) + , m_ofs(ofs) { - ((convert_reference_wrapper(std::get(t)) << std::forward(x)), ...); + *this << lv; } - ostreams& operator<<(std::ostream& (*pf)(std::ostream&)) + template + LogStream(std::mutex& mtx, _stream_t&& ofs, Logger::level lv, Args&&... buff) + : m_trace_lock(mtx) + , m_ofs(ofs) { - streams_put(m_ofss, pf, std::index_sequence_for {}); - return *this; + ((*this << lv) << ... << std::forward(buff)); } - template - static void streams_put(Tuple& t, std::ostream& (*pf)(std::ostream&), std::index_sequence) + + template + LogStream( + std::unique_lock&& lock, + _stream_t&& ofs, + Logger::level lv, + Args&&... buff) + : m_trace_lock(std::move(lock)) + , m_ofs(ofs) { - ((convert_reference_wrapper(std::get(t)) << pf), ...); + ((*this << lv) << ... << std::forward(buff)); } - }; - template - ostreams(Args&&...) -> ostreams...>; + LogStream(LogStream&&) = delete; + LogStream(const LogStream&) = delete; + LogStream& operator=(LogStream&&) = delete; + LogStream& operator=(const LogStream&) = delete; - class Logger : public SingletonHolder - { - public: - struct separator + ~LogStream() { m_ofs << std::endl; } + + private: + template + static Stream& stream_put(Stream& s, T&& v) { - constexpr separator() = default; - constexpr separator(const separator&) = default; - constexpr separator(separator&&) noexcept = default; - constexpr explicit separator(std::string_view s) noexcept : str(s) {} - constexpr separator& operator=(const separator&) = default; - constexpr separator& operator=(separator&&) noexcept = default; - constexpr separator& operator=(std::string_view s) noexcept - { - str = s; - return *this; + if constexpr (std::same_as>) { + s << utils::path_to_utf8_string(std::forward(v)); } - - static const separator none; - static const separator space; - static const separator tab; - static const separator newline; - static const separator comma; - - std::string_view str; - }; - - struct level - { - constexpr level(const level&) = default; - constexpr level(level&&) noexcept = default; - constexpr explicit level(std::string_view s) noexcept : str(s) {} - constexpr level& operator=(const level&) = default; - constexpr level& operator=(level&&) noexcept = default; - constexpr level& operator=(std::string_view s) noexcept - { - str = s; - return *this; - } - - static const level debug; - static const level trace; - static const level info; - static const level warn; - static const level error; - - std::string_view str; - }; - - template - class LogStream - { - public: - template - LogStream& operator<<(T&& arg) - { - if constexpr (std::same_as>) { - m_sep = std::forward(arg); - } - else { - // 如果是 level,则不输出 separator - if constexpr (!std::same_as>) { - stream_put(m_ofs, m_sep.str); - } - stream_put(m_ofs, std::forward(arg)); - } - return *this; - } - - template - LogStream(std::mutex& mtx, _stream_t&& ofs, Logger::level lv) : m_trace_lock(mtx), m_ofs(ofs) - { - *this << lv; - } - template - LogStream(std::mutex& mtx, _stream_t&& ofs, Logger::level lv, Args&&... buff) - : m_trace_lock(mtx), m_ofs(ofs) - { - ((*this << lv) << ... << std::forward(buff)); - } - template - LogStream(std::unique_lock&& lock, _stream_t&& ofs, Logger::level lv, Args&&... buff) - : m_trace_lock(std::move(lock)), m_ofs(ofs) - { - ((*this << lv) << ... << std::forward(buff)); - } - LogStream(LogStream&&) = delete; - LogStream(const LogStream&) = delete; - LogStream& operator=(LogStream&&) = delete; - LogStream& operator=(const LogStream&) = delete; - - ~LogStream() { m_ofs << std::endl; } - - private: - template - static Stream& stream_put(Stream& s, T&& v) - { - if constexpr (std::same_as>) { - s << utils::path_to_utf8_string(std::forward(v)); - } - else if constexpr (std::same_as>) { - constexpr int buff_len = 128; - char buff[buff_len] = { 0 }; + else if constexpr (std::same_as>) { + constexpr int buff_len = 128; + char buff[buff_len] = { 0 }; #ifdef _WIN32 #ifdef _MSC_VER - sprintf_s(buff, buff_len, + sprintf_s( + buff, + buff_len, #else // ! _MSC_VER - sprintf(buff, + sprintf( + buff, #endif // END _MSC_VER - "[%s][%s][Px%x][Tx%4.4lx]", asst::utils::get_format_time().c_str(), v.str.data(), m_pid, - m_tid); + "[%s][%s][Px%x][Tx%4.4lx]", + asst::utils::get_format_time().c_str(), + v.str.data(), + m_pid, + m_tid); #else // ! _WIN32 - sprintf(buff, "[%s][%s][Px%x][Tx%4.4hx]", asst::utils::get_format_time().c_str(), v.str.data(), - m_pid, m_tid); + sprintf( + buff, + "[%s][%s][Px%x][Tx%4.4hx]", + asst::utils::get_format_time().c_str(), + v.str.data(), + m_pid, + m_tid); #endif // END _WIN32 - s << buff; - } - else if constexpr (std::is_enum_v && enum_could_to_string) { - s << asst::enum_to_string(std::forward(v)); - } - else if constexpr (has_stream_insertion_operator) { - s << std::forward(v); - } - else if constexpr (std::constructible_from) { - s << std::string(std::forward(v)); - } - else if constexpr (ranges::input_range) { - s << "["; - std::string_view comma_space {}; - for (const auto& elem : std::forward(v)) { - s << comma_space; - stream_put(s, elem); - comma_space = ", "; - } - s << "]"; - } - else { - ASST_STATIC_ASSERT_FALSE( - "unsupported type, one of the following expected\n" - "\t1. those can be converted to string;\n" - "\t2. those can be inserted to stream with operator<< directly;\n" - "\t3. container or nested container containing 1. 2. or 3. and iterable with range-based for", - Stream, T); - } - return s; + s << buff; } - - separator m_sep = separator::space; - std::unique_lock m_trace_lock; - stream_t m_ofs; - - inline static thread_local const auto m_pid = -#ifdef _WIN32 - _getpid(); -#else - ::getpid(); -#endif - - inline static thread_local const auto m_tid = -#ifdef _WIN32 - ::GetCurrentThreadId(); -#else - static_cast(std::hash {}(std::this_thread::get_id())); -#endif - }; - - // template - // LogStream(std::mutex&, stream_t&) -> LogStream; - - // template - // LogStream(std::mutex&, stream_t&&) -> LogStream; - - template - LogStream(std::mutex&, stream_t&, Args&&...) -> LogStream; - - template - LogStream(std::mutex&, stream_t&&, Args&&...) -> LogStream; - - template - LogStream(std::unique_lock&&, stream_t&&, Args&&...) -> LogStream; - - public: - virtual ~Logger() override { flush(false); } - - // static bool set_directory(const std::filesystem::path& dir) - // { - // if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) { - // return false; - // } - // m_directory = dir; - - // return true; - // } - - template - auto operator<<(T&& arg) - { - if (!m_ofs || !m_ofs.is_open()) { - m_ofs = std::ofstream(m_log_path, std::ios::out | std::ios::app); + else if constexpr (std::is_enum_v && enum_could_to_string) { + s << asst::enum_to_string(std::forward(v)); } - if constexpr (std::same_as>) { -#ifdef ASST_DEBUG - return LogStream(m_trace_mutex, ostreams { toansi_ostream(std::cout), m_ofs }, arg); -#else - return LogStream(m_trace_mutex, m_ofs, arg); -#endif + else if constexpr (has_stream_insertion_operator) { + s << std::forward(v); + } + else if constexpr (std::constructible_from) { + s << std::string(std::forward(v)); + } + else if constexpr (ranges::input_range) { + s << "["; + std::string_view comma_space {}; + for (const auto& elem : std::forward(v)) { + s << comma_space; + stream_put(s, elem); + comma_space = ", "; + } + s << "]"; } else { -#ifdef ASST_DEBUG - return LogStream(m_trace_mutex, ostreams { toansi_ostream(std::cout), m_ofs }, level::trace, arg); -#else - return LogStream(m_trace_mutex, m_ofs, level::trace, arg); -#endif + ASST_STATIC_ASSERT_FALSE( + "unsupported type, one of the following expected\n" + "\t1. those can be converted to string;\n" + "\t2. those can be inserted to stream with operator<< directly;\n" + "\t3. container or nested container containing 1. 2. or 3. and iterable with " + "range-based for", + Stream, + T); } + return s; } + separator m_sep = separator::space; + std::unique_lock m_trace_lock; + stream_t m_ofs; + + inline static thread_local const auto m_pid = +#ifdef _WIN32 + _getpid(); +#else + ::getpid(); +#endif + + inline static thread_local const auto m_tid = +#ifdef _WIN32 + ::GetCurrentThreadId(); +#else + static_cast(std::hash {}(std::this_thread::get_id())); +#endif + }; + + // template + // LogStream(std::mutex&, stream_t&) -> LogStream; + + // template + // LogStream(std::mutex&, stream_t&&) -> LogStream; + + template + LogStream(std::mutex&, stream_t&, Args&&...) -> LogStream; + + template + LogStream(std::mutex&, stream_t&&, Args&&...) -> LogStream; + + template + LogStream(std::unique_lock&&, stream_t&&, Args&&...) -> LogStream; + +public: + virtual ~Logger() override { flush(false); } + + // static bool set_directory(const std::filesystem::path& dir) + // { + // if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) { + // return false; + // } + // m_directory = dir; + + // return true; + // } + + template + auto operator<<(T&& arg) + { + if (!m_ofs || !m_ofs.is_open()) { + m_ofs = std::ofstream(m_log_path, std::ios::out | std::ios::app); + } + if constexpr (std::same_as>) { +#ifdef ASST_DEBUG + return LogStream(m_trace_mutex, ostreams { toansi_ostream(std::cout), m_ofs }, arg); +#else + return LogStream(m_trace_mutex, m_ofs, arg); +#endif + } + else { +#ifdef ASST_DEBUG + return LogStream( + m_trace_mutex, + ostreams { toansi_ostream(std::cout), m_ofs }, + level::trace, + arg); +#else + return LogStream(m_trace_mutex, m_ofs, level::trace, arg); +#endif + } + } + #ifdef ASST_DEBUG #define LOGGER_FUNC_WITH_LEVEL(lv) \ template \ @@ -527,186 +607,205 @@ namespace asst } #endif - LOGGER_FUNC_WITH_LEVEL(trace) - LOGGER_FUNC_WITH_LEVEL(info) - LOGGER_FUNC_WITH_LEVEL(warn) - LOGGER_FUNC_WITH_LEVEL(error) - template - inline void debug([[maybe_unused]] Args&&... args) - { + LOGGER_FUNC_WITH_LEVEL(trace) + LOGGER_FUNC_WITH_LEVEL(info) + LOGGER_FUNC_WITH_LEVEL(warn) + LOGGER_FUNC_WITH_LEVEL(error) + + template + inline void debug([[maybe_unused]] Args&&... args) + { #ifdef ASST_DEBUG - std::unique_lock lock { m_trace_mutex }; - log(std::move(lock), level::debug, std::forward(args)...); + std::unique_lock lock { m_trace_mutex }; + log(std::move(lock), level::debug, std::forward(args)...); #endif - } + } #undef LOGGER_FUNC_WITH_LEVEL - template - inline int push(Args&&... args) - { - int id = -1; - std::unique_lock lock { m_trace_mutex }; - log(std::move(lock), level::trace, m_scopes.push(id), std::forward(args)...); - return id; - } - template - inline void pop(int id, Args&&... args) - { - std::unique_lock lock { m_trace_mutex }; - log(std::move(lock), level::trace, m_scopes.pop(id), std::forward(args)...); - } + template + inline int push(Args&&... args) + { + int id = -1; + std::unique_lock lock { m_trace_mutex }; + log(std::move(lock), level::trace, m_scopes.push(id), std::forward(args)...); + return id; + } - template - inline void log(level lv, Args&&... args) - { - ((*this << lv) << ... << std::forward(args)); + template + inline void pop(int id, Args&&... args) + { + std::unique_lock lock { m_trace_mutex }; + log(std::move(lock), level::trace, m_scopes.pop(id), std::forward(args)...); + } + + template + inline void log(level lv, Args&&... args) + { + ((*this << lv) << ... << std::forward(args)); + } + + template + inline void log(std::unique_lock&& lock, level lv, Args&&... args) + { + if (!m_ofs || !m_ofs.is_open()) { + m_ofs = std::ofstream(m_log_path, std::ios::out | std::ios::app); } - template - inline void log(std::unique_lock&& lock, level lv, Args&&... args) - { - if (!m_ofs || !m_ofs.is_open()) { - m_ofs = std::ofstream(m_log_path, std::ios::out | std::ios::app); - } - (LogStream(std::move(lock), + (LogStream( + std::move(lock), #ifdef ASST_DEBUG - ostreams { toansi_ostream(std::cout), m_ofs }, + ostreams { toansi_ostream(std::cout), m_ofs }, #else - m_ofs, + m_ofs, #endif - lv) - << ... << std::forward(args)); + lv) + << ... << std::forward(args)); + } + + void flush(bool rorate_log_file = true) + { + std::unique_lock m_trace_lock(m_trace_mutex); + if (m_ofs.is_open()) { + m_ofs.close(); } - - void flush(bool rorate_log_file = true) - { - std::unique_lock m_trace_lock(m_trace_mutex); - if (m_ofs.is_open()) { - m_ofs.close(); - } - if (rorate_log_file) { - rotate(); - } - } - - private: - friend class SingletonHolder; - - Logger() : m_directory(UserDir.get()) - { - std::filesystem::create_directories(m_log_path.parent_path()); + if (rorate_log_file) { rotate(); - log_init_info(); } + } - void rotate() const - { - constexpr uintmax_t MaxLogSize = 4ULL * 1024 * 1024; - try { - if (std::filesystem::exists(m_log_path) && std::filesystem::is_regular_file(m_log_path)) { - const uintmax_t log_size = std::filesystem::file_size(m_log_path); - if (log_size >= MaxLogSize) { - std::filesystem::rename(m_log_path, m_log_bak_path); - } +private: + friend class SingletonHolder; + + Logger() + : m_directory(UserDir.get()) + { + std::filesystem::create_directories(m_log_path.parent_path()); + rotate(); + log_init_info(); + } + + void rotate() const + { + constexpr uintmax_t MaxLogSize = 4ULL * 1024 * 1024; + try { + if (std::filesystem::exists(m_log_path) + && std::filesystem::is_regular_file(m_log_path)) { + const uintmax_t log_size = std::filesystem::file_size(m_log_path); + if (log_size >= MaxLogSize) { + std::filesystem::rename(m_log_path, m_log_bak_path); } } - catch (std::filesystem::filesystem_error& e) { - std::cerr << e.what() << std::endl; - } - catch (...) { - } } - void log_init_info() - { - trace("-----------------------------"); - trace("MaaCore Process Start"); - trace("Version", asst::Version); - trace("Built at", __DATE__, __TIME__); - trace("User Dir", m_directory); - trace("-----------------------------"); + catch (std::filesystem::filesystem_error& e) { + std::cerr << e.what() << std::endl; } + catch (...) { + } + } - detail::scope_slice m_scopes; - - std::filesystem::path m_directory; - - std::filesystem::path m_log_path = m_directory / "debug" / "asst.log"; - std::filesystem::path m_log_bak_path = m_directory / "debug" / "asst.bak.log"; - std::mutex m_trace_mutex; - std::ofstream m_ofs; - }; - - inline constexpr Logger::separator Logger::separator::none; - inline constexpr Logger::separator Logger::separator::space(" "); - inline constexpr Logger::separator Logger::separator::tab("\t"); - inline constexpr Logger::separator Logger::separator::newline("\n"); - inline constexpr Logger::separator Logger::separator::comma(","); - - inline constexpr Logger::level Logger::level::debug("DBG"); - inline constexpr Logger::level Logger::level::trace("TRC"); - inline constexpr Logger::level Logger::level::info("INF"); - inline constexpr Logger::level Logger::level::warn("WRN"); - inline constexpr Logger::level Logger::level::error("ERR"); - - class LoggerAux + void log_init_info() { - public: - explicit LoggerAux(std::string_view func_name) - : m_func_name(func_name), m_start_time(std::chrono::steady_clock::now()) - { -#ifdef ASST_DEBUG - m_id = Logger::get_instance().push -#else - Logger::get_instance().trace -#endif - (m_func_name, "| enter"); - } - ~LoggerAux() - { - const auto duration = std::chrono::steady_clock::now() - m_start_time; -#ifdef ASST_DEBUG - Logger::get_instance().pop(m_id, -#else - Logger::get_instance().trace( -#endif - m_func_name, "| leave,", - std::chrono::duration_cast(duration).count(), "ms"); - } - LoggerAux(const LoggerAux&) = default; - LoggerAux(LoggerAux&&) = default; - LoggerAux& operator=(const LoggerAux&) = default; - LoggerAux& operator=(LoggerAux&&) = default; + trace("-----------------------------"); + trace("MaaCore Process Start"); + trace("Version", asst::Version); + trace("Built at", __DATE__, __TIME__); + trace("User Dir", m_directory); + trace("-----------------------------"); + } - private: - std::string m_func_name; - std::chrono::time_point m_start_time; - int m_id [[maybe_unused]] = -1; - }; + detail::scope_slice m_scopes; + + std::filesystem::path m_directory; + + std::filesystem::path m_log_path = m_directory / "debug" / "asst.log"; + std::filesystem::path m_log_bak_path = m_directory / "debug" / "asst.bak.log"; + std::mutex m_trace_mutex; + std::ofstream m_ofs; +}; + +inline constexpr Logger::separator Logger::separator::none; +inline constexpr Logger::separator Logger::separator::space(" "); +inline constexpr Logger::separator Logger::separator::tab("\t"); +inline constexpr Logger::separator Logger::separator::newline("\n"); +inline constexpr Logger::separator Logger::separator::comma(","); + +inline constexpr Logger::level Logger::level::debug("DBG"); +inline constexpr Logger::level Logger::level::trace("TRC"); +inline constexpr Logger::level Logger::level::info("INF"); +inline constexpr Logger::level Logger::level::warn("WRN"); +inline constexpr Logger::level Logger::level::error("ERR"); + +class LoggerAux +{ +public: + explicit LoggerAux(std::string_view func_name) + : m_func_name(func_name) + , m_start_time(std::chrono::steady_clock::now()) + { +#ifdef ASST_DEBUG + m_id = Logger::get_instance().push +#else + Logger::get_instance().trace +#endif + (m_func_name, "| enter"); + } + + ~LoggerAux() + { + const auto duration = std::chrono::steady_clock::now() - m_start_time; +#ifdef ASST_DEBUG + Logger::get_instance().pop( + m_id, +#else + Logger::get_instance().trace( +#endif + m_func_name, + "| leave,", + std::chrono::duration_cast(duration).count(), + "ms"); + } + + LoggerAux(const LoggerAux&) = default; + LoggerAux(LoggerAux&&) = default; + LoggerAux& operator=(const LoggerAux&) = default; + LoggerAux& operator=(LoggerAux&&) = default; + +private: + std::string m_func_name; + std::chrono::time_point m_start_time; + int m_id [[maybe_unused]] = -1; +}; #define _Cat_(a, b) a##b #define _Cat(a, b) _Cat_(a, b) #define _CatVarNameWithLine(Var) _Cat(Var, __LINE__) -#define Log Logger::get_instance() -#define LogDebug Log << Logger::level::debug -#define LogTrace Log << Logger::level::trace -#define LogInfo Log << Logger::level::info -#define LogWarn Log << Logger::level::warn -#define LogError Log << Logger::level::error +#define Log asst::Logger::get_instance() +#define LogDebug Log << asst::Logger::level::debug +#define LogTrace Log << asst::Logger::level::trace +#define LogInfo Log << asst::Logger::level::info +#define LogWarn Log << asst::Logger::level::warn +#define LogError Log << asst::Logger::level::error #define LogTraceScope LoggerAux _CatVarNameWithLine(_func_aux_) #ifndef _MSC_VER - inline constexpr std::string_view summarize_pretty_function(std::string_view pf) // can be consteval? - { - // unable to handle something like std::function gen_func() - const auto paren = pf.find_first_of('('); - if (paren != std::string_view::npos) pf.remove_suffix(pf.size() - paren); - const auto space = pf.find_last_of(' '); - if (space != std::string_view::npos) pf.remove_prefix(space + 1); - return pf; +inline constexpr std::string_view + summarize_pretty_function(std::string_view pf) // can be consteval? +{ + // unable to handle something like std::function gen_func() + const auto paren = pf.find_first_of('('); + if (paren != std::string_view::npos) { + pf.remove_suffix(pf.size() - paren); } - // TODO: use std::source_location + const auto space = pf.find_last_of(' '); + if (space != std::string_view::npos) { + pf.remove_prefix(space + 1); + } + return pf; +} + +// TODO: use std::source_location #define LogTraceFunction \ LogTraceScope \ { \ @@ -716,5 +815,11 @@ namespace asst #define LogTraceFunction LogTraceScope(__FUNCTION__) #endif -#define LogTraceFunctionWithArgs // how to do this?, like LogTraceScope(__FUNCTION__, __FUNCTION_ALL_ARGS__) +#define VAR_RAW(x) "[" << #x << "=" << (x) << "] " +#define VAR(x) Logger::separator::none << VAR_RAW(x) << Logger::separator::space +#define VAR_VOIDP_RAW(x) "[" << #x << "=" << reinterpret_cast(x) << "] " +#define VAR_VOIDP(x) Logger::separator::none << VAR_VOIDP_RAW(x) << Logger::separator::space + +#define LogTraceFunctionWithArgs // how to do this?, like LogTraceScope(__FUNCTION__, + // __FUNCTION_ALL_ARGS__) } // namespace asst diff --git a/src/MaaCore/Utils/Platform/PlatformWin32.cpp b/src/MaaCore/Utils/Platform/PlatformWin32.cpp index 88970ab421..63b4f166b7 100644 --- a/src/MaaCore/Utils/Platform/PlatformWin32.cpp +++ b/src/MaaCore/Utils/Platform/PlatformWin32.cpp @@ -28,21 +28,45 @@ void asst::platform::aligned_free(void* ptr) _aligned_free(ptr); } -bool asst::win32::CreateOverlappablePipe(HANDLE* read, HANDLE* write, SECURITY_ATTRIBUTES* secattr_read, - SECURITY_ATTRIBUTES* secattr_write, DWORD bufsize, bool overlapped_read, - bool overlapped_write) +bool asst::win32::CreateOverlappablePipe( + HANDLE* read, + HANDLE* write, + SECURITY_ATTRIBUTES* secattr_read, + SECURITY_ATTRIBUTES* secattr_write, + DWORD bufsize, + bool overlapped_read, + bool overlapped_write) { static std::atomic pipeid {}; auto pipename = std::format(L"\\\\.\\pipe\\maa-pipe-{}-{}", GetCurrentProcessId(), pipeid++); DWORD read_flag = PIPE_ACCESS_INBOUND; - if (overlapped_read) read_flag |= FILE_FLAG_OVERLAPPED; + if (overlapped_read) { + read_flag |= FILE_FLAG_OVERLAPPED; + } DWORD write_flag = GENERIC_WRITE; - if (overlapped_write) write_flag |= FILE_FLAG_OVERLAPPED; - auto pipe_read = - CreateNamedPipeW(pipename.c_str(), read_flag, PIPE_TYPE_BYTE | PIPE_WAIT, 1, bufsize, bufsize, 0, secattr_read); - if (pipe_read == INVALID_HANDLE_VALUE) return false; - auto pipe_write = - CreateFileW(pipename.c_str(), write_flag, 0, secattr_write, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (overlapped_write) { + write_flag |= FILE_FLAG_OVERLAPPED; + } + auto pipe_read = CreateNamedPipeW( + pipename.c_str(), + read_flag, + PIPE_TYPE_BYTE | PIPE_WAIT, + 1, + bufsize, + bufsize, + 0, + secattr_read); + if (pipe_read == INVALID_HANDLE_VALUE) { + return false; + } + auto pipe_write = CreateFileW( + pipename.c_str(), + write_flag, + 0, + secattr_write, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); if (pipe_write == INVALID_HANDLE_VALUE) { CloseHandle(pipe_read); return false; @@ -58,10 +82,15 @@ static std::string get_ansi_short_path(const std::filesystem::path& path) wchar_t short_path[MAX_PATH] {}; auto& osstr = path.native(); auto shortlen = GetShortPathNameW(osstr.c_str(), short_path, MAX_PATH); - if (shortlen == 0) return {}; + if (shortlen == 0) { + return {}; + } BOOL failed = FALSE; - auto ansilen = WideCharToMultiByte(CP_ACP, 0, short_path, shortlen, nullptr, 0, nullptr, &failed); - if (failed) return {}; + auto ansilen = + WideCharToMultiByte(CP_ACP, 0, short_path, shortlen, nullptr, 0, nullptr, &failed); + if (failed) { + return {}; + } std::string result(ansilen, 0); WideCharToMultiByte(CP_ACP, 0, short_path, shortlen, result.data(), ansilen, nullptr, nullptr); return result; @@ -77,7 +106,9 @@ std::string asst::platform::path_to_crt_string(const std::filesystem::path& path if (err == 0) { std::string result(mbsize, 0); err = wcstombs_s(&mbsize, result.data(), mbsize, osstr.c_str(), osstr.size()); - if (err != 0) return {}; + if (err != 0) { + return {}; + } return result.substr(0, mbsize - 1); } else { @@ -92,10 +123,26 @@ std::string asst::platform::path_to_ansi_string(const std::filesystem::path& pat // so we use CRT wcstombs instead of WideCharToMultiByte BOOL failed = FALSE; auto& osstr = path.native(); - auto ansilen = WideCharToMultiByte(CP_ACP, 0, osstr.c_str(), (int)osstr.size(), nullptr, 0, nullptr, &failed); + auto ansilen = WideCharToMultiByte( + CP_ACP, + 0, + osstr.c_str(), + (int)osstr.size(), + nullptr, + 0, + nullptr, + &failed); if (!failed) { std::string result(ansilen, 0); - WideCharToMultiByte(CP_ACP, 0, osstr.c_str(), (int)osstr.size(), result.data(), ansilen, nullptr, &failed); + WideCharToMultiByte( + CP_ACP, + 0, + osstr.c_str(), + (int)osstr.size(), + result.data(), + ansilen, + nullptr, + &failed); return result; } else { @@ -114,9 +161,25 @@ asst::platform::os_string asst::platform::to_osstring(const std::string& utf8_st std::string asst::platform::from_osstring(const os_string& os_str) { - int len = WideCharToMultiByte(CP_UTF8, 0, os_str.c_str(), (int)os_str.size(), nullptr, 0, nullptr, nullptr); + int len = WideCharToMultiByte( + CP_UTF8, + 0, + os_str.c_str(), + (int)os_str.size(), + nullptr, + 0, + nullptr, + nullptr); std::string result(len, 0); - WideCharToMultiByte(CP_UTF8, 0, os_str.c_str(), (int)os_str.size(), result.data(), len, nullptr, nullptr); + WideCharToMultiByte( + CP_UTF8, + 0, + os_str.c_str(), + (int)os_str.size(), + result.data(), + len, + nullptr, + nullptr); return result; } @@ -127,11 +190,18 @@ std::string asst::platform::call_command(const std::string& cmdline, bool* exit_ std::string pipe_str; HANDLE pipe_parent_read = INVALID_HANDLE_VALUE, pipe_child_write = INVALID_HANDLE_VALUE; - SECURITY_ATTRIBUTES sa_inherit { .nLength = sizeof(SECURITY_ATTRIBUTES), .bInheritHandle = TRUE }; - if (!asst::win32::CreateOverlappablePipe(&pipe_parent_read, &pipe_child_write, nullptr, &sa_inherit, PipeBuffSize, - true, false)) { + SECURITY_ATTRIBUTES sa_inherit { .nLength = sizeof(SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE }; + if (!asst::win32::CreateOverlappablePipe( + &pipe_parent_read, + &pipe_child_write, + nullptr, + &sa_inherit, + PipeBuffSize, + true, + false)) { DWORD err = GetLastError(); - asst::Log.error("CreateOverlappablePipe failed, err", err); + Log.error("CreateOverlappablePipe failed, err", err); return {}; } @@ -150,7 +220,11 @@ std::string asst::platform::call_command(const std::string& cmdline, bool* exit_ InitializeProcThreadAttributeList(nullptr, 1, 0, &attrsize); if (attrsize == 0) { DWORD err = GetLastError(); - Log.error("Call `", cmdline, "` InitializeProcThreadAttributeList failed, ret error code:", err); + Log.error( + "Call `", + cmdline, + "` InitializeProcThreadAttributeList failed, ret error code:", + err); return {}; } attrs.resize(attrsize); @@ -158,22 +232,48 @@ std::string asst::platform::call_command(const std::string& cmdline, bool* exit_ auto attr_success = InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrsize); if (!attr_success) { DWORD err = GetLastError(); - Log.error("Call `", cmdline, "` InitializeProcThreadAttributeList failed, ret error code:", err); + Log.error( + "Call `", + cmdline, + "` InitializeProcThreadAttributeList failed, ret error code:", + err); return {}; } - attr_success = UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &pipe_child_write, sizeof(HANDLE), nullptr, nullptr); + attr_success = UpdateProcThreadAttribute( + si.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + &pipe_child_write, + sizeof(HANDLE), + nullptr, + nullptr); if (!attr_success) { DWORD err = GetLastError(); Log.error("Call `", cmdline, "` UpdateProcThreadAttribute failed, ret error code:", err); return {}; } auto cmdline_osstr = to_osstring(cmdline); - BOOL create_ret = - CreateProcessW(nullptr, cmdline_osstr.data(), nullptr, nullptr, TRUE, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &si.StartupInfo, &process_info); + BOOL create_ret = CreateProcessW( + nullptr, + cmdline_osstr.data(), + nullptr, + nullptr, + TRUE, + EXTENDED_STARTUPINFO_PRESENT, + nullptr, + nullptr, + &si.StartupInfo, + &process_info); DeleteProcThreadAttributeList(si.lpAttributeList); if (!create_ret) { DWORD err = GetLastError(); - Log.error("Call `", cmdline, "` create process failed, ret", create_ret, "error code:", err); + Log.error( + "Call `", + cmdline, + "` create process failed, ret", + create_ret, + "error code:", + err); return {}; } @@ -189,11 +289,21 @@ std::string asst::platform::call_command(const std::string& cmdline, bool* exit_ while (!(exit_flag && *exit_flag)) { wait_handles.clear(); - if (process_running) wait_handles.push_back(process_info.hProcess); - if (!pipe_eof) wait_handles.push_back(pipeov.hEvent); - if (wait_handles.empty()) break; - auto wait_result = - WaitForMultipleObjectsEx((DWORD)wait_handles.size(), wait_handles.data(), FALSE, INFINITE, TRUE); + if (process_running) { + wait_handles.push_back(process_info.hProcess); + } + if (!pipe_eof) { + wait_handles.push_back(pipeov.hEvent); + } + if (wait_handles.empty()) { + break; + } + auto wait_result = WaitForMultipleObjectsEx( + (DWORD)wait_handles.size(), + wait_handles.data(), + FALSE, + INFINITE, + TRUE); HANDLE signaled_object = INVALID_HANDLE_VALUE; if (wait_result >= WAIT_OBJECT_0 && wait_result < WAIT_OBJECT_0 + wait_handles.size()) { signaled_object = wait_handles[(size_t)wait_result - WAIT_OBJECT_0]; @@ -257,7 +367,9 @@ struct REPARSE_DATA_BUFFER DWORD ReparseTag; WORD ReparseDataLength; WORD Reserved; - union { + + union + { struct { WORD SubstituteNameOffset; @@ -266,6 +378,7 @@ struct REPARSE_DATA_BUFFER WORD PrintNameLength; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; + struct { WORD SubstituteNameOffset; @@ -274,6 +387,7 @@ struct REPARSE_DATA_BUFFER WORD PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; + struct { BYTE DataBuffer[1]; @@ -289,7 +403,10 @@ HANDLE asst::win32::OpenDirectory(const std::filesystem::path& path, BOOL bReadW HANDLE hToken; TOKEN_PRIVILEGES tp; OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken); - LookupPrivilegeValueW(NULL, (bReadWrite ? SE_RESTORE_NAME : SE_BACKUP_NAME), &tp.Privileges[0].Luid); + LookupPrivilegeValueW( + NULL, + (bReadWrite ? SE_RESTORE_NAME : SE_BACKUP_NAME), + &tp.Privileges[0].Luid); tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); @@ -297,23 +414,36 @@ HANDLE asst::win32::OpenDirectory(const std::filesystem::path& path, BOOL bReadW // Open the directory DWORD dwAccess = bReadWrite ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ; - HANDLE hDir = CreateFileW(path.c_str(), dwAccess, 0, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + HANDLE hDir = CreateFileW( + path.c_str(), + dwAccess, + 0, + NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); return hDir; } -bool asst::win32::SetDirectoryReparsePoint(const std::filesystem::path& link, const std::filesystem::path& target) +bool asst::win32::SetDirectoryReparsePoint( + const std::filesystem::path& link, + const std::filesystem::path& target) { - auto normtarget = asst::platform::path(asst::utils::string_replace_all(target.native(), L"/", L"\\")); + auto normtarget = + asst::platform::path(asst::utils::string_replace_all(target.native(), L"/", L"\\")); auto nttarget = L"\\GLOBAL??\\" + std::filesystem::absolute(normtarget).native(); - if (nttarget.back() != L'\\') nttarget.push_back(L'\\'); + if (nttarget.back() != L'\\') { + nttarget.push_back(L'\\'); + } // set reparse point auto hReparsePoint = OpenDirectory(link.c_str(), TRUE); - if (hReparsePoint == INVALID_HANDLE_VALUE) return false; + if (hReparsePoint == INVALID_HANDLE_VALUE) { + return false; + } BYTE buf[sizeof(REPARSE_MOUNTPOINT_DATA_BUFFER) + MAX_PATH * sizeof(WCHAR)]; REPARSE_MOUNTPOINT_DATA_BUFFER& ReparseBuffer = (REPARSE_MOUNTPOINT_DATA_BUFFER&)buf; @@ -327,9 +457,15 @@ bool asst::win32::SetDirectoryReparsePoint(const std::filesystem::path& link, co ReparseBuffer.ReparseDataLength = ReparseBuffer.ReparseTargetLength + 12; // Attach reparse point - auto success = - DeviceIoControl(hReparsePoint, FSCTL_SET_REPARSE_POINT, &ReparseBuffer, - ReparseBuffer.ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE, nullptr, 0, nullptr, nullptr); + auto success = DeviceIoControl( + hReparsePoint, + FSCTL_SET_REPARSE_POINT, + &ReparseBuffer, + ReparseBuffer.ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE, + nullptr, + 0, + nullptr, + nullptr); CloseHandle(hReparsePoint); diff --git a/src/MaaWpfGui/Constants/ConfigurationKeys.cs b/src/MaaWpfGui/Constants/ConfigurationKeys.cs index 58a75126c4..aae95b0d30 100644 --- a/src/MaaWpfGui/Constants/ConfigurationKeys.cs +++ b/src/MaaWpfGui/Constants/ConfigurationKeys.cs @@ -55,6 +55,10 @@ namespace MaaWpfGui.Constants public const string ConnectAddress = "Connect.Address"; public const string AdbPath = "Connect.AdbPath"; public const string ConnectConfig = "Connect.ConnectConfig"; + public const string MuMu12ExtrasEnabled = "Connect.MuMu12Extras.Enabled"; + public const string MuMu12EmulatorPath = "Connect.MuMu12EmulatorPath"; + public const string MuMu12Index = "Connect.MuMu12Index"; + public const string MuMu12Display = "Connect.MuMu12Display"; public const string RetryOnAdbDisconnected = "Connect.RetryOnDisconnected"; public const string AllowAdbRestart = "Connect.AllowADBRestart"; public const string AllowAdbHardRestart = "Connect.AllowADBHardRestart"; diff --git a/src/MaaWpfGui/Main/AsstProxy.cs b/src/MaaWpfGui/Main/AsstProxy.cs index e087b664f7..59f8910557 100644 --- a/src/MaaWpfGui/Main/AsstProxy.cs +++ b/src/MaaWpfGui/Main/AsstProxy.cs @@ -23,6 +23,7 @@ using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; +using System.Xml.Linq; using HandyControl.Data; using MaaWpfGui.Constants; using MaaWpfGui.Extensions; @@ -100,6 +101,22 @@ namespace MaaWpfGui.Main } } + private static unsafe void AsstSetConnectionExtras(string name, string extras) + { + _logger.Information($"name: {name}, extras: {extras}"); + + fixed (byte* ptr1 = EncodeNullTerminatedUtf8(name), + ptr2 = EncodeNullTerminatedUtf8(extras)) + { + MaaService.AsstSetConnectionExtras(ptr1, ptr2); + } + } + + private static unsafe void AsstSetConnectionExtrasMuMu12(string extras) + { + AsstSetConnectionExtras("MuMuEmulator12", extras); + } + private static unsafe AsstTaskId AsstAppendTask(AsstHandle handle, string type, string taskParams) { fixed (byte* ptr1 = EncodeNullTerminatedUtf8(type), @@ -468,6 +485,7 @@ namespace MaaWpfGui.Main costString = timeCost.ToString("#,#"); color = UiLogColor.Warning; break; + case > 400: color = UiLogColor.Warning; break; @@ -478,12 +496,13 @@ namespace MaaWpfGui.Main color = UiLogColor.Error; } - fastestScreencapStringBuilder.Insert(0, string.Format(LocalizationHelper.GetString("FastestWayToScreencap"), costString)); + fastestScreencapStringBuilder.Insert(0, string.Format(LocalizationHelper.GetString("FastestWayToScreencap"), costString, method)); Instances.TaskQueueViewModel.AddLog(fastestScreencapStringBuilder.ToString(), color); Instances.CopilotViewModel.AddLog(fastestScreencapStringBuilder.ToString(), color, showTime: false); } break; + case "ScreencapCost": var screencapCostMin = details["details"]?["min"]?.ToString() ?? "???"; var screencapCostAvg = details["details"]?["avg"]?.ToString() ?? "???"; @@ -504,6 +523,7 @@ namespace MaaWpfGui.Main case >= 800: AddLog(string.Format(LocalizationHelper.GetString("FastestWayToScreencapErrorTip"), screencapCostAvgInt), UiLogColor.Warning); break; + case >= 400: AddLog(string.Format(LocalizationHelper.GetString("FastestWayToScreencapWarningTip"), screencapCostAvgInt), UiLogColor.Warning); break; @@ -1534,6 +1554,11 @@ namespace MaaWpfGui.Main /// 是否成功。 public bool AsstConnect(ref string error) { + if (Instances.SettingsViewModel.MuMuEmulator12Extras.Enable) + { + AsstSetConnectionExtrasMuMu12(Instances.SettingsViewModel.MuMuEmulator12Extras.Config); + } + if (Instances.SettingsViewModel.AutoDetectConnection) { string bsHvAddress = Instances.SettingsViewModel.TryToSetBlueStacksHyperVAddress(); diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml index 82948f7ac0..35105ae6b9 100644 --- a/src/MaaWpfGui/Res/Localizations/en-us.xaml +++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml @@ -335,6 +335,10 @@ Only supports Official(CN) and Bilibili server. Login account is not supported.< Connection address It will be filled in automatically when you use it for the first time. If you encounter any problems, you can try to manually modify it Connection Preset + Enable MuMu’s exclusive extreme speed control mode + MuMu12 Emulator exec path + Instance Number + Display Number Retry launching the emulator when ADB connection fails Try restarting the emulator after 20 failed reconnect attempts, must be checked when using a background timed task and setting the task to close the emulator when finished Emulator path is empty! Please fill in "Launch Settings - Emulator Path" and reselect. @@ -594,7 +598,7 @@ The video aspect ratio needs to be 16:9 without interference factors such as bla Reconnection failed. The connection is down! Touch mode is not available, please go to 「Settings - Connection Settings」 to switch to a different touch mode. Screencap failed. If it happens repeatedly, try to restart or change the emulator! - Screenshot test tasks: {0}ms + Screenshot test tasks: {0}ms ({1}) Screencap takes a long time (avg: {0}ms) which may cause some abnormal behaviors using automatic combat functions (e.g. Auto I. S.). Screencap takes too long (avg: {0}ms), so automatic combat functions (e.g. Auto I. S.) may not run properly. It is recommended to try restarting or changing the simulator! Recognition error diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml index ba682df756..9e1c6a48bc 100644 --- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml +++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml @@ -336,6 +336,9 @@ 接続先アドレス 問題があれば手動で修正してください 接続構成 + MuMu12 エミュレータパス + インスタンス番号 + 表示番号 ADB接続が失敗した場合、エミュレータの起動を試みる 再接続に20回失敗した後、エミュレータを再起動してみてください,バックグラウンドのタイムドタスクを使用し、終了時にエミュレータを終了させるタスクを設定する場合は、必ずチェックを入れること エミュレータのパスが空です!「起動設定-エミュレータのパス」を入力してから再度選択してください。 @@ -595,7 +598,7 @@ 再接続に失敗、切断されました! タッチモードは利用できません。設定-接続設定に入って、他のタッチモードに変更してください。 スクリーンキャプチャに失敗しました。繰り返し発生する場合は、エミュレータの再起動または変更をお試しください! - スクリーンショット テストには: {0}ms + スクリーンショット テストには: {0}ms ({1}) スクリーンキャップには時間がかかるため(平均: {0}ms)、自動戦闘機能 (Auto I.S. など) を使用すると異常な動作が発生する可能性があります。 スクリーンキャップに時間がかかりすぎるため(平均: {0}ms)、自動戦闘機能 (Auto I.S. など) が適切に動作しない可能性があります。 認識エラー diff --git a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml index 3fc2f6b6cb..0cbc2d9130 100644 --- a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml +++ b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml @@ -336,6 +336,9 @@ OF-1을 해금하지 않았다면 선택하지 말아 주세요. 연결 주소 문제 발생 시 직접 수정해 주세요 연결 프리셋 + MuMu12 에뮬레이터 경로 + 인스턴스 번호 + 표시 번호 ADB 연결이 실패하면 에뮬레이터를 다시 시도합니다 재연결을 20회 시도해도 실패하면 에뮬레이터를 다시 시작합니다. 백그라운드 예약을 사용하고 작업이 완료되면 에뮬레이터가 종료되도록 설정하려면 반드시 선택해야 합니다 에뮬레이터 경로가 비어 있습니다!「시작 설정 - 에뮬레이터 경로」를 입력한 후 다시 선택하세요 @@ -595,7 +598,7 @@ OF-1을 해금하지 않았다면 선택하지 말아 주세요. 다시 연결하지 못했습니다. 연결 해제되었습니다! 터치 수행 방식이 사용 불가능합니다. 설정 - 연결 설정에서 다른 방식으로 변경해 주세요. 스크린샷 캡처에 실패했습니다. 반복적으로 발생 시 에뮬레이터를 다시 시작하거나 변경해 보세요! - 스크린샷 캡쳐 속도: {0}ms + 스크린샷 캡쳐 속도: {0}ms ({1}) 스크린샷 캡쳐 속도가 느리므로 (평균: {0}ms) 자동 전투 기능(예: Copilot)을 사용하면 비정상적인 동작이 발생할 수 있습니다. 스크린샷 캡쳐 속도가 느리므로 (평균: {0}ms) 자동 전투 기능(예: Copilot)이 제대로 실행되지 않을 수 있습니다. 에뮬레이터를 다시 시작하거나 변경해 보는 것이 좋습니다! 인식 오류 diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml index 2fbefb9917..a029820f41 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml @@ -335,6 +335,10 @@ 连接地址 若遇到问题可尝试手动修改 连接配置 + 启用 MuMu 独家极速操控模式 + MuMu 模拟器路径 + 实例编号 + 屏幕编号 ADB 连接失败时尝试启动模拟器 重连失败 20 次后尝试重启模拟器,使用后台定时任务且设置任务完成关闭模拟器时请勾选 模拟器路径为空!请填写「启动设置-模拟器路径」后重新勾选。 @@ -594,7 +598,7 @@ 重连失败,连接断开! 触控模式不可用。请进入 设置 - 连接设置 切换其他触控模式。 截图失败,如反复出现请尝试重启或更换模拟器! - 最快截图耗时: {0}ms + 最快截图耗时: {0}ms ({1}) 截图用时较长(平均: {0}ms),自动战斗类功能(如自动肉鸽)可能表现异常 截图用时过长(平均: {0}ms),自动战斗类功能(如自动肉鸽)很可能无法正常运行,建议尝试重启或更换模拟器 识别错误 diff --git a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml index c0794e6abc..b6cae737f5 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml @@ -333,6 +333,9 @@ 我確定沒選錯 好的,我會重新選擇 連接配置 + MuMu12 模擬器路徑 + 實例號碼 + 螢幕號碼 ADB 連線失敗時嘗試啟動模擬器 重連失敗 20 次後嘗試重開模擬器,使用後台定時任務且設定任務完成關閉模擬器時必須勾選 模擬器路徑為空!請填寫「啟動設置-模擬器路徑」後重新勾選。 @@ -592,7 +595,7 @@ 重連失敗,連接斷開! 觸控模式不可用。請進入 設定 - 連接設定 切換其他觸控模式。 截圖失敗,如反覆出現請嘗試重開或更換模擬器! - 最快截圖用時: {0}ms + 最快截圖用時: {0}ms ({1}) 截圖用時較長(平均: {0}ms),自動戰鬥類功能(如自動肉鴿)時可能表現異常 截圖用時過長(平均: {0}ms),自動戰鬥類功能(如自動肉鴿)很可能無法正常運行,建議嘗試重新啟動或更換模擬器 辨識錯誤 diff --git a/src/MaaWpfGui/Services/MaaService.cs b/src/MaaWpfGui/Services/MaaService.cs index 56cf0d3b6c..74dc27dee5 100644 --- a/src/MaaWpfGui/Services/MaaService.cs +++ b/src/MaaWpfGui/Services/MaaService.cs @@ -68,5 +68,8 @@ namespace MaaWpfGui.Services [DllImport("MaaCore.dll")] public static extern bool AsstBackToHome(AsstHandle handle); + + [DllImport("MaaCore.dll")] + public static extern unsafe void AsstSetConnectionExtras(byte* name, byte* extras); } } diff --git a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs index 622af37a63..94c41924a2 100644 --- a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs @@ -21,6 +21,7 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Management; +using System.Printing; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -46,7 +47,6 @@ using MaaWpfGui.Utilities.ValueType; using Microsoft.Win32; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using ObservableCollections; using Serilog; using Stylet; using ComboBox = System.Windows.Controls.ComboBox; @@ -1799,8 +1799,10 @@ namespace MaaWpfGui.ViewModels.UI case NotifyType.ScrollOffset: SetAndNotify(ref _selectedIndex, value); break; + case NotifyType.SelectedIndex: break; + default: throw new ArgumentOutOfRangeException(); } @@ -1849,8 +1851,10 @@ namespace MaaWpfGui.ViewModels.UI case NotifyType.SelectedIndex: SetAndNotify(ref _scrollOffset, value); break; + case NotifyType.ScrollOffset: break; + default: throw new ArgumentOutOfRangeException(); } @@ -1978,6 +1982,7 @@ namespace MaaWpfGui.ViewModels.UI case true when RoguelikeUseSupportUnit: RoguelikeUseSupportUnit = false; break; + case false when RoguelikeOnlyStartWithEliteTwo: RoguelikeOnlyStartWithEliteTwo = false; break; @@ -2496,7 +2501,6 @@ namespace MaaWpfGui.ViewModels.UI } } - /* 定时设置 */ public class TimerModel @@ -3275,6 +3279,91 @@ namespace MaaWpfGui.ViewModels.UI } } + public class MuMuEmulator12ConnectionExtras : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string name = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + + private bool _enable = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.MuMu12ExtrasEnabled, bool.FalseString)); + + public bool Enable + { + get => _enable; + set + { + _enable = value; + OnPropertyChanged(); + ConfigurationHelper.SetValue(ConfigurationKeys.MuMu12ExtrasEnabled, value.ToString()); + } + } + + private string _emulatorPath = ConfigurationHelper.GetValue(ConfigurationKeys.MuMu12EmulatorPath, "C:\\Program Files\\Netease\\MuMuPlayer-12.0"); + + /// + /// Gets or sets a value indicating the path of the emulator. + /// + public string EmulatorPath + { + get => _emulatorPath; + set + { + _emulatorPath = value; + OnPropertyChanged(); + ConfigurationHelper.SetValue(ConfigurationKeys.MuMu12EmulatorPath, value); + } + } + + private string _index = ConfigurationHelper.GetValue(ConfigurationKeys.MuMu12Index, "0"); + + /// + /// Gets or sets the index of the emulator. + /// + public string Index + { + get => _index; + set + { + _index = value; + OnPropertyChanged(); + ConfigurationHelper.SetValue(ConfigurationKeys.MuMu12Index, value); + } + } + + private string _display = ConfigurationHelper.GetValue(ConfigurationKeys.MuMu12Display, "0"); + + /// + /// Gets or sets the display of the emulator. + /// + public string Display + { + get => _display; + set + { + _display = value; + OnPropertyChanged(); + ConfigurationHelper.SetValue(ConfigurationKeys.MuMu12Display, _display); + } + } + + public string Config => JsonConvert.SerializeObject(new JObject() + { + ["path"] = EmulatorPath, + ["index"] = int.TryParse(Index, out var indexParse) ? indexParse : 0, + ["display"] = int.TryParse(Display, out var displayParse) ? displayParse : 0, + }); + + public MuMuEmulator12ConnectionExtras() + { + PropertyChanged += (_, _) => { }; + } + } + + public MuMuEmulator12ConnectionExtras MuMuEmulator12Extras { get; set; } = new(); + private bool _retryOnDisconnected = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.RetryOnAdbDisconnected, bool.FalseString)); /// @@ -3415,6 +3504,7 @@ namespace MaaWpfGui.ViewModels.UI case 0: error = LocalizationHelper.GetString("EmulatorNotFound"); return false; + case > 1: error = LocalizationHelper.GetString("EmulatorTooMany"); break; @@ -3435,6 +3525,7 @@ namespace MaaWpfGui.ViewModels.UI case 1: ConnectAddress = addresses.First(); break; + case > 1: { foreach (var address in addresses.Where(address => address != "emulator-5554")) @@ -3524,6 +3615,7 @@ namespace MaaWpfGui.ViewModels.UI case "1": // 配置名 currentConfiguration = $" ({CurrentConfiguration})"; break; + case "2": // 连接模式 foreach (var data in ConnectConfigList.Where(data => data.Value == ConnectConfig)) { @@ -3531,9 +3623,11 @@ namespace MaaWpfGui.ViewModels.UI } break; + case "3": // 端口地址 connectAddress = $" ({ConnectAddress})"; break; + case "4": // 客户端类型 clientName = $" - {ClientName}"; break; diff --git a/src/MaaWpfGui/Views/UserControl/ConnectSettingsUserControl.xaml b/src/MaaWpfGui/Views/UserControl/ConnectSettingsUserControl.xaml index 6f811c0eac..ce885dfa1a 100644 --- a/src/MaaWpfGui/Views/UserControl/ConnectSettingsUserControl.xaml +++ b/src/MaaWpfGui/Views/UserControl/ConnectSettingsUserControl.xaml @@ -6,6 +6,7 @@ xmlns:controls="clr-namespace:MaaWpfGui.Styles.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dd="urn:gong-wpf-dragdrop" + xmlns:hc="https://handyorg.github.io/handycontrol" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:s="https://github.com/canton7/Stylet" xmlns:styles="clr-namespace:MaaWpfGui.Styles" @@ -13,32 +14,27 @@ xmlns:viewModels="clr-namespace:MaaWpfGui.ViewModels" xmlns:vm="clr-namespace:MaaWpfGui" d:DataContext="{d:DesignInstance {x:Type ui:SettingsViewModel}}" - d:DesignHeight="600" + d:DesignHeight="800" d:DesignWidth="550" mc:Ignorable="d"> - - - - - - - - - - - - - - - - - + + + + + + + + + + + @@ -56,45 +52,38 @@ IsChecked="{Binding AlwaysAutoDetectConnection}" Visibility="{c:Binding AutoDetectConnection}" /> - - -