mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-15 17:30:27 +08:00
style: 删除界面上关于自动编队特别关注的提示
This commit is contained in:
180
enc_temp_folder/253bfca5c8279848d022a7e97cf1a564/Controller.h
Normal file
180
enc_temp_folder/253bfca5c8279848d022a7e97cf1a564/Controller.h
Normal file
@@ -0,0 +1,180 @@
|
||||
#pragma once
|
||||
|
||||
#include "AsstConf.h"
|
||||
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <shared_mutex>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include "NoWarningCVMat.h"
|
||||
|
||||
#include "AsstTypes.h"
|
||||
#include "AsstMsg.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class Controller
|
||||
{
|
||||
public:
|
||||
Controller(AsstCallback callback, void* callback_arg);
|
||||
Controller(const Controller&) = delete;
|
||||
Controller(Controller&&) = delete;
|
||||
~Controller();
|
||||
|
||||
bool connect(const std::string& adb_path, const std::string& address, const std::string& config);
|
||||
bool release();
|
||||
bool inited() const noexcept;
|
||||
|
||||
const std::string& get_uuid() const;
|
||||
cv::Mat get_image(bool raw = false);
|
||||
std::vector<uchar> get_image_encode() const;
|
||||
|
||||
/* 开启游戏、点击和滑动都是异步执行,返回该任务的id */
|
||||
|
||||
std::optional<int> start_game(const std::string& client_type, bool block = true);
|
||||
std::optional<int> stop_game(bool block = true);
|
||||
|
||||
int click(const Point& p, bool block = true);
|
||||
int click(const Rect& rect, bool block = true);
|
||||
int click_without_scale(const Point& p, bool block = true);
|
||||
int click_without_scale(const Rect& rect, bool block = true);
|
||||
|
||||
constexpr static int SwipeExtraDelayDefault = 1000;
|
||||
int swipe(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false);
|
||||
int swipe(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false);
|
||||
int swipe_without_scale(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false);
|
||||
int swipe_without_scale(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false);
|
||||
|
||||
void wait(unsigned id) const noexcept;
|
||||
|
||||
// 异形屏矫正
|
||||
//Rect shaped_correct(const Rect& rect) const;
|
||||
std::pair<int, int> get_scale_size() const noexcept;
|
||||
|
||||
Controller& operator=(const Controller&) = delete;
|
||||
Controller& operator=(Controller&&) = delete;
|
||||
|
||||
private:
|
||||
void pipe_working_proc();
|
||||
std::optional<std::vector<uchar>> call_command(const std::string& cmd, int64_t timeout = 20000, bool recv_by_socket = false);
|
||||
int push_cmd(const std::string& cmd);
|
||||
|
||||
std::optional<unsigned short> try_to_init_socket(const std::string& local_address, unsigned short try_port, unsigned short try_times = 10U);
|
||||
|
||||
using DecodeFunc = std::function<bool(std::vector<uchar>&)>;
|
||||
bool screencap();
|
||||
bool screencap(const std::string& cmd, const DecodeFunc& decode_func, bool by_nc = false);
|
||||
void clear_lf_info();
|
||||
cv::Mat get_resized_image() const;
|
||||
|
||||
Point rand_point_in_rect(const Rect& rect);
|
||||
|
||||
void random_delay() const;
|
||||
void clear_info() noexcept;
|
||||
|
||||
// 转换data中所有的crlf为lf:有些模拟器自带的adb,exec-out输出的\n,会被替换成\r\n,导致解码错误,所以这里转一下回来(点名批评mumu)
|
||||
static void convert_lf(std::vector<uchar>& data);
|
||||
|
||||
AsstCallback m_callback;
|
||||
void* m_callback_arg = nullptr;
|
||||
|
||||
std::minstd_rand m_rand_engine;
|
||||
|
||||
constexpr static int PipeBuffSize = 4 * 1024 * 1024; // 管道缓冲区大小
|
||||
constexpr static int SocketBuffSize = 4 * 1024 * 1024; // socket 缓冲区大小
|
||||
std::unique_ptr<uchar[]> m_pipe_buffer = nullptr;
|
||||
std::mutex m_pipe_mutex;
|
||||
std::unique_ptr<char[]> m_socket_buffer = nullptr;
|
||||
std::mutex m_socket_mutex;
|
||||
#ifdef _WIN32
|
||||
HANDLE m_pipe_read = nullptr; // 读管道句柄
|
||||
HANDLE m_pipe_write = nullptr; // 写管道句柄
|
||||
HANDLE m_pipe_child_read = nullptr; // 子进程的读管道句柄
|
||||
HANDLE m_pipe_child_write = nullptr; // 子进程的写管道句柄
|
||||
|
||||
ASST_AUTO_DEDUCED_ZERO_INIT_START
|
||||
SECURITY_ATTRIBUTES m_pipe_sec_attr = { 0 }; // 管道安全描述符
|
||||
STARTUPINFOA m_child_startup_info = { 0 }; // 子进程启动信息
|
||||
|
||||
WSADATA m_wsa_data = { 0 };
|
||||
SOCKET m_server_sock = 0ULL;
|
||||
sockaddr_in m_server_addr = { 0 };
|
||||
ASST_AUTO_DEDUCED_ZERO_INIT_END
|
||||
|
||||
#else
|
||||
constexpr static int PIPE_READ = 0;
|
||||
constexpr static int PIPE_WRITE = 1;
|
||||
int m_pipe_in[2] = { 0 };
|
||||
int m_pipe_out[2] = { 0 };
|
||||
int m_child = 0;
|
||||
#endif
|
||||
|
||||
struct AdbProperty
|
||||
{
|
||||
/* command */
|
||||
std::string connect;
|
||||
std::string click;
|
||||
std::string swipe;
|
||||
|
||||
std::string screencap_raw_by_nc;
|
||||
std::string screencap_raw_with_gzip;
|
||||
std::string screencap_encode;
|
||||
std::string release;
|
||||
|
||||
std::string start;
|
||||
std::string stop;
|
||||
|
||||
/* properties */
|
||||
enum class ScreencapEndOfLine
|
||||
{
|
||||
UnknownYet,
|
||||
CRLF,
|
||||
LF,
|
||||
CR
|
||||
} screencap_end_of_line = ScreencapEndOfLine::UnknownYet;
|
||||
|
||||
enum class ScreencapMethod
|
||||
{
|
||||
UnknownYet,
|
||||
//Default,
|
||||
RawByNc,
|
||||
RawWithGzip,
|
||||
Encode
|
||||
} screencap_method = ScreencapMethod::UnknownYet;
|
||||
} m_adb;
|
||||
|
||||
std::string m_uuid;
|
||||
std::pair<int, int> m_scale_size = { WindowWidthDefault, WindowHeightDefault };
|
||||
double m_control_scale = 1.0;
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
bool m_support_socket = false;
|
||||
bool m_server_started = false;
|
||||
bool m_inited = false;
|
||||
|
||||
inline static int m_instance_count = 0;
|
||||
|
||||
mutable std::shared_mutex m_image_mutex;
|
||||
cv::Mat m_cache_image;
|
||||
|
||||
bool m_thread_exit = false;
|
||||
//bool m_thread_idle = true;
|
||||
std::mutex m_cmd_queue_mutex;
|
||||
std::condition_variable m_cmd_condvar;
|
||||
std::queue<std::string> m_cmd_queue;
|
||||
std::atomic<unsigned> m_completed_id = 0;
|
||||
unsigned m_push_id = 0; // push_id的自增总是伴随着queue的push,肯定是要上锁的,所以没必要原子
|
||||
std::thread m_cmd_thread;
|
||||
};
|
||||
}
|
||||
343
enc_temp_folder/29f045225470d5084a37cf9521429ad/Assistant.cpp
Normal file
343
enc_temp_folder/29f045225470d5084a37cf9521429ad/Assistant.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
#include "Assistant.h"
|
||||
|
||||
#include "AsstRanges.hpp"
|
||||
|
||||
#include <meojson/json.hpp>
|
||||
|
||||
#include "AsstUtils.hpp"
|
||||
#include "Controller.h"
|
||||
#include "Logger.hpp"
|
||||
#include "Resource.h"
|
||||
#include "RuntimeStatus.h"
|
||||
|
||||
#include "FightTask.h"
|
||||
#include "StartUpTask.h"
|
||||
#include "CloseDownTask.h"
|
||||
#include "AwardTask.h"
|
||||
#include "VisitTask.h"
|
||||
#include "MallTask.h"
|
||||
#include "InfrastTask.h"
|
||||
#include "RecruitTask.h"
|
||||
#include "RoguelikeTask.h"
|
||||
#include "CopilotTask.h"
|
||||
#include "DepotTask.h"
|
||||
#ifdef ASST_DEBUG
|
||||
#include "DebugTask.h"
|
||||
#endif
|
||||
|
||||
using namespace asst;
|
||||
|
||||
Assistant::Assistant(AsstApiCallback callback, void* callback_arg)
|
||||
: m_callback(callback),
|
||||
m_callback_arg(callback_arg)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
m_status = std::make_shared<RuntimeStatus>();
|
||||
m_ctrler = std::make_shared<Controller>(task_callback, static_cast<void*>(this));
|
||||
|
||||
m_working_thread = std::thread(&Assistant::working_proc, this);
|
||||
m_msg_thread = std::thread(&Assistant::msg_proc, this);
|
||||
}
|
||||
|
||||
Assistant::~Assistant()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
m_thread_exit = true;
|
||||
m_thread_idle = true;
|
||||
m_condvar.notify_all();
|
||||
m_msg_condvar.notify_all();
|
||||
|
||||
if (m_working_thread.joinable()) {
|
||||
m_working_thread.join();
|
||||
}
|
||||
if (m_msg_thread.joinable()) {
|
||||
m_msg_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool asst::Assistant::connect(const std::string& adb_path, const std::string& address, const std::string& config)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
stop(false);
|
||||
|
||||
bool ret = m_ctrler->connect(adb_path, address, config.empty() ? "General" : config);
|
||||
if (ret) {
|
||||
m_uuid = m_ctrler->get_uuid();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
asst::Assistant::TaskId asst::Assistant::append_task(const std::string& type, const std::string& params)
|
||||
{
|
||||
Log.info(__FUNCTION__, type, params);
|
||||
|
||||
auto ret = json::parse(params.empty() ? "{}" : params);
|
||||
if (!ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<PackageTask> ptr = nullptr;
|
||||
|
||||
#define ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(TASK) \
|
||||
else if (type == TASK::TaskType) { ptr = std::make_shared<TASK>(task_callback, static_cast<void*>(this)); }
|
||||
|
||||
if constexpr (false) {}
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(FightTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(StartUpTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(CloseDownTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(AwardTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(VisitTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(MallTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(InfrastTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(RecruitTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(RoguelikeTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(CopilotTask)
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(DepotTask)
|
||||
#ifdef ASST_DEBUG
|
||||
ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(DebugTask)
|
||||
#endif
|
||||
else {
|
||||
Log.error(__FUNCTION__, "| invalid type:", type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH
|
||||
|
||||
auto& json = ret.value();
|
||||
bool enable = json.get("enable", true);
|
||||
ptr->set_enable(enable);
|
||||
bool params_ret = ptr->set_params(json);
|
||||
if (!params_ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
++m_task_id;
|
||||
ptr->set_task_id(m_task_id);
|
||||
m_tasks_list.emplace_back(m_task_id, ptr);
|
||||
return m_task_id;
|
||||
}
|
||||
|
||||
bool asst::Assistant::set_task_params(TaskId task_id, const std::string& params)
|
||||
{
|
||||
Log.info(__FUNCTION__, task_id, params);
|
||||
|
||||
if (task_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ret = json::parse(params.empty() ? "{}" : params);
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
auto& json = ret.value();
|
||||
|
||||
bool is_set = false;
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
for (auto&& [id, ptr] : m_tasks_list) {
|
||||
if (id != task_id) {
|
||||
continue;
|
||||
}
|
||||
bool enable = json.get("enable", true);
|
||||
ptr->set_enable(enable);
|
||||
is_set = ptr->set_params(json);
|
||||
break;
|
||||
}
|
||||
|
||||
return is_set;
|
||||
}
|
||||
|
||||
std::vector<uchar> asst::Assistant::get_image() const
|
||||
{
|
||||
if (!inited()) {
|
||||
return {};
|
||||
}
|
||||
return m_ctrler->get_image_encode();
|
||||
}
|
||||
|
||||
bool asst::Assistant::ctrler_click(int x, int y, bool block)
|
||||
{
|
||||
if (!inited()) {
|
||||
return false;
|
||||
}
|
||||
m_ctrler->click(Point(x, y), block);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string asst::Assistant::get_uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
std::vector<Assistant::TaskId> asst::Assistant::get_tasks_list() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
std::vector<TaskId> result(m_tasks_list.size());
|
||||
ranges::copy(m_tasks_list | views::keys, result.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
bool asst::Assistant::start(bool block)
|
||||
{
|
||||
LogTraceFunction;
|
||||
Log.trace("Start |", block ? "block" : "non block");
|
||||
|
||||
if (!m_thread_idle || !inited()) {
|
||||
return false;
|
||||
}
|
||||
std::unique_lock<std::mutex> lock;
|
||||
if (block) { // 外部调用
|
||||
lock = std::unique_lock<std::mutex>(m_mutex);
|
||||
}
|
||||
|
||||
m_thread_idle = false;
|
||||
m_condvar.notify_one();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assistant::stop(bool block)
|
||||
{
|
||||
LogTraceFunction;
|
||||
Log.trace("Stop |", block ? "block" : "non block");
|
||||
|
||||
m_thread_idle = true;
|
||||
|
||||
std::unique_lock<std::mutex> lock;
|
||||
if (block) { // 外部调用
|
||||
lock = std::unique_lock<std::mutex>(m_mutex);
|
||||
}
|
||||
m_tasks_list.clear();
|
||||
|
||||
clear_cache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Assistant::working_proc()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
std::vector<TaskId> finished_tasks;
|
||||
while (!m_thread_exit) {
|
||||
//LogTraceScope("Assistant::working_proc Loop");
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (!m_thread_idle && !m_tasks_list.empty()) {
|
||||
const auto [id, task_ptr] = m_tasks_list.front();
|
||||
lock.unlock();
|
||||
// only one instance of working_proc running, unlock here to allow set_task_param to the running task
|
||||
|
||||
json::value callback_json = json::object{
|
||||
{ "taskchain", task_ptr->get_task_chain() },
|
||||
{ "taskid", id }
|
||||
};
|
||||
task_callback(AsstMsg::TaskChainStart, callback_json, this);
|
||||
|
||||
task_ptr->set_exit_flag(&m_thread_idle)
|
||||
.set_ctrler(m_ctrler)
|
||||
.set_status(m_status);
|
||||
|
||||
bool ret = task_ptr->run();
|
||||
finished_tasks.emplace_back(id);
|
||||
|
||||
lock.lock();
|
||||
if (!m_tasks_list.empty()) {
|
||||
m_tasks_list.pop_front();
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
task_callback(ret ? AsstMsg::TaskChainCompleted : AsstMsg::TaskChainError, callback_json, this);
|
||||
|
||||
if (!m_thread_idle && m_tasks_list.empty()) {
|
||||
callback_json["finished_tasks"] = json::array(finished_tasks);
|
||||
task_callback(AsstMsg::AllTasksCompleted, callback_json, this);
|
||||
finished_tasks.clear();
|
||||
}
|
||||
|
||||
auto delay = Resrc.cfg().get_options().task_delay;
|
||||
lock.lock();
|
||||
m_condvar.wait_for(lock, std::chrono::milliseconds(delay),
|
||||
[&]() -> bool { return m_thread_idle; });
|
||||
}
|
||||
else {
|
||||
m_thread_idle = true;
|
||||
finished_tasks.clear();
|
||||
Log.flush();
|
||||
m_condvar.wait(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Assistant::msg_proc()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
while (!m_thread_exit) {
|
||||
//LogTraceScope("Assistant::msg_proc Loop");
|
||||
std::unique_lock<std::mutex> lock(m_msg_mutex);
|
||||
if (!m_msg_queue.empty()) {
|
||||
// 结构化绑定只能是引用,后续的pop会使引用失效,所以需要重新构造一份,这里采用了move的方式
|
||||
auto&& [temp_msg, temp_detail] = m_msg_queue.front();
|
||||
AsstMsg msg = temp_msg;
|
||||
json::value detail = std::move(temp_detail);
|
||||
m_msg_queue.pop();
|
||||
lock.unlock();
|
||||
|
||||
if (m_callback) {
|
||||
m_callback(static_cast<int>(msg), detail.to_string().c_str(), m_callback_arg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_msg_condvar.wait(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Assistant::task_callback(AsstMsg msg, const json::value& detail, void* custom_arg)
|
||||
{
|
||||
auto p_this = static_cast<Assistant*>(custom_arg);
|
||||
json::value more_detail = detail;
|
||||
if (!more_detail.contains("uuid")) {
|
||||
more_detail["uuid"] = p_this->m_uuid;
|
||||
}
|
||||
|
||||
switch (msg) {
|
||||
case AsstMsg::InternalError:
|
||||
case AsstMsg::InitFailed:
|
||||
p_this->stop(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Log.trace("Assistant::task_callback |", msg, more_detail.to_string());
|
||||
|
||||
// 加入回调消息队列,由回调消息线程外抛给外部
|
||||
p_this->append_callback(msg, std::move(more_detail));
|
||||
}
|
||||
|
||||
void asst::Assistant::append_callback(AsstMsg msg, json::value detail)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_msg_mutex);
|
||||
m_msg_queue.emplace(msg, std::move(detail));
|
||||
m_msg_condvar.notify_one();
|
||||
}
|
||||
|
||||
void Assistant::clear_cache()
|
||||
{
|
||||
m_status->clear_number();
|
||||
m_status->clear_rect();
|
||||
m_status->clear_str();
|
||||
}
|
||||
|
||||
bool asst::Assistant::inited() const noexcept
|
||||
{
|
||||
return m_ctrler && m_ctrler->inited();
|
||||
}
|
||||
1247
enc_temp_folder/9275a37be81f8ae6386e54f72ddf75/Controller.cpp
Normal file
1247
enc_temp_folder/9275a37be81f8ae6386e54f72ddf75/Controller.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -58,8 +58,8 @@ namespace MeoAsstGui
|
||||
Localization.GetString("CopilotTip1") + "\n\n" +
|
||||
Localization.GetString("CopilotTip2") + "\n\n" +
|
||||
Localization.GetString("CopilotTip3") + "\n\n" +
|
||||
Localization.GetString("CopilotTip4") + "\n\n" +
|
||||
Localization.GetString("CopilotTip5"),
|
||||
Localization.GetString("CopilotTip4"),
|
||||
//Localization.GetString("CopilotTip5"),
|
||||
LogColor.Message);
|
||||
}
|
||||
|
||||
@@ -295,8 +295,8 @@ namespace MeoAsstGui
|
||||
Localization.GetString("CopilotTip1") + "\n\n" +
|
||||
Localization.GetString("CopilotTip2") + "\n\n" +
|
||||
Localization.GetString("CopilotTip3") + "\n\n" +
|
||||
Localization.GetString("CopilotTip4") + "\n\n" +
|
||||
Localization.GetString("CopilotTip5"));
|
||||
Localization.GetString("CopilotTip4"));
|
||||
//Localization.GetString("CopilotTip5"));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -368,10 +368,10 @@ namespace MeoAsstGui
|
||||
public async void Start()
|
||||
{
|
||||
ClearLog();
|
||||
if (_form)
|
||||
{
|
||||
AddLog(Localization.GetString("AutoSquadTip"), LogColor.Message);
|
||||
}
|
||||
//if (_form)
|
||||
//{
|
||||
// AddLog(Localization.GetString("AutoSquadTip"), LogColor.Message);
|
||||
//}
|
||||
|
||||
AddLog(Localization.GetString("ConnectingToEmulator"));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user