From fac521a312d1f6055b29d12da8b8773adaa24afb Mon Sep 17 00:00:00 2001 From: iTrooz Date: Wed, 13 Nov 2024 17:44:54 +0100 Subject: [PATCH 01/71] Add dummy UI button --- launcher/ui/pages/instance/ServersPage.cpp | 5 +++++ launcher/ui/pages/instance/ServersPage.h | 1 + launcher/ui/pages/instance/ServersPage.ui | 7 +++++++ 3 files changed, 13 insertions(+) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index d8035e73e..3e8c57f6f 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -734,4 +734,9 @@ void ServersPage::on_actionJoin_triggered() APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(address, false))); } +void ServersPage::on_actionRefresh_triggered() +{ + qDebug() << "Action clicked"; +} + #include "ServersPage.moc" diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h index a27d1d297..77710d6cc 100644 --- a/launcher/ui/pages/instance/ServersPage.h +++ b/launcher/ui/pages/instance/ServersPage.h @@ -85,6 +85,7 @@ class ServersPage : public QMainWindow, public BasePage { void on_actionMove_Up_triggered(); void on_actionMove_Down_triggered(); void on_actionJoin_triggered(); + void on_actionRefresh_triggered(); void runningStateChanged(bool running); diff --git a/launcher/ui/pages/instance/ServersPage.ui b/launcher/ui/pages/instance/ServersPage.ui index e8f79cf2e..d330835c8 100644 --- a/launcher/ui/pages/instance/ServersPage.ui +++ b/launcher/ui/pages/instance/ServersPage.ui @@ -149,6 +149,8 @@ + + @@ -175,6 +177,11 @@ Join + + + Refresh + + From 43a54cafef95a3b4c2181f4d3d1e2d3876b8e7f9 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 15 Nov 2024 19:52:06 +0100 Subject: [PATCH 02/71] add my classes --- launcher/ui/pages/instance/McClient.hpp | 169 ++++++++++++++++++++++ launcher/ui/pages/instance/McResolver.hpp | 85 +++++++++++ 2 files changed, 254 insertions(+) create mode 100644 launcher/ui/pages/instance/McClient.hpp create mode 100644 launcher/ui/pages/instance/McResolver.hpp diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.hpp new file mode 100644 index 000000000..611e0c143 --- /dev/null +++ b/launcher/ui/pages/instance/McClient.hpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include + +#define SEGMENT_BITS 0x7F +#define CONTINUE_BIT 0x80 + +// Client for the Minecraft protocol +class McClient : public QObject { + Q_OBJECT + + QString domain; + QString ip; + short port; + QTcpSocket socket; + +public: + explicit McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), domain(domain), ip(ip), port(port) {} + + QJsonObject getStatusData() { + qDebug() << "Connecting to socket.."; + socket.connectToHost(ip, port); + + if (!socket.waitForConnected(3000)) { + throw std::runtime_error("Failed to connect to socket"); + } + qDebug() << "Connected to socket successfully"; + sendRequest(); + + if (!socket.waitForReadyRead(3000)) { + throw std::runtime_error("Socket didn't send anything to read"); + } + return readResponse(); + } + + int getOnlinePlayers() { + auto status = getStatusData(); + return status.value("players").toObject().value("online").toInt(); + } + + void sendRequest() { + QByteArray data; + writeVarInt(data, 0x00); // packet ID + writeVarInt(data, 0x760); // protocol version + writeVarInt(data, domain.size()); // server address length + writeString(data, domain.toStdString()); // server address + writeFixedInt(data, port, 2); // server port + writeVarInt(data, 0x01); // next state + writePacketToSocket(data); // send handshake packet + + data.clear(); + + writeVarInt(data, 0x00); // packet ID + writePacketToSocket(data); // send status packet + } + + void readBytesExactFromSocket(QByteArray &resp, int bytesToRead) { + while (bytesToRead > 0) { + qDebug() << bytesToRead << " bytes left to read"; + if (!socket.waitForReadyRead()) { + throw std::runtime_error("Read timeout or error"); + } + + QByteArray chunk = socket.read(qMin(bytesToRead, socket.bytesAvailable())); + resp.append(chunk); + bytesToRead -= chunk.size(); + } + } + + QJsonObject readResponse() { + auto resp = socket.readAll(); + int length = readVarInt(resp); + + // finish ready response + readBytesExactFromSocket(resp, length-resp.size()); + + if (length != resp.size()) { + printf("Warning: Packet length doesn't match actual packet size (%d expected vs %d received)\n", length, resp.size()); + } + qDebug() << "Received response successfully"; + + int packetID = readVarInt(resp); + if (packetID != 0x00) { + throw std::runtime_error( + QString("Packet ID doesn't match expected value (0x00 vs 0x%1)") + .arg(packetID, 0, 16).toStdString() + ); + } + + int jsonLength = readVarInt(resp); + std::string json = resp.toStdString(); + + QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(json)); + return doc.object(); + } + + void close() { + socket.close(); + } + +private: + // From https://wiki.vg/Protocol#VarInt_and_VarLong + void writeVarInt(QByteArray &data, int value) { + while (true) { + if ((value & ~SEGMENT_BITS) == 0) { + data.append(value); + return; + } + + data.append((value & SEGMENT_BITS) | CONTINUE_BIT); + + // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone + value >>= 7; + } + } + + // From https://wiki.vg/Protocol#VarInt_and_VarLong + int readVarInt(QByteArray &data) { + int value = 0; + int position = 0; + char currentByte; + + while (true) { + currentByte = readByte(data); + value |= (currentByte & SEGMENT_BITS) << position; + + if ((currentByte & CONTINUE_BIT) == 0) break; + + position += 7; + + if (position >= 32) throw std::runtime_error("VarInt is too big"); + } + + return value; + } + + char readByte(QByteArray &data) { + if (data.isEmpty()) { + throw std::runtime_error("No more bytes to read"); + } + + char byte = data.at(0); + data.remove(0, 1); + return byte; + } + + // write number with specified size in big endian format + void writeFixedInt(QByteArray &data, int value, int size) { + for (int i = size - 1; i >= 0; i--) { + data.append((value >> (i * 8)) & 0xFF); + } + } + + void writeString(QByteArray &data, std::string value) { + data.append(value); + } + + void writePacketToSocket(QByteArray &data) { + // we prefix the packet with its length + QByteArray dataWithSize; + writeVarInt(dataWithSize, data.size()); + dataWithSize.append(data); + + // write it to the socket + socket.write(dataWithSize); + socket.flush(); + } +}; diff --git a/launcher/ui/pages/instance/McResolver.hpp b/launcher/ui/pages/instance/McResolver.hpp new file mode 100644 index 000000000..29968f26b --- /dev/null +++ b/launcher/ui/pages/instance/McResolver.hpp @@ -0,0 +1,85 @@ +#include +#include +#include +#include + +// resolve the IP and port of a Minecraft server +class MCResolver : public QObject { + Q_OBJECT + + std::string constrDomain; + int constrPort; + +public: + explicit MCResolver(QObject *parent, std::string domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} + + void ping() { + pingWithDomainSRV(QString::fromStdString(constrDomain), constrPort); + } + +private: + + void pingWithDomainSRV(QString domain, int port) { + QDnsLookup *lookup = new QDnsLookup(this); + lookup->setName(QString("_minecraft._tcp.%1").arg(domain)); + lookup->setType(QDnsLookup::SRV); + + connect(lookup, &QDnsLookup::finished, this, [&, domain, port]() { + QDnsLookup *lookup = qobject_cast(sender()); + + lookup->deleteLater(); + + if (lookup->error() != QDnsLookup::NoError) { + printf("Warning: SRV record lookup failed (%v), trying A record lookup\n", lookup->errorString().toStdString()); + pingWithDomainA(domain, port); + return; + } + + auto records = lookup->serviceRecords(); + if (records.isEmpty()) { + printf("Warning: no SRV entries found for domain, trying A record lookup\n"); + pingWithDomainA(domain, port); + return; + } + + + const auto& firstRecord = records.at(0); + QString domain = firstRecord.target(); + int port = firstRecord.port(); + pingWithDomainA(domain, port); + }); + + lookup->lookup(); + } + + void pingWithDomainA(QString domain, int port) { + QHostInfo::lookupHost(domain, this, [&, port](const QHostInfo &hostInfo){ + if (hostInfo.error() != QHostInfo::NoError) { + emitFail("A record lookup failed"); + return; + } else { + auto records = hostInfo.addresses(); + if (records.isEmpty()) { + emitFail("No A entries found for domain"); + return; + } + + const auto& firstRecord = records.at(0); + emitSucceed(firstRecord.toString(), port); + } + }); + } + + void emitFail(std::string error) { + printf("Ping error: %s\n", error.c_str()); + emit fail(); + } + + void emitSucceed(QString ip, int port) { + emit succeed(ip, port); + } + +signals: + void succeed(QString ip, int port); + void fail(); +}; From ee35ac5afdb2a6409b7ad52fde38c411d112f064 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 15 Nov 2024 20:07:14 +0100 Subject: [PATCH 03/71] add method queryStatus() to servers and use it on click --- launcher/ui/pages/instance/McResolver.hpp | 6 ++-- launcher/ui/pages/instance/ServersPage.cpp | 42 +++++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/launcher/ui/pages/instance/McResolver.hpp b/launcher/ui/pages/instance/McResolver.hpp index 29968f26b..49b1f23eb 100644 --- a/launcher/ui/pages/instance/McResolver.hpp +++ b/launcher/ui/pages/instance/McResolver.hpp @@ -7,14 +7,14 @@ class MCResolver : public QObject { Q_OBJECT - std::string constrDomain; + QString constrDomain; int constrPort; public: - explicit MCResolver(QObject *parent, std::string domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} + explicit MCResolver(QObject *parent, QString domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} void ping() { - pingWithDomainSRV(QString::fromStdString(constrDomain), constrPort); + pingWithDomainSRV(constrDomain, constrPort); } private: diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 3e8c57f6f..427e9c3e7 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -39,6 +39,9 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" +#include "McClient.hpp" +#include "McResolver.hpp" + #include #include #include @@ -52,7 +55,7 @@ #include #include -static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. +static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things. struct Server { // Types @@ -88,6 +91,29 @@ struct Server { } } + std::tuple splitAddress() { + auto parts = m_address.split(":"); + if (parts.size() == 1) { + return std::make_tuple(parts[0], 25565); + } else { + return std::make_tuple(parts[0], parts[1].toInt()); + } + } + + void queryStatus() { + auto [domain, port] = splitAddress(); + MCResolver resolver(nullptr, domain, port); + QObject::connect(&resolver, &MCResolver::succeed, [&](QString ip, int port) { + qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; + McClient client(nullptr, domain, ip, port); + int online = client.getOnlinePlayers(); + printf("Online players: %d\n", online); + + client.close(); + }); + resolver.ping(); + } + void serialize(nbt::tag_compound& server) { server.insert("name", m_name.trimmed().toUtf8().toStdString()); @@ -112,7 +138,6 @@ struct Server { bool m_checked = false; bool m_up = false; QString m_motd; // https://mctools.org/motd-creator - int m_ping = 0; int m_currentPlayers = 0; int m_maxPlayers = 0; }; @@ -296,7 +321,7 @@ class ServersModel : public QAbstractListModel { case 1: return tr("Address"); case 2: - return tr("Latency"); + return tr("Online"); } } @@ -345,7 +370,7 @@ class ServersModel : public QAbstractListModel { case 2: switch (role) { case Qt::DisplayRole: - return m_servers[row].m_ping; + return m_servers[row].m_currentPlayers; default: return QVariant(); } @@ -433,6 +458,14 @@ class ServersModel : public QAbstractListModel { } } + + void queryServersStatus() + { + for (auto& server : m_servers) { + server.queryStatus(); + } + } + public slots: void dirChanged(const QString& path) { @@ -737,6 +770,7 @@ void ServersPage::on_actionJoin_triggered() void ServersPage::on_actionRefresh_triggered() { qDebug() << "Action clicked"; + m_model->queryServersStatus(); } #include "ServersPage.moc" From 99ac11bc408b5102a7c9d6d0c003e854d5e7fdef Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 15 Nov 2024 20:26:10 +0100 Subject: [PATCH 04/71] add my classes to CMakeLists --- launcher/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 486aaff3e..4fdadba6f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -918,7 +918,9 @@ SET(LAUNCHER_SOURCES ui/pages/instance/ServersPage.h ui/pages/instance/WorldListPage.cpp ui/pages/instance/WorldListPage.h - + ui/pages/instance/McClient.hpp + ui/pages/instance/McResolver.hpp + # GUI - global settings pages ui/pages/global/AccountListPage.cpp ui/pages/global/AccountListPage.h From 8fa1dff17dc4d7953fbf7f0961a33e751183b06b Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 15 Nov 2024 20:26:50 +0100 Subject: [PATCH 05/71] remove space --- launcher/ui/pages/instance/ServersPage.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 427e9c3e7..7591d2a9d 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -458,7 +458,6 @@ class ServersModel : public QAbstractListModel { } } - void queryServersStatus() { for (auto& server : m_servers) { From 2f70115be5555c7c9ad46765f61e6b1cc965e8b4 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 16 Nov 2024 22:06:53 +0100 Subject: [PATCH 06/71] add debug print --- launcher/ui/pages/instance/ServersPage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 7591d2a9d..435e7f347 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -101,6 +101,7 @@ struct Server { } void queryStatus() { + qDebug() << "Querying status of " << m_address; auto [domain, port] = splitAddress(); MCResolver resolver(nullptr, domain, port); QObject::connect(&resolver, &MCResolver::succeed, [&](QString ip, int port) { From ea2a2349f8ebd353ac4ba4296f9514c3f8419c58 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 16 Nov 2024 22:24:45 +0100 Subject: [PATCH 07/71] make splitAddress() const --- launcher/ui/pages/instance/ServersPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 435e7f347..82f8c8453 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -91,7 +91,7 @@ struct Server { } } - std::tuple splitAddress() { + std::tuple splitAddress() const { auto parts = m_address.split(":"); if (parts.size() == 1) { return std::make_tuple(parts[0], 25565); From 87c9066a2b218aef5296ab8ad4dddfa483dcd9cb Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 16 Nov 2024 22:41:50 +0100 Subject: [PATCH 08/71] run the code in tasks --- launcher/ui/pages/instance/ServersPage.cpp | 48 ++++++++++++++-------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 82f8c8453..fa8d6c891 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -54,6 +54,7 @@ #include #include #include +#include static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things. @@ -100,21 +101,6 @@ struct Server { } } - void queryStatus() { - qDebug() << "Querying status of " << m_address; - auto [domain, port] = splitAddress(); - MCResolver resolver(nullptr, domain, port); - QObject::connect(&resolver, &MCResolver::succeed, [&](QString ip, int port) { - qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; - McClient client(nullptr, domain, ip, port); - int online = client.getOnlinePlayers(); - printf("Online players: %d\n", online); - - client.close(); - }); - resolver.ping(); - } - void serialize(nbt::tag_compound& server) { server.insert("name", m_name.trimmed().toUtf8().toStdString()); @@ -143,6 +129,33 @@ struct Server { int m_maxPlayers = 0; }; +class ServerPingTask : public Task { + Q_OBJECT + public: + explicit ServerPingTask(QObject* parent, const Server &server) : Task(parent), m_server(server) {} + ~ServerPingTask() override = default; + + + protected: + virtual void executeTask() override { + qDebug() << "Querying status of " << m_server.m_address; + auto [domain, port] = m_server.splitAddress(); + MCResolver resolver(nullptr, domain, port); + QObject::connect(&resolver, &MCResolver::succeed, [&](QString ip, int port) { + qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; + McClient client(nullptr, domain, ip, port); + int online = client.getOnlinePlayers(); + printf("Online players: %d\n", online); + + client.close(); + }); + resolver.ping(); + } + + private: + const Server &m_server; +}; + static std::unique_ptr parseServersDat(const QString& filename) { try { @@ -461,9 +474,12 @@ class ServersModel : public QAbstractListModel { void queryServersStatus() { + ConcurrentTask::Ptr job(new ConcurrentTask(this, "Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); for (auto& server : m_servers) { - server.queryStatus(); + ServerPingTask *task = new ServerPingTask(this, server); + job->addTask(Task::Ptr(task)); } + job->start(); } public slots: From fe28a051d533bd34681c6306deab5b93b13dc5e9 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 16 Nov 2024 23:37:20 +0100 Subject: [PATCH 09/71] make MCResolver a dynamic object so it doesnt get deleted before the callback See https://discord.com/channels/1031648380885147709/1031823065937629267/1307471566166167696 --- launcher/ui/pages/instance/ServersPage.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index fa8d6c891..100f92386 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -140,8 +140,9 @@ class ServerPingTask : public Task { virtual void executeTask() override { qDebug() << "Querying status of " << m_server.m_address; auto [domain, port] = m_server.splitAddress(); - MCResolver resolver(nullptr, domain, port); - QObject::connect(&resolver, &MCResolver::succeed, [&](QString ip, int port) { + MCResolver *resolver = new MCResolver(nullptr, domain, port); + QObject::connect(resolver, &MCResolver::succeed, [=](QString ip, int port) { + resolver->deleteLater(); qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; McClient client(nullptr, domain, ip, port); int online = client.getOnlinePlayers(); @@ -149,7 +150,7 @@ class ServerPingTask : public Task { client.close(); }); - resolver.ping(); + resolver->ping(); } private: From 0a379a05ff03b2c55fcc4342c84f90fff64bb9a9 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 16 Nov 2024 23:47:43 +0100 Subject: [PATCH 10/71] replace my printf calls with qDebug --- launcher/ui/pages/instance/McClient.hpp | 2 +- launcher/ui/pages/instance/McResolver.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.hpp index 611e0c143..4ebcf37be 100644 --- a/launcher/ui/pages/instance/McClient.hpp +++ b/launcher/ui/pages/instance/McClient.hpp @@ -76,7 +76,7 @@ public: readBytesExactFromSocket(resp, length-resp.size()); if (length != resp.size()) { - printf("Warning: Packet length doesn't match actual packet size (%d expected vs %d received)\n", length, resp.size()); + qDebug() << "Warning: Packet length doesn't match actual packet size (" << length << " expected vs " << resp.size() << " received)"; } qDebug() << "Received response successfully"; diff --git a/launcher/ui/pages/instance/McResolver.hpp b/launcher/ui/pages/instance/McResolver.hpp index 49b1f23eb..ea6f517ca 100644 --- a/launcher/ui/pages/instance/McResolver.hpp +++ b/launcher/ui/pages/instance/McResolver.hpp @@ -30,14 +30,14 @@ private: lookup->deleteLater(); if (lookup->error() != QDnsLookup::NoError) { - printf("Warning: SRV record lookup failed (%v), trying A record lookup\n", lookup->errorString().toStdString()); + qDebug() << "Warning: SRV record lookup failed (" << lookup->errorString().toStdString() << "), trying A record lookup"; pingWithDomainA(domain, port); return; } auto records = lookup->serviceRecords(); if (records.isEmpty()) { - printf("Warning: no SRV entries found for domain, trying A record lookup\n"); + qDebug() << "Warning: no SRV entries found for domain, trying A record lookup"; pingWithDomainA(domain, port); return; } @@ -71,7 +71,7 @@ private: } void emitFail(std::string error) { - printf("Ping error: %s\n", error.c_str()); + qDebug() << "Ping error:" << QString::fromStdString(error); emit fail(); } From 6a7678a6e9548501b84ed3a2d81a9cabe6955703 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 16 Nov 2024 23:54:44 +0100 Subject: [PATCH 11/71] Actually show online players when clicking on the button --- launcher/ui/pages/instance/ServersPage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 100f92386..a3c1ef702 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -132,7 +132,7 @@ struct Server { class ServerPingTask : public Task { Q_OBJECT public: - explicit ServerPingTask(QObject* parent, const Server &server) : Task(parent), m_server(server) {} + explicit ServerPingTask(QObject* parent, Server &server) : Task(parent), m_server(server) {} ~ServerPingTask() override = default; @@ -146,7 +146,7 @@ class ServerPingTask : public Task { qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; McClient client(nullptr, domain, ip, port); int online = client.getOnlinePlayers(); - printf("Online players: %d\n", online); + m_server.m_currentPlayers = online; client.close(); }); @@ -154,7 +154,7 @@ class ServerPingTask : public Task { } private: - const Server &m_server; + Server &m_server; }; static std::unique_ptr parseServersDat(const QString& filename) From cba7e2dc362238cbfa9bcc680d164c828cd39836 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 00:02:24 +0100 Subject: [PATCH 12/71] use std::optional<> to signify when there is no value --- launcher/ui/pages/instance/ServersPage.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index a3c1ef702..5ab97f6cb 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -125,7 +125,7 @@ struct Server { bool m_checked = false; bool m_up = false; QString m_motd; // https://mctools.org/motd-creator - int m_currentPlayers = 0; + std::optional m_currentPlayers; // nullopt if not calculated/calculating int m_maxPlayers = 0; }; @@ -146,6 +146,7 @@ class ServerPingTask : public Task { qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; McClient client(nullptr, domain, ip, port); int online = client.getOnlinePlayers(); + qDebug() << "Online players: " << online; m_server.m_currentPlayers = online; client.close(); @@ -385,7 +386,11 @@ class ServersModel : public QAbstractListModel { case 2: switch (role) { case Qt::DisplayRole: - return m_servers[row].m_currentPlayers; + if (m_servers[row].m_currentPlayers) { + return *m_servers[row].m_currentPlayers; + } else { + return "..."; + } default: return QVariant(); } From 8cf0c2029ccccf40cbf21fd122bea233732911bc Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 18:51:57 +0100 Subject: [PATCH 13/71] No need to close the socket, it is done automatically https://doc.qt.io/qt-6/qtcpsocket.html#dtor.QTcpSocket --- launcher/ui/pages/instance/McClient.hpp | 4 ---- launcher/ui/pages/instance/ServersPage.cpp | 2 -- 2 files changed, 6 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.hpp index 4ebcf37be..f85d7fd21 100644 --- a/launcher/ui/pages/instance/McClient.hpp +++ b/launcher/ui/pages/instance/McClient.hpp @@ -95,10 +95,6 @@ public: return doc.object(); } - void close() { - socket.close(); - } - private: // From https://wiki.vg/Protocol#VarInt_and_VarLong void writeVarInt(QByteArray &data, int value) { diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 5ab97f6cb..1a973580d 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -148,8 +148,6 @@ class ServerPingTask : public Task { int online = client.getOnlinePlayers(); qDebug() << "Online players: " << online; m_server.m_currentPlayers = online; - - client.close(); }); resolver->ping(); } From 0d830e56e9c888b860e03ee4a9e44239d67a4d4a Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 18:56:08 +0100 Subject: [PATCH 14/71] replace std::runtime_exception with PrismLauncher Exception + add try/catch --- launcher/ui/pages/instance/McClient.hpp | 16 +++++++++------- launcher/ui/pages/instance/ServersPage.cpp | 11 ++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.hpp index f85d7fd21..992328504 100644 --- a/launcher/ui/pages/instance/McClient.hpp +++ b/launcher/ui/pages/instance/McClient.hpp @@ -3,6 +3,8 @@ #include #include +#include + #define SEGMENT_BITS 0x7F #define CONTINUE_BIT 0x80 @@ -23,13 +25,13 @@ public: socket.connectToHost(ip, port); if (!socket.waitForConnected(3000)) { - throw std::runtime_error("Failed to connect to socket"); + throw Exception("Failed to connect to socket"); } qDebug() << "Connected to socket successfully"; sendRequest(); if (!socket.waitForReadyRead(3000)) { - throw std::runtime_error("Socket didn't send anything to read"); + throw Exception("Socket didn't send anything to read"); } return readResponse(); } @@ -59,7 +61,7 @@ public: while (bytesToRead > 0) { qDebug() << bytesToRead << " bytes left to read"; if (!socket.waitForReadyRead()) { - throw std::runtime_error("Read timeout or error"); + throw Exception("Read timeout or error"); } QByteArray chunk = socket.read(qMin(bytesToRead, socket.bytesAvailable())); @@ -82,9 +84,9 @@ public: int packetID = readVarInt(resp); if (packetID != 0x00) { - throw std::runtime_error( + throw Exception( QString("Packet ID doesn't match expected value (0x00 vs 0x%1)") - .arg(packetID, 0, 16).toStdString() + .arg(packetID, 0, 16) ); } @@ -125,7 +127,7 @@ private: position += 7; - if (position >= 32) throw std::runtime_error("VarInt is too big"); + if (position >= 32) throw Exception("VarInt is too big"); } return value; @@ -133,7 +135,7 @@ private: char readByte(QByteArray &data) { if (data.isEmpty()) { - throw std::runtime_error("No more bytes to read"); + throw Exception("No more bytes to read"); } char byte = data.at(0); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 1a973580d..5f3babcf2 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -145,9 +145,14 @@ class ServerPingTask : public Task { resolver->deleteLater(); qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; McClient client(nullptr, domain, ip, port); - int online = client.getOnlinePlayers(); - qDebug() << "Online players: " << online; - m_server.m_currentPlayers = online; + + try { + int online = client.getOnlinePlayers(); + qDebug() << "Online players: " << online; + m_server.m_currentPlayers = online; + } catch(const Exception& e) { + qDebug() << "Failed to get online players: " << e.cause(); + } }); resolver->ping(); } From b35cffb3477b997a4dc779696aeb0acb79880d51 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 19:00:57 +0100 Subject: [PATCH 15/71] fix warning about unused jsonLength --- launcher/ui/pages/instance/McClient.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.hpp index 992328504..f171ae674 100644 --- a/launcher/ui/pages/instance/McClient.hpp +++ b/launcher/ui/pages/instance/McClient.hpp @@ -90,7 +90,7 @@ public: ); } - int jsonLength = readVarInt(resp); + Q_UNUSED(readVarInt(resp)); // json length std::string json = resp.toStdString(); QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(json)); From 1f094b98039b4f8c79bc402246babd4f821116db Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 19:02:08 +0100 Subject: [PATCH 16/71] avoid translating back to stf string without reason --- launcher/ui/pages/instance/McClient.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.hpp index f171ae674..1dc88cc30 100644 --- a/launcher/ui/pages/instance/McClient.hpp +++ b/launcher/ui/pages/instance/McClient.hpp @@ -91,9 +91,9 @@ public: } Q_UNUSED(readVarInt(resp)); // json length - std::string json = resp.toStdString(); - QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(json)); + // 'resp' should now be the JSON string + QJsonDocument doc = QJsonDocument::fromJson(resp); return doc.object(); } From 2d06e0a11194a3a4c379ddd518b857c4c2b98f54 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 19:21:09 +0100 Subject: [PATCH 17/71] Fix after rebase --- launcher/ui/pages/instance/ServersPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 5f3babcf2..7ecf84ebf 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -483,7 +483,7 @@ class ServersModel : public QAbstractListModel { void queryServersStatus() { - ConcurrentTask::Ptr job(new ConcurrentTask(this, "Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); + ConcurrentTask::Ptr job(new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); for (auto& server : m_servers) { ServerPingTask *task = new ServerPingTask(this, server); job->addTask(Task::Ptr(task)); From c3543b104b5f13778677e2244ab93faf6850cb99 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 19:26:21 +0100 Subject: [PATCH 18/71] fix qDebug() call with string << append --- launcher/ui/pages/instance/McResolver.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/McResolver.hpp b/launcher/ui/pages/instance/McResolver.hpp index ea6f517ca..4bb767200 100644 --- a/launcher/ui/pages/instance/McResolver.hpp +++ b/launcher/ui/pages/instance/McResolver.hpp @@ -30,7 +30,7 @@ private: lookup->deleteLater(); if (lookup->error() != QDnsLookup::NoError) { - qDebug() << "Warning: SRV record lookup failed (" << lookup->errorString().toStdString() << "), trying A record lookup"; + qDebug() << QString("Warning: SRV record lookup failed (%1), trying A record lookup").arg(lookup->errorString()); pingWithDomainA(domain, port); return; } From 8b7040d416b46d8011b97b71505778026c2c5ece Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 19:37:15 +0100 Subject: [PATCH 19/71] use Qt5-compatible writeString() impl --- launcher/ui/pages/instance/McClient.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.hpp index 1dc88cc30..afbb64739 100644 --- a/launcher/ui/pages/instance/McClient.hpp +++ b/launcher/ui/pages/instance/McClient.hpp @@ -150,8 +150,8 @@ private: } } - void writeString(QByteArray &data, std::string value) { - data.append(value); + void writeString(QByteArray &data, const std::string &value) { + data.append(value.c_str()); } void writePacketToSocket(QByteArray &data) { From b8035ca0783ba42efbc7c11cf329c6f4372abbc5 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 19:50:52 +0100 Subject: [PATCH 20/71] refactor header and code into 2 files --- launcher/CMakeLists.txt | 2 + launcher/ui/pages/instance/McClient.cpp | 156 ++++++++++++++++++++++ launcher/ui/pages/instance/McClient.hpp | 155 ++------------------- launcher/ui/pages/instance/McResolver.cpp | 72 ++++++++++ launcher/ui/pages/instance/McResolver.hpp | 72 +--------- 5 files changed, 249 insertions(+), 208 deletions(-) create mode 100644 launcher/ui/pages/instance/McClient.cpp create mode 100644 launcher/ui/pages/instance/McResolver.cpp diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 4fdadba6f..e05b0fafc 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -918,7 +918,9 @@ SET(LAUNCHER_SOURCES ui/pages/instance/ServersPage.h ui/pages/instance/WorldListPage.cpp ui/pages/instance/WorldListPage.h + ui/pages/instance/McClient.cpp ui/pages/instance/McClient.hpp + ui/pages/instance/McResolver.cpp ui/pages/instance/McResolver.hpp # GUI - global settings pages diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp new file mode 100644 index 000000000..f49d4c8ad --- /dev/null +++ b/launcher/ui/pages/instance/McClient.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include + +#include +#include "McClient.hpp" + +#define SEGMENT_BITS 0x7F +#define CONTINUE_BIT 0x80 + +McClient::McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), domain(domain), ip(ip), port(port) {} + +QJsonObject McClient::getStatusData() { + qDebug() << "Connecting to socket.."; + socket.connectToHost(ip, port); + + if (!socket.waitForConnected(3000)) { + throw Exception("Failed to connect to socket"); + } + qDebug() << "Connected to socket successfully"; + sendRequest(); + + if (!socket.waitForReadyRead(3000)) { + throw Exception("Socket didn't send anything to read"); + } + return readResponse(); +} + +int McClient::getOnlinePlayers() { + auto status = getStatusData(); + return status.value("players").toObject().value("online").toInt(); +} + +void McClient::sendRequest() { + QByteArray data; + writeVarInt(data, 0x00); // packet ID + writeVarInt(data, 0x760); // protocol version + writeVarInt(data, domain.size()); // server address length + writeString(data, domain.toStdString()); // server address + writeFixedInt(data, port, 2); // server port + writeVarInt(data, 0x01); // next state + writePacketToSocket(data); // send handshake packet + + data.clear(); + + writeVarInt(data, 0x00); // packet ID + writePacketToSocket(data); // send status packet +} + +void McClient::readBytesExactFromSocket(QByteArray &resp, int bytesToRead) { + while (bytesToRead > 0) { + qDebug() << bytesToRead << " bytes left to read"; + if (!socket.waitForReadyRead()) { + throw Exception("Read timeout or error"); + } + + QByteArray chunk = socket.read(qMin(bytesToRead, socket.bytesAvailable())); + resp.append(chunk); + bytesToRead -= chunk.size(); + } +} + +QJsonObject McClient::readResponse() { + auto resp = socket.readAll(); + int length = readVarInt(resp); + + // finish ready response + readBytesExactFromSocket(resp, length-resp.size()); + + if (length != resp.size()) { + qDebug() << "Warning: Packet length doesn't match actual packet size (" << length << " expected vs " << resp.size() << " received)"; + } + qDebug() << "Received response successfully"; + + int packetID = readVarInt(resp); + if (packetID != 0x00) { + throw Exception( + QString("Packet ID doesn't match expected value (0x00 vs 0x%1)") + .arg(packetID, 0, 16) + ); + } + + Q_UNUSED(readVarInt(resp)); // json length + + // 'resp' should now be the JSON string + QJsonDocument doc = QJsonDocument::fromJson(resp); + return doc.object(); +} + +// From https://wiki.vg/Protocol#VarInt_and_VarLong +void McClient::writeVarInt(QByteArray &data, int value) { + while (true) { + if ((value & ~SEGMENT_BITS) == 0) { + data.append(value); + return; + } + + data.append((value & SEGMENT_BITS) | CONTINUE_BIT); + + // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone + value >>= 7; + } +} + +// From https://wiki.vg/Protocol#VarInt_and_VarLong +int McClient::readVarInt(QByteArray &data) { + int value = 0; + int position = 0; + char currentByte; + + while (true) { + currentByte = readByte(data); + value |= (currentByte & SEGMENT_BITS) << position; + + if ((currentByte & CONTINUE_BIT) == 0) break; + + position += 7; + + if (position >= 32) throw Exception("VarInt is too big"); + } + + return value; +} + +char McClient::readByte(QByteArray &data) { + if (data.isEmpty()) { + throw Exception("No more bytes to read"); + } + + char byte = data.at(0); + data.remove(0, 1); + return byte; +} + +// write number with specified size in big endian format +void McClient::writeFixedInt(QByteArray &data, int value, int size) { + for (int i = size - 1; i >= 0; i--) { + data.append((value >> (i * 8)) & 0xFF); + } +} + +void McClient::writeString(QByteArray &data, const std::string &value) { + data.append(value.c_str()); +} + +void McClient::writePacketToSocket(QByteArray &data) { + // we prefix the packet with its length + QByteArray dataWithSize; + writeVarInt(dataWithSize, data.size()); + dataWithSize.append(data); + + // write it to the socket + socket.write(dataWithSize); + socket.flush(); +} diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.hpp index afbb64739..e3da1fbcf 100644 --- a/launcher/ui/pages/instance/McClient.hpp +++ b/launcher/ui/pages/instance/McClient.hpp @@ -18,150 +18,19 @@ class McClient : public QObject { QTcpSocket socket; public: - explicit McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), domain(domain), ip(ip), port(port) {} - - QJsonObject getStatusData() { - qDebug() << "Connecting to socket.."; - socket.connectToHost(ip, port); - - if (!socket.waitForConnected(3000)) { - throw Exception("Failed to connect to socket"); - } - qDebug() << "Connected to socket successfully"; - sendRequest(); - - if (!socket.waitForReadyRead(3000)) { - throw Exception("Socket didn't send anything to read"); - } - return readResponse(); - } - - int getOnlinePlayers() { - auto status = getStatusData(); - return status.value("players").toObject().value("online").toInt(); - } - - void sendRequest() { - QByteArray data; - writeVarInt(data, 0x00); // packet ID - writeVarInt(data, 0x760); // protocol version - writeVarInt(data, domain.size()); // server address length - writeString(data, domain.toStdString()); // server address - writeFixedInt(data, port, 2); // server port - writeVarInt(data, 0x01); // next state - writePacketToSocket(data); // send handshake packet - - data.clear(); - - writeVarInt(data, 0x00); // packet ID - writePacketToSocket(data); // send status packet - } - - void readBytesExactFromSocket(QByteArray &resp, int bytesToRead) { - while (bytesToRead > 0) { - qDebug() << bytesToRead << " bytes left to read"; - if (!socket.waitForReadyRead()) { - throw Exception("Read timeout or error"); - } - - QByteArray chunk = socket.read(qMin(bytesToRead, socket.bytesAvailable())); - resp.append(chunk); - bytesToRead -= chunk.size(); - } - } - - QJsonObject readResponse() { - auto resp = socket.readAll(); - int length = readVarInt(resp); - - // finish ready response - readBytesExactFromSocket(resp, length-resp.size()); - - if (length != resp.size()) { - qDebug() << "Warning: Packet length doesn't match actual packet size (" << length << " expected vs " << resp.size() << " received)"; - } - qDebug() << "Received response successfully"; - - int packetID = readVarInt(resp); - if (packetID != 0x00) { - throw Exception( - QString("Packet ID doesn't match expected value (0x00 vs 0x%1)") - .arg(packetID, 0, 16) - ); - } - - Q_UNUSED(readVarInt(resp)); // json length - - // 'resp' should now be the JSON string - QJsonDocument doc = QJsonDocument::fromJson(resp); - return doc.object(); - } - + explicit McClient(QObject *parent, QString domain, QString ip, short port); + QJsonObject getStatusData(); + int getOnlinePlayers(); + void sendRequest(); + void readBytesExactFromSocket(QByteArray &resp, int bytesToRead); + QJsonObject readResponse(); private: - // From https://wiki.vg/Protocol#VarInt_and_VarLong - void writeVarInt(QByteArray &data, int value) { - while (true) { - if ((value & ~SEGMENT_BITS) == 0) { - data.append(value); - return; - } - - data.append((value & SEGMENT_BITS) | CONTINUE_BIT); - - // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone - value >>= 7; - } - } - - // From https://wiki.vg/Protocol#VarInt_and_VarLong - int readVarInt(QByteArray &data) { - int value = 0; - int position = 0; - char currentByte; - - while (true) { - currentByte = readByte(data); - value |= (currentByte & SEGMENT_BITS) << position; - - if ((currentByte & CONTINUE_BIT) == 0) break; - - position += 7; - - if (position >= 32) throw Exception("VarInt is too big"); - } - - return value; - } - - char readByte(QByteArray &data) { - if (data.isEmpty()) { - throw Exception("No more bytes to read"); - } - - char byte = data.at(0); - data.remove(0, 1); - return byte; - } - + void writeVarInt(QByteArray &data, int value); + int readVarInt(QByteArray &data); + char readByte(QByteArray &data); // write number with specified size in big endian format - void writeFixedInt(QByteArray &data, int value, int size) { - for (int i = size - 1; i >= 0; i--) { - data.append((value >> (i * 8)) & 0xFF); - } - } + void writeFixedInt(QByteArray &data, int value, int size); + void writeString(QByteArray &data, const std::string &value); - void writeString(QByteArray &data, const std::string &value) { - data.append(value.c_str()); - } - - void writePacketToSocket(QByteArray &data) { - // we prefix the packet with its length - QByteArray dataWithSize; - writeVarInt(dataWithSize, data.size()); - dataWithSize.append(data); - - // write it to the socket - socket.write(dataWithSize); - socket.flush(); - } + void writePacketToSocket(QByteArray &data); }; diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp new file mode 100644 index 000000000..e7103e211 --- /dev/null +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +#include "McResolver.hpp" + +MCResolver::MCResolver(QObject *parent, QString domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} + +void MCResolver::ping() { + pingWithDomainSRV(constrDomain, constrPort); +} + +void MCResolver::pingWithDomainSRV(QString domain, int port) { + QDnsLookup *lookup = new QDnsLookup(this); + lookup->setName(QString("_minecraft._tcp.%1").arg(domain)); + lookup->setType(QDnsLookup::SRV); + + connect(lookup, &QDnsLookup::finished, this, [&, domain, port]() { + QDnsLookup *lookup = qobject_cast(sender()); + + lookup->deleteLater(); + + if (lookup->error() != QDnsLookup::NoError) { + qDebug() << QString("Warning: SRV record lookup failed (%1), trying A record lookup").arg(lookup->errorString()); + pingWithDomainA(domain, port); + return; + } + + auto records = lookup->serviceRecords(); + if (records.isEmpty()) { + qDebug() << "Warning: no SRV entries found for domain, trying A record lookup"; + pingWithDomainA(domain, port); + return; + } + + + const auto& firstRecord = records.at(0); + QString domain = firstRecord.target(); + int port = firstRecord.port(); + pingWithDomainA(domain, port); + }); + + lookup->lookup(); +} + +void MCResolver::pingWithDomainA(QString domain, int port) { + QHostInfo::lookupHost(domain, this, [&, port](const QHostInfo &hostInfo){ + if (hostInfo.error() != QHostInfo::NoError) { + emitFail("A record lookup failed"); + return; + } else { + auto records = hostInfo.addresses(); + if (records.isEmpty()) { + emitFail("No A entries found for domain"); + return; + } + + const auto& firstRecord = records.at(0); + emitSucceed(firstRecord.toString(), port); + } + }); +} + +void MCResolver::emitFail(std::string error) { + qDebug() << "Ping error:" << QString::fromStdString(error); + emit fail(); +} + +void MCResolver::emitSucceed(QString ip, int port) { + emit succeed(ip, port); +} diff --git a/launcher/ui/pages/instance/McResolver.hpp b/launcher/ui/pages/instance/McResolver.hpp index 4bb767200..e7800d539 100644 --- a/launcher/ui/pages/instance/McResolver.hpp +++ b/launcher/ui/pages/instance/McResolver.hpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -11,73 +12,14 @@ class MCResolver : public QObject { int constrPort; public: - explicit MCResolver(QObject *parent, QString domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} - - void ping() { - pingWithDomainSRV(constrDomain, constrPort); - } + explicit MCResolver(QObject *parent, QString domain, int port); + void ping(); private: - - void pingWithDomainSRV(QString domain, int port) { - QDnsLookup *lookup = new QDnsLookup(this); - lookup->setName(QString("_minecraft._tcp.%1").arg(domain)); - lookup->setType(QDnsLookup::SRV); - - connect(lookup, &QDnsLookup::finished, this, [&, domain, port]() { - QDnsLookup *lookup = qobject_cast(sender()); - - lookup->deleteLater(); - - if (lookup->error() != QDnsLookup::NoError) { - qDebug() << QString("Warning: SRV record lookup failed (%1), trying A record lookup").arg(lookup->errorString()); - pingWithDomainA(domain, port); - return; - } - - auto records = lookup->serviceRecords(); - if (records.isEmpty()) { - qDebug() << "Warning: no SRV entries found for domain, trying A record lookup"; - pingWithDomainA(domain, port); - return; - } - - - const auto& firstRecord = records.at(0); - QString domain = firstRecord.target(); - int port = firstRecord.port(); - pingWithDomainA(domain, port); - }); - - lookup->lookup(); - } - - void pingWithDomainA(QString domain, int port) { - QHostInfo::lookupHost(domain, this, [&, port](const QHostInfo &hostInfo){ - if (hostInfo.error() != QHostInfo::NoError) { - emitFail("A record lookup failed"); - return; - } else { - auto records = hostInfo.addresses(); - if (records.isEmpty()) { - emitFail("No A entries found for domain"); - return; - } - - const auto& firstRecord = records.at(0); - emitSucceed(firstRecord.toString(), port); - } - }); - } - - void emitFail(std::string error) { - qDebug() << "Ping error:" << QString::fromStdString(error); - emit fail(); - } - - void emitSucceed(QString ip, int port) { - emit succeed(ip, port); - } + void pingWithDomainSRV(QString domain, int port); + void pingWithDomainA(QString domain, int port); + void emitFail(std::string error); + void emitSucceed(QString ip, int port); signals: void succeed(QString ip, int port); From 9d5727e36b22d2a2eeb17f6faf0bf5b2c01421fd Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 19:51:15 +0100 Subject: [PATCH 21/71] MCResolver -> McResolver --- launcher/ui/pages/instance/McResolver.cpp | 12 ++++++------ launcher/ui/pages/instance/McResolver.hpp | 4 ++-- launcher/ui/pages/instance/ServersPage.cpp | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index e7103e211..20ebe1e8a 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -5,13 +5,13 @@ #include "McResolver.hpp" -MCResolver::MCResolver(QObject *parent, QString domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} +McResolver::McResolver(QObject *parent, QString domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} -void MCResolver::ping() { +void McResolver::ping() { pingWithDomainSRV(constrDomain, constrPort); } -void MCResolver::pingWithDomainSRV(QString domain, int port) { +void McResolver::pingWithDomainSRV(QString domain, int port) { QDnsLookup *lookup = new QDnsLookup(this); lookup->setName(QString("_minecraft._tcp.%1").arg(domain)); lookup->setType(QDnsLookup::SRV); @@ -44,7 +44,7 @@ void MCResolver::pingWithDomainSRV(QString domain, int port) { lookup->lookup(); } -void MCResolver::pingWithDomainA(QString domain, int port) { +void McResolver::pingWithDomainA(QString domain, int port) { QHostInfo::lookupHost(domain, this, [&, port](const QHostInfo &hostInfo){ if (hostInfo.error() != QHostInfo::NoError) { emitFail("A record lookup failed"); @@ -62,11 +62,11 @@ void MCResolver::pingWithDomainA(QString domain, int port) { }); } -void MCResolver::emitFail(std::string error) { +void McResolver::emitFail(std::string error) { qDebug() << "Ping error:" << QString::fromStdString(error); emit fail(); } -void MCResolver::emitSucceed(QString ip, int port) { +void McResolver::emitSucceed(QString ip, int port) { emit succeed(ip, port); } diff --git a/launcher/ui/pages/instance/McResolver.hpp b/launcher/ui/pages/instance/McResolver.hpp index e7800d539..3f5b4e701 100644 --- a/launcher/ui/pages/instance/McResolver.hpp +++ b/launcher/ui/pages/instance/McResolver.hpp @@ -5,14 +5,14 @@ #include // resolve the IP and port of a Minecraft server -class MCResolver : public QObject { +class McResolver : public QObject { Q_OBJECT QString constrDomain; int constrPort; public: - explicit MCResolver(QObject *parent, QString domain, int port); + explicit McResolver(QObject *parent, QString domain, int port); void ping(); private: diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 7ecf84ebf..39fcaf8c2 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -140,8 +140,8 @@ class ServerPingTask : public Task { virtual void executeTask() override { qDebug() << "Querying status of " << m_server.m_address; auto [domain, port] = m_server.splitAddress(); - MCResolver *resolver = new MCResolver(nullptr, domain, port); - QObject::connect(resolver, &MCResolver::succeed, [=](QString ip, int port) { + McResolver *resolver = new McResolver(nullptr, domain, port); + QObject::connect(resolver, &McResolver::succeed, [=](QString ip, int port) { resolver->deleteLater(); qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; McClient client(nullptr, domain, ip, port); From 7cf24586c2a9ff8e5dd33a390f83b7f098266881 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 19:58:30 +0100 Subject: [PATCH 22/71] .hpp -> .h --- launcher/CMakeLists.txt | 4 ++-- launcher/ui/pages/instance/McClient.cpp | 2 +- launcher/ui/pages/instance/{McClient.hpp => McClient.h} | 0 launcher/ui/pages/instance/McResolver.cpp | 2 +- launcher/ui/pages/instance/{McResolver.hpp => McResolver.h} | 0 launcher/ui/pages/instance/ServersPage.cpp | 4 ++-- 6 files changed, 6 insertions(+), 6 deletions(-) rename launcher/ui/pages/instance/{McClient.hpp => McClient.h} (100%) rename launcher/ui/pages/instance/{McResolver.hpp => McResolver.h} (100%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e05b0fafc..2cfb63af3 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -919,9 +919,9 @@ SET(LAUNCHER_SOURCES ui/pages/instance/WorldListPage.cpp ui/pages/instance/WorldListPage.h ui/pages/instance/McClient.cpp - ui/pages/instance/McClient.hpp + ui/pages/instance/McClient.h ui/pages/instance/McResolver.cpp - ui/pages/instance/McResolver.hpp + ui/pages/instance/McResolver.h # GUI - global settings pages ui/pages/global/AccountListPage.cpp diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index f49d4c8ad..609864bbb 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -4,7 +4,7 @@ #include #include -#include "McClient.hpp" +#include "McClient.h" #define SEGMENT_BITS 0x7F #define CONTINUE_BIT 0x80 diff --git a/launcher/ui/pages/instance/McClient.hpp b/launcher/ui/pages/instance/McClient.h similarity index 100% rename from launcher/ui/pages/instance/McClient.hpp rename to launcher/ui/pages/instance/McClient.h diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 20ebe1e8a..880e3d5ef 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -3,7 +3,7 @@ #include #include -#include "McResolver.hpp" +#include "McResolver.h" McResolver::McResolver(QObject *parent, QString domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} diff --git a/launcher/ui/pages/instance/McResolver.hpp b/launcher/ui/pages/instance/McResolver.h similarity index 100% rename from launcher/ui/pages/instance/McResolver.hpp rename to launcher/ui/pages/instance/McResolver.h diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 39fcaf8c2..0ea2caaca 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -39,8 +39,8 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" -#include "McClient.hpp" -#include "McResolver.hpp" +#include "McClient.h" +#include "McResolver.h" #include #include From 7d04f0ee758cb3c0c9c4273de67424269d63507c Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 17 Nov 2024 20:06:08 +0100 Subject: [PATCH 23/71] remove useless code when reading response --- launcher/ui/pages/instance/McClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 609864bbb..3259feed5 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -55,7 +55,7 @@ void McClient::readBytesExactFromSocket(QByteArray &resp, int bytesToRead) { throw Exception("Read timeout or error"); } - QByteArray chunk = socket.read(qMin(bytesToRead, socket.bytesAvailable())); + QByteArray chunk = socket.read(bytesToRead); resp.append(chunk); bytesToRead -= chunk.size(); } From 60fb922ba2ed56703e5d64626f015e57d5cb0de5 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 19 Nov 2024 08:42:50 +0100 Subject: [PATCH 24/71] remove QObject parent argument from ServerPingTask constructor --- launcher/ui/pages/instance/ServersPage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 0ea2caaca..1d39ca7ee 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -132,7 +132,7 @@ struct Server { class ServerPingTask : public Task { Q_OBJECT public: - explicit ServerPingTask(QObject* parent, Server &server) : Task(parent), m_server(server) {} + explicit ServerPingTask(Server &server) : Task(), m_server(server) {} ~ServerPingTask() override = default; @@ -485,7 +485,7 @@ class ServersModel : public QAbstractListModel { { ConcurrentTask::Ptr job(new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); for (auto& server : m_servers) { - ServerPingTask *task = new ServerPingTask(this, server); + ServerPingTask *task = new ServerPingTask(server); job->addTask(Task::Ptr(task)); } job->start(); From a79a66c177194f1761f353e00bc53f1dc653eea3 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 19 Nov 2024 19:42:49 +0100 Subject: [PATCH 25/71] remove debug print --- launcher/ui/pages/instance/ServersPage.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 1d39ca7ee..4ccfd4bb8 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -794,7 +794,6 @@ void ServersPage::on_actionJoin_triggered() void ServersPage::on_actionRefresh_triggered() { - qDebug() << "Action clicked"; m_model->queryServersStatus(); } From 1fb0fe0171fa2db09b296a62d636bab1ea026009 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 19 Nov 2024 20:48:17 +0100 Subject: [PATCH 26/71] ping servers when opening Servers page --- launcher/ui/pages/instance/ServersPage.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 4ccfd4bb8..8f32cbc94 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -734,6 +734,9 @@ void ServersPage::openedImpl() m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); + + // ping servers + m_model->queryServersStatus(); } void ServersPage::closedImpl() From 5cfb5a6f0db81e850ed3299759ffc131d049f289 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 24 Nov 2024 16:37:42 +0100 Subject: [PATCH 27/71] do not capture things implicitely in lambdas --- launcher/ui/pages/instance/ServersPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 8f32cbc94..459179c03 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -141,7 +141,7 @@ class ServerPingTask : public Task { qDebug() << "Querying status of " << m_server.m_address; auto [domain, port] = m_server.splitAddress(); McResolver *resolver = new McResolver(nullptr, domain, port); - QObject::connect(resolver, &McResolver::succeed, [=](QString ip, int port) { + QObject::connect(resolver, &McResolver::succeed, [this, resolver, domain](QString ip, int port) { resolver->deleteLater(); qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; McClient client(nullptr, domain, ip, port); From 9ce5eaaa0c8e8ca74dce42e2d287e865905efdf4 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 24 Nov 2024 23:54:19 +0100 Subject: [PATCH 28/71] fix `job` being deleted before tasks are finished --- launcher/ui/pages/instance/ServersPage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 459179c03..8193df4db 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -483,12 +483,16 @@ class ServersModel : public QAbstractListModel { void queryServersStatus() { - ConcurrentTask::Ptr job(new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); + auto *job = new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); for (auto& server : m_servers) { ServerPingTask *task = new ServerPingTask(server); job->addTask(Task::Ptr(task)); } job->start(); + + connect(job, &ConcurrentTask::finished, [job]() { + job->deleteLater(); + }); } public slots: From 0c6f78dee2c16820624e2f55e12d903392baad75 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 25 Nov 2024 00:10:17 +0100 Subject: [PATCH 29/71] communicate when ServerPingTask succeeds/fails --- launcher/ui/pages/instance/ServersPage.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 8193df4db..86e13b7eb 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -150,8 +150,10 @@ class ServerPingTask : public Task { int online = client.getOnlinePlayers(); qDebug() << "Online players: " << online; m_server.m_currentPlayers = online; + emitSucceeded(); } catch(const Exception& e) { qDebug() << "Failed to get online players: " << e.cause(); + emitFailed(e.cause()); } }); resolver->ping(); From 7c8d2c9b55526db21a9656a65adbe4b2cfabd892 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 25 Nov 2024 00:16:25 +0100 Subject: [PATCH 30/71] always delete McResolver object, even when it fails --- launcher/ui/pages/instance/McResolver.cpp | 2 ++ launcher/ui/pages/instance/McResolver.h | 1 + launcher/ui/pages/instance/ServersPage.cpp | 11 +++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 880e3d5ef..1159e5df2 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -65,8 +65,10 @@ void McResolver::pingWithDomainA(QString domain, int port) { void McResolver::emitFail(std::string error) { qDebug() << "Ping error:" << QString::fromStdString(error); emit fail(); + emit finish(); } void McResolver::emitSucceed(QString ip, int port) { emit succeed(ip, port); + emit finish(); } diff --git a/launcher/ui/pages/instance/McResolver.h b/launcher/ui/pages/instance/McResolver.h index 3f5b4e701..5de4e8de4 100644 --- a/launcher/ui/pages/instance/McResolver.h +++ b/launcher/ui/pages/instance/McResolver.h @@ -24,4 +24,5 @@ private: signals: void succeed(QString ip, int port); void fail(); + void finish(); }; diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 86e13b7eb..2efaef187 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -139,13 +139,15 @@ class ServerPingTask : public Task { protected: virtual void executeTask() override { qDebug() << "Querying status of " << m_server.m_address; + + // Resolve the actual IP and port for the server auto [domain, port] = m_server.splitAddress(); McResolver *resolver = new McResolver(nullptr, domain, port); QObject::connect(resolver, &McResolver::succeed, [this, resolver, domain](QString ip, int port) { - resolver->deleteLater(); qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; - McClient client(nullptr, domain, ip, port); + // Now that we have the IP and port, query the server + McClient client(nullptr, domain, ip, port); try { int online = client.getOnlinePlayers(); qDebug() << "Online players: " << online; @@ -156,6 +158,11 @@ class ServerPingTask : public Task { emitFailed(e.cause()); } }); + + // Delete McResolver object when done + QObject::connect(resolver, &McResolver::finish, [resolver]() { + resolver->deleteLater(); + }); resolver->ping(); } From 24b9815763ce1f81b30c4d422ee0d2dc5660dede Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 25 Nov 2024 00:16:46 +0100 Subject: [PATCH 31/71] cleanup --- launcher/ui/pages/instance/ServersPage.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 2efaef187..62742076a 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -144,7 +144,7 @@ class ServerPingTask : public Task { auto [domain, port] = m_server.splitAddress(); McResolver *resolver = new McResolver(nullptr, domain, port); QObject::connect(resolver, &McResolver::succeed, [this, resolver, domain](QString ip, int port) { - qDebug() << "Resolved Addresse for" << domain << ": " << ip << ":" << port; + qDebug() << "Resolved Address for" << domain << ": " << ip << ":" << port; // Now that we have the IP and port, query the server McClient client(nullptr, domain, ip, port); @@ -494,8 +494,7 @@ class ServersModel : public QAbstractListModel { { auto *job = new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); for (auto& server : m_servers) { - ServerPingTask *task = new ServerPingTask(server); - job->addTask(Task::Ptr(task)); + job->addTask(Task::Ptr(new ServerPingTask(server))); } job->start(); From 7d2da194184180846362af7c5d8e3726db190b88 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 25 Nov 2024 01:09:44 +0100 Subject: [PATCH 32/71] make McResolver have the same signal name conventions as Task --- launcher/ui/pages/instance/McResolver.cpp | 8 ++++---- launcher/ui/pages/instance/McResolver.h | 6 +++--- launcher/ui/pages/instance/ServersPage.cpp | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 1159e5df2..6dd37ff8b 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -64,11 +64,11 @@ void McResolver::pingWithDomainA(QString domain, int port) { void McResolver::emitFail(std::string error) { qDebug() << "Ping error:" << QString::fromStdString(error); - emit fail(); - emit finish(); + emit failed(); + emit finished(); } void McResolver::emitSucceed(QString ip, int port) { - emit succeed(ip, port); - emit finish(); + emit succeeded(ip, port); + emit finished(); } diff --git a/launcher/ui/pages/instance/McResolver.h b/launcher/ui/pages/instance/McResolver.h index 5de4e8de4..4837b029e 100644 --- a/launcher/ui/pages/instance/McResolver.h +++ b/launcher/ui/pages/instance/McResolver.h @@ -22,7 +22,7 @@ private: void emitSucceed(QString ip, int port); signals: - void succeed(QString ip, int port); - void fail(); - void finish(); + void succeeded(QString ip, int port); + void failed(); + void finished(); }; diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 62742076a..875d63869 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -143,7 +143,7 @@ class ServerPingTask : public Task { // Resolve the actual IP and port for the server auto [domain, port] = m_server.splitAddress(); McResolver *resolver = new McResolver(nullptr, domain, port); - QObject::connect(resolver, &McResolver::succeed, [this, resolver, domain](QString ip, int port) { + QObject::connect(resolver, &McResolver::succeeded, [this, resolver, domain](QString ip, int port) { qDebug() << "Resolved Address for" << domain << ": " << ip << ":" << port; // Now that we have the IP and port, query the server @@ -160,7 +160,7 @@ class ServerPingTask : public Task { }); // Delete McResolver object when done - QObject::connect(resolver, &McResolver::finish, [resolver]() { + QObject::connect(resolver, &McResolver::finished, [resolver]() { resolver->deleteLater(); }); resolver->ping(); From ca6d66970ea52ae95c064babf42f74a1d953717b Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 25 Nov 2024 23:49:03 +0100 Subject: [PATCH 33/71] add documentation for Task and ConcurrentTask --- launcher/tasks/ConcurrentTask.h | 5 +++++ launcher/tasks/Task.h | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index d988623b9..cc6256cf8 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -43,6 +43,10 @@ #include "tasks/Task.h" +/*! + * Runs a list of tasks concurrently (according to `max_concurrent` parameter). + * Behaviour is the same as regular Task (e.g. starts using start()) + */ class ConcurrentTask : public Task { Q_OBJECT public: @@ -59,6 +63,7 @@ class ConcurrentTask : public Task { inline auto isMultiStep() const -> bool override { return totalSize() > 1; } auto getStepProgress() const -> TaskStepProgressList override; + //! Adds a task to execute in this ConcurrentTask void addTask(Task::Ptr task); public slots: diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index e712700a1..3f4d8274e 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -79,6 +79,13 @@ Q_DECLARE_METATYPE(TaskStepProgress) using TaskStepProgressList = QList>; + +/*! + * Represents a task that has to be done. + * To create a task, you need to subclass this class, implement the executeTask() method and call + * emitSucceeded() or emitFailed() when the task is done. + * the caller needs to call start() to start the task. + */ class Task : public QObject, public QRunnable { Q_OBJECT public: @@ -130,9 +137,10 @@ class Task : public QObject, public QRunnable { signals: void started(); void progress(qint64 current, qint64 total); + //! called when a task has eother succeeded, aborted or failed. void finished(); - void succeeded(); - void aborted(); + void succeeded(); // called when a task has succeeded + void aborted(); // called when a task has been aborted by calling abort() void failed(QString reason); void status(QString status); void details(QString details); @@ -146,6 +154,7 @@ class Task : public QObject, public QRunnable { // QRunnable's interface void run() override { start(); } + // used by the task caller to start the task virtual void start(); virtual bool abort() { @@ -161,11 +170,17 @@ class Task : public QObject, public QRunnable { } protected: + /*! + * The task subclass must implement this method. This method is called to start to run the task. + * The task is not finished when this method returns. the subclass must manually call emitSucceeded() or emitFailed() instead. + */ virtual void executeTask() = 0; protected slots: + //! The Task subclass must call this method when the task has succeeded virtual void emitSucceeded(); virtual void emitAborted(); + //! The Task subclass must call this method when the task has failed virtual void emitFailed(QString reason = ""); virtual void propagateStepProgress(TaskStepProgress const& task_progress); From 520d6b0b42eae5b7fb5757cc07d96b53d2f6e7e8 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 13:20:14 +0100 Subject: [PATCH 34/71] run socket code in thread --- launcher/ui/pages/instance/McClient.cpp | 17 +++++++++--- launcher/ui/pages/instance/McClient.h | 5 ++-- launcher/ui/pages/instance/ServersPage.cpp | 32 +++++++++++++++------- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 3259feed5..ac0c62242 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -5,13 +5,14 @@ #include #include "McClient.h" +#include #define SEGMENT_BITS 0x7F #define CONTINUE_BIT 0x80 McClient::McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), domain(domain), ip(ip), port(port) {} -QJsonObject McClient::getStatusData() { +QJsonObject McClient::getStatusDataBlocking() { qDebug() << "Connecting to socket.."; socket.connectToHost(ip, port); @@ -27,9 +28,17 @@ QJsonObject McClient::getStatusData() { return readResponse(); } -int McClient::getOnlinePlayers() { - auto status = getStatusData(); - return status.value("players").toObject().value("online").toInt(); +QFuture McClient::getOnlinePlayers() { + return QtConcurrent::run([this]() { + try { + auto status = getStatusDataBlocking(); + int onlinePlayers = status.value("players").toObject().value("online").toInt(); + return onlinePlayers; + } catch (const Exception &e) { + qDebug() << "Error: " << e.what(); + return -1; + } + }); } void McClient::sendRequest() { diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index e3da1fbcf..f9334de6d 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -19,8 +20,8 @@ class McClient : public QObject { public: explicit McClient(QObject *parent, QString domain, QString ip, short port); - QJsonObject getStatusData(); - int getOnlinePlayers(); + QJsonObject getStatusDataBlocking(); + QFuture getOnlinePlayers(); void sendRequest(); void readBytesExactFromSocket(QByteArray &resp, int bytesToRead); QJsonObject readResponse(); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 875d63869..08ac7573e 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things. @@ -147,16 +148,27 @@ class ServerPingTask : public Task { qDebug() << "Resolved Address for" << domain << ": " << ip << ":" << port; // Now that we have the IP and port, query the server - McClient client(nullptr, domain, ip, port); - try { - int online = client.getOnlinePlayers(); - qDebug() << "Online players: " << online; - m_server.m_currentPlayers = online; - emitSucceeded(); - } catch(const Exception& e) { - qDebug() << "Failed to get online players: " << e.cause(); - emitFailed(e.cause()); - } + McClient *client = new McClient(nullptr, domain, ip, port); + auto onlineFuture = client->getOnlinePlayers(); + + // Wait for query to finish + QFutureWatcher *watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcher::finished, [this, client, onlineFuture, watcher]() { + client->deleteLater(); + watcher->deleteLater(); + + int online = onlineFuture.result(); + if (online == -1) { + qDebug() << "Failed to get online players"; + emitFailed(); + return; + } else { + qDebug() << "Online players: " << online; + m_server.m_currentPlayers = online; + emitSucceeded(); + } + }); + watcher->setFuture(onlineFuture); }); // Delete McResolver object when done From 4fad298d675102df39fadd0837bc36450068a7dc Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 13:21:39 +0100 Subject: [PATCH 35/71] put more McClient methods to private --- launcher/ui/pages/instance/McClient.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index f9334de6d..9c3a9a977 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -20,12 +20,13 @@ class McClient : public QObject { public: explicit McClient(QObject *parent, QString domain, QString ip, short port); - QJsonObject getStatusDataBlocking(); QFuture getOnlinePlayers(); - void sendRequest(); - void readBytesExactFromSocket(QByteArray &resp, int bytesToRead); - QJsonObject readResponse(); private: + QJsonObject getStatusDataBlocking(); + void sendRequest(); + QJsonObject readResponse(); + void readBytesExactFromSocket(QByteArray &resp, int bytesToRead); + void writeVarInt(QByteArray &data, int value); int readVarInt(QByteArray &data); char readByte(QByteArray &data); From 087ab70143c69de8e8b9c5006393137f42b41f1c Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 14:33:02 +0100 Subject: [PATCH 36/71] refresh UI when we got the players online --- launcher/ui/pages/instance/ServersPage.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 08ac7573e..3fd5e8fb1 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -505,8 +505,14 @@ class ServersModel : public QAbstractListModel { void queryServersStatus() { auto *job = new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + int row = 0; for (auto& server : m_servers) { - job->addTask(Task::Ptr(new ServerPingTask(server))); + auto *task = new ServerPingTask(server); + job->addTask(Task::Ptr(task)); + connect(task, &Task::finished, [this, row]() { + emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); + }); + row++; } job->start(); From 7c61fec8e0e753de1b7215e45b0a4272551ffac4 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 18:03:35 +0100 Subject: [PATCH 37/71] Make task output online players rather than updating Server itself This doesn't really change anything --- launcher/ui/pages/instance/ServersPage.cpp | 30 ++++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 3fd5e8fb1..2fa823d4e 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -133,22 +133,25 @@ struct Server { class ServerPingTask : public Task { Q_OBJECT public: - explicit ServerPingTask(Server &server) : Task(), m_server(server) {} + explicit ServerPingTask(QString domain, int port) : Task(), m_domain(domain), m_port(port) {} ~ServerPingTask() override = default; + int m_outputOnlinePlayers = -1; + private: + QString m_domain; + int m_port; protected: virtual void executeTask() override { - qDebug() << "Querying status of " << m_server.m_address; + qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port); // Resolve the actual IP and port for the server - auto [domain, port] = m_server.splitAddress(); - McResolver *resolver = new McResolver(nullptr, domain, port); - QObject::connect(resolver, &McResolver::succeeded, [this, resolver, domain](QString ip, int port) { - qDebug() << "Resolved Address for" << domain << ": " << ip << ":" << port; + McResolver *resolver = new McResolver(nullptr, m_domain, m_port); + QObject::connect(resolver, &McResolver::succeeded, [this, resolver](QString ip, int port) { + qDebug() << "Resolved Address for" << m_domain << ": " << ip << ":" << port; // Now that we have the IP and port, query the server - McClient *client = new McClient(nullptr, domain, ip, port); + McClient *client = new McClient(nullptr, m_domain, ip, port); auto onlineFuture = client->getOnlinePlayers(); // Wait for query to finish @@ -164,7 +167,7 @@ class ServerPingTask : public Task { return; } else { qDebug() << "Online players: " << online; - m_server.m_currentPlayers = online; + m_outputOnlinePlayers = online; emitSucceeded(); } }); @@ -177,9 +180,6 @@ class ServerPingTask : public Task { }); resolver->ping(); } - - private: - Server &m_server; }; static std::unique_ptr parseServersDat(const QString& filename) @@ -506,10 +506,12 @@ class ServersModel : public QAbstractListModel { { auto *job = new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); int row = 0; - for (auto& server : m_servers) { - auto *task = new ServerPingTask(server); + for (Server &server : m_servers) { + auto [domain, port] = server.splitAddress(); + auto *task = new ServerPingTask(domain, port); job->addTask(Task::Ptr(task)); - connect(task, &Task::finished, [this, row]() { + connect(task, &Task::finished, [this, task, row, &server]() { + server.m_currentPlayers = task->m_outputOnlinePlayers; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); }); row++; From fef8ee2d1b4f866437b30ec4ea8fd5cae2426e9c Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 18:16:15 +0100 Subject: [PATCH 38/71] Disconnect task finished signal when ServersModel is destroyed --- launcher/ui/pages/instance/ServersPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 2fa823d4e..0d77526dd 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -510,7 +510,7 @@ class ServersModel : public QAbstractListModel { auto [domain, port] = server.splitAddress(); auto *task = new ServerPingTask(domain, port); job->addTask(Task::Ptr(task)); - connect(task, &Task::finished, [this, task, row, &server]() { + connect(task, &Task::finished, this, [this, task, row, &server]() { server.m_currentPlayers = task->m_outputOnlinePlayers; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); }); From 3a9c03098267fd64a27b9bfe62dddccbdffc6cf0 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 18:24:46 +0100 Subject: [PATCH 39/71] Do not timeout after just 3000ms Keep the network default timeout --- launcher/ui/pages/instance/McClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index ac0c62242..ed46ed03b 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -16,13 +16,13 @@ QJsonObject McClient::getStatusDataBlocking() { qDebug() << "Connecting to socket.."; socket.connectToHost(ip, port); - if (!socket.waitForConnected(3000)) { + if (!socket.waitForConnected()) { throw Exception("Failed to connect to socket"); } qDebug() << "Connected to socket successfully"; sendRequest(); - if (!socket.waitForReadyRead(3000)) { + if (!socket.waitForReadyRead()) { throw Exception("Socket didn't send anything to read"); } return readResponse(); From f05548f3a36c0badcedec86d6fc5dd7a973bd36e Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 18:43:14 +0100 Subject: [PATCH 40/71] remove implicit captures --- launcher/ui/pages/instance/McResolver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 6dd37ff8b..df3ea3864 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -16,7 +16,7 @@ void McResolver::pingWithDomainSRV(QString domain, int port) { lookup->setName(QString("_minecraft._tcp.%1").arg(domain)); lookup->setType(QDnsLookup::SRV); - connect(lookup, &QDnsLookup::finished, this, [&, domain, port]() { + connect(lookup, &QDnsLookup::finished, this, [this, domain, port]() { QDnsLookup *lookup = qobject_cast(sender()); lookup->deleteLater(); @@ -45,7 +45,7 @@ void McResolver::pingWithDomainSRV(QString domain, int port) { } void McResolver::pingWithDomainA(QString domain, int port) { - QHostInfo::lookupHost(domain, this, [&, port](const QHostInfo &hostInfo){ + QHostInfo::lookupHost(domain, this, [this, port](const QHostInfo &hostInfo){ if (hostInfo.error() != QHostInfo::NoError) { emitFail("A record lookup failed"); return; From 66f36195d8d2d4c362e94dc56239feb42e31fd36 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 18:44:21 +0100 Subject: [PATCH 41/71] simplify code --- launcher/ui/pages/instance/McResolver.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index df3ea3864..3801d48a7 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -49,16 +49,16 @@ void McResolver::pingWithDomainA(QString domain, int port) { if (hostInfo.error() != QHostInfo::NoError) { emitFail("A record lookup failed"); return; - } else { - auto records = hostInfo.addresses(); - if (records.isEmpty()) { - emitFail("No A entries found for domain"); - return; - } - - const auto& firstRecord = records.at(0); - emitSucceed(firstRecord.toString(), port); } + + auto records = hostInfo.addresses(); + if (records.isEmpty()) { + emitFail("No A entries found for domain"); + return; + } + + const auto& firstRecord = records.at(0); + emitSucceed(firstRecord.toString(), port); }); } From ca52d00b8040e27860f931bdf5a6e7ce92c08413 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 18:47:13 +0100 Subject: [PATCH 42/71] replace std::string by QString in emitFail() --- launcher/ui/pages/instance/McResolver.cpp | 4 ++-- launcher/ui/pages/instance/McResolver.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 3801d48a7..8b5b2e887 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -62,8 +62,8 @@ void McResolver::pingWithDomainA(QString domain, int port) { }); } -void McResolver::emitFail(std::string error) { - qDebug() << "Ping error:" << QString::fromStdString(error); +void McResolver::emitFail(QString error) { + qDebug() << "Ping error:" << error; emit failed(); emit finished(); } diff --git a/launcher/ui/pages/instance/McResolver.h b/launcher/ui/pages/instance/McResolver.h index 4837b029e..06947a03c 100644 --- a/launcher/ui/pages/instance/McResolver.h +++ b/launcher/ui/pages/instance/McResolver.h @@ -18,7 +18,7 @@ public: private: void pingWithDomainSRV(QString domain, int port); void pingWithDomainA(QString domain, int port); - void emitFail(std::string error); + void emitFail(QString error); void emitSucceed(QString ip, int port); signals: From 26f50f9b818e68fc21c8ae06fc313bfc2e0da6e9 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Tue, 26 Nov 2024 18:50:37 +0100 Subject: [PATCH 43/71] connect job to deletion task before starting it This ensures the signal is not fired before we have connected it --- launcher/ui/pages/instance/ServersPage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 0d77526dd..954cdc046 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -516,11 +516,12 @@ class ServersModel : public QAbstractListModel { }); row++; } - job->start(); connect(job, &ConcurrentTask::finished, [job]() { job->deleteLater(); }); + + job->start(); } public slots: From 873232ebe3b3e41a97f908018dd5ff7fd14be10e Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 28 Nov 2024 17:29:35 +0100 Subject: [PATCH 44/71] remove infinite loop in writeVarInt() --- launcher/ui/pages/instance/McClient.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index ed46ed03b..f2cf1394f 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -7,6 +7,7 @@ #include "McClient.h" #include +// 7 first bits #define SEGMENT_BITS 0x7F #define CONTINUE_BIT 0x80 @@ -99,17 +100,15 @@ QJsonObject McClient::readResponse() { // From https://wiki.vg/Protocol#VarInt_and_VarLong void McClient::writeVarInt(QByteArray &data, int value) { - while (true) { - if ((value & ~SEGMENT_BITS) == 0) { - data.append(value); - return; - } - + while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits + // Write 7 bits data.append((value & SEGMENT_BITS) | CONTINUE_BIT); + // Erase theses 7 bits from the value to write // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone value >>= 7; } + data.append(value); } // From https://wiki.vg/Protocol#VarInt_and_VarLong From 8b90a9f2b3e3d58b9725eda081cf209781c13aa6 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 28 Nov 2024 17:32:49 +0100 Subject: [PATCH 45/71] remove infinite loop from readVarInt() --- launcher/ui/pages/instance/McClient.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index f2cf1394f..f8a222645 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -117,17 +117,17 @@ int McClient::readVarInt(QByteArray &data) { int position = 0; char currentByte; - while (true) { + while (position < 32) { currentByte = readByte(data); value |= (currentByte & SEGMENT_BITS) << position; if ((currentByte & CONTINUE_BIT) == 0) break; position += 7; - - if (position >= 32) throw Exception("VarInt is too big"); } + if (position >= 32) throw Exception("VarInt is too big"); + return value; } From dbb88ca7df6594f65cba5c5e5bd821a0c2605c00 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 28 Nov 2024 17:46:42 +0100 Subject: [PATCH 46/71] move ServerPingTask in its own file --- launcher/CMakeLists.txt | 2 + launcher/ui/pages/instance/ServerPingTask.cpp | 44 +++++++++++++++ launcher/ui/pages/instance/ServerPingTask.h | 22 ++++++++ launcher/ui/pages/instance/ServersPage.cpp | 56 +------------------ 4 files changed, 69 insertions(+), 55 deletions(-) create mode 100644 launcher/ui/pages/instance/ServerPingTask.cpp create mode 100644 launcher/ui/pages/instance/ServerPingTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 2cfb63af3..e60c16913 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -922,6 +922,8 @@ SET(LAUNCHER_SOURCES ui/pages/instance/McClient.h ui/pages/instance/McResolver.cpp ui/pages/instance/McResolver.h + ui/pages/instance/ServerPingTask.cpp + ui/pages/instance/ServerPingTask.h # GUI - global settings pages ui/pages/global/AccountListPage.cpp diff --git a/launcher/ui/pages/instance/ServerPingTask.cpp b/launcher/ui/pages/instance/ServerPingTask.cpp new file mode 100644 index 000000000..42144f354 --- /dev/null +++ b/launcher/ui/pages/instance/ServerPingTask.cpp @@ -0,0 +1,44 @@ +#include + +#include "ServerPingTask.h" +#include "McResolver.h" +#include "McClient.h" + +void ServerPingTask::executeTask() { + qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port); + + // Resolve the actual IP and port for the server + McResolver *resolver = new McResolver(nullptr, m_domain, m_port); + QObject::connect(resolver, &McResolver::succeeded, [this, resolver](QString ip, int port) { + qDebug() << "Resolved Address for" << m_domain << ": " << ip << ":" << port; + + // Now that we have the IP and port, query the server + McClient *client = new McClient(nullptr, m_domain, ip, port); + auto onlineFuture = client->getOnlinePlayers(); + + // Wait for query to finish + QFutureWatcher *watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcher::finished, [this, client, onlineFuture, watcher]() { + client->deleteLater(); + watcher->deleteLater(); + + int online = onlineFuture.result(); + if (online == -1) { + qDebug() << "Failed to get online players"; + emitFailed(); + return; + } else { + qDebug() << "Online players: " << online; + m_outputOnlinePlayers = online; + emitSucceeded(); + } + }); + watcher->setFuture(onlineFuture); + }); + + // Delete McResolver object when done + QObject::connect(resolver, &McResolver::finished, [resolver]() { + resolver->deleteLater(); + }); + resolver->ping(); +} \ No newline at end of file diff --git a/launcher/ui/pages/instance/ServerPingTask.h b/launcher/ui/pages/instance/ServerPingTask.h new file mode 100644 index 000000000..0956a4f63 --- /dev/null +++ b/launcher/ui/pages/instance/ServerPingTask.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include + + +class ServerPingTask : public Task { + Q_OBJECT + public: + explicit ServerPingTask(QString domain, int port) : Task(), m_domain(domain), m_port(port) {} + ~ServerPingTask() override = default; + int m_outputOnlinePlayers = -1; + + private: + QString m_domain; + int m_port; + + protected: + virtual void executeTask() override; +}; diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 954cdc046..98058ddad 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -38,9 +38,7 @@ #include "ServersPage.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" - -#include "McClient.h" -#include "McResolver.h" +#include "ServerPingTask.h" #include #include @@ -130,58 +128,6 @@ struct Server { int m_maxPlayers = 0; }; -class ServerPingTask : public Task { - Q_OBJECT - public: - explicit ServerPingTask(QString domain, int port) : Task(), m_domain(domain), m_port(port) {} - ~ServerPingTask() override = default; - int m_outputOnlinePlayers = -1; - - private: - QString m_domain; - int m_port; - - protected: - virtual void executeTask() override { - qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port); - - // Resolve the actual IP and port for the server - McResolver *resolver = new McResolver(nullptr, m_domain, m_port); - QObject::connect(resolver, &McResolver::succeeded, [this, resolver](QString ip, int port) { - qDebug() << "Resolved Address for" << m_domain << ": " << ip << ":" << port; - - // Now that we have the IP and port, query the server - McClient *client = new McClient(nullptr, m_domain, ip, port); - auto onlineFuture = client->getOnlinePlayers(); - - // Wait for query to finish - QFutureWatcher *watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcher::finished, [this, client, onlineFuture, watcher]() { - client->deleteLater(); - watcher->deleteLater(); - - int online = onlineFuture.result(); - if (online == -1) { - qDebug() << "Failed to get online players"; - emitFailed(); - return; - } else { - qDebug() << "Online players: " << online; - m_outputOnlinePlayers = online; - emitSucceeded(); - } - }); - watcher->setFuture(onlineFuture); - }); - - // Delete McResolver object when done - QObject::connect(resolver, &McResolver::finished, [resolver]() { - resolver->deleteLater(); - }); - resolver->ping(); - } -}; - static std::unique_ptr parseServersDat(const QString& filename) { try { From 6f9be258dc87752c3d8d80cac44570575ecba7b0 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 28 Nov 2024 17:49:35 +0100 Subject: [PATCH 47/71] add documentation about task abortion --- launcher/tasks/Task.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 3f4d8274e..60799498f 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -156,6 +156,7 @@ class Task : public QObject, public QRunnable { // used by the task caller to start the task virtual void start(); + //! used by external code to ask the task to aborta virtual bool abort() { if (canAbort()) @@ -179,6 +180,7 @@ class Task : public QObject, public QRunnable { protected slots: //! The Task subclass must call this method when the task has succeeded virtual void emitSucceeded(); + //! **The Task subclass** must call this method when the task has succeeded. External code should call abort() instead. virtual void emitAborted(); //! The Task subclass must call this method when the task has failed virtual void emitFailed(QString reason = ""); From d124e2e0cba25e914bb885a4efd785294790227c Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 28 Nov 2024 17:50:40 +0100 Subject: [PATCH 48/71] cleanup --- launcher/tasks/Task.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 60799498f..7a61ddbb4 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -146,15 +146,14 @@ class Task : public QObject, public QRunnable { void details(QString details); void stepProgress(TaskStepProgress const& task_progress); - /** Emitted when the canAbort() status has changed. - */ + //! Emitted when the canAbort() status has changed. */ void abortStatusChanged(bool can_abort); public slots: // QRunnable's interface void run() override { start(); } - // used by the task caller to start the task + //! used by the task caller to start the task virtual void start(); //! used by external code to ask the task to aborta virtual bool abort() From cfb0c97262f2f6467f1f35460938a9160cb2484e Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 28 Nov 2024 18:09:38 +0100 Subject: [PATCH 49/71] use Json::requireInteger() --- launcher/ui/pages/instance/McClient.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index f8a222645..65225f077 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -2,10 +2,11 @@ #include #include #include +#include #include #include "McClient.h" -#include +#include "Json.h" // 7 first bits #define SEGMENT_BITS 0x7F @@ -33,8 +34,8 @@ QFuture McClient::getOnlinePlayers() { return QtConcurrent::run([this]() { try { auto status = getStatusDataBlocking(); - int onlinePlayers = status.value("players").toObject().value("online").toInt(); - return onlinePlayers; + auto players = Json::requireObject(status, "players"); + return Json::requireInteger(players, "online"); } catch (const Exception &e) { qDebug() << "Error: " << e.what(); return -1; From 5eb417ff2be159330558999082ecb64e3ee8f003 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 28 Nov 2024 23:54:51 +0100 Subject: [PATCH 50/71] Always autodelete signal connections with `this` captured when the objet is deleted, just to be sure --- launcher/ui/pages/instance/ServerPingTask.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/ServerPingTask.cpp b/launcher/ui/pages/instance/ServerPingTask.cpp index 42144f354..260405dc0 100644 --- a/launcher/ui/pages/instance/ServerPingTask.cpp +++ b/launcher/ui/pages/instance/ServerPingTask.cpp @@ -9,7 +9,7 @@ void ServerPingTask::executeTask() { // Resolve the actual IP and port for the server McResolver *resolver = new McResolver(nullptr, m_domain, m_port); - QObject::connect(resolver, &McResolver::succeeded, [this, resolver](QString ip, int port) { + QObject::connect(resolver, &McResolver::succeeded, this, [this, resolver](QString ip, int port) { qDebug() << "Resolved Address for" << m_domain << ": " << ip << ":" << port; // Now that we have the IP and port, query the server @@ -18,7 +18,7 @@ void ServerPingTask::executeTask() { // Wait for query to finish QFutureWatcher *watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcher::finished, [this, client, onlineFuture, watcher]() { + QObject::connect(watcher, &QFutureWatcher::finished, this, [this, client, onlineFuture, watcher]() { client->deleteLater(); watcher->deleteLater(); From 3fb6764ea48e942a655aafbbce9fc4a226839eff Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 00:13:43 +0100 Subject: [PATCH 51/71] fix typo --- launcher/tasks/Task.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 7a61ddbb4..0ec818684 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -155,7 +155,7 @@ class Task : public QObject, public QRunnable { //! used by the task caller to start the task virtual void start(); - //! used by external code to ask the task to aborta + //! used by external code to ask the task to abort virtual bool abort() { if (canAbort()) From ae7d3379e4ed2d8aee51026f37b7344026f97de7 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 00:39:43 +0100 Subject: [PATCH 52/71] abort current query task if present when refreshing --- launcher/ui/pages/instance/ServersPage.cpp | 24 +++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 98058ddad..1018c6bd3 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -450,12 +450,18 @@ class ServersModel : public QAbstractListModel { void queryServersStatus() { - auto *job = new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + // Abort the currently running task if present + if (currentQueryTask != nullptr) { + currentQueryTask->abort(); + qDebug() << "Aborted previous server query task"; + } + + currentQueryTask = new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); int row = 0; for (Server &server : m_servers) { auto [domain, port] = server.splitAddress(); auto *task = new ServerPingTask(domain, port); - job->addTask(Task::Ptr(task)); + currentQueryTask->addTask(Task::Ptr(task)); connect(task, &Task::finished, this, [this, task, row, &server]() { server.m_currentPlayers = task->m_outputOnlinePlayers; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); @@ -463,11 +469,18 @@ class ServersModel : public QAbstractListModel { row++; } - connect(job, &ConcurrentTask::finished, [job]() { - job->deleteLater(); + // make task delete itself when done + auto *c = currentQueryTask; + connect(currentQueryTask, &ConcurrentTask::finished, [c]() { + c->deleteLater(); }); - job->start(); + // Also delete it from the model, if the model itself hasn't been deleted + connect(currentQueryTask, &ConcurrentTask::finished, this, [this, c]() { + if (c == currentQueryTask) currentQueryTask = nullptr; + }); + + currentQueryTask->start(); } public slots: @@ -557,6 +570,7 @@ class ServersModel : public QAbstractListModel { QList m_servers; QFileSystemWatcher* m_watcher = nullptr; QTimer m_saveTimer; + ConcurrentTask *currentQueryTask = nullptr; }; ServersPage::ServersPage(InstancePtr inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::ServersPage) From 09782745c6f1e1ad0edfd4222f432d679d10c17b Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 00:43:42 +0100 Subject: [PATCH 53/71] make writePacketToSocket() clear data automatically --- launcher/ui/pages/instance/McClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 65225f077..92db3ba34 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -53,8 +53,6 @@ void McClient::sendRequest() { writeVarInt(data, 0x01); // next state writePacketToSocket(data); // send handshake packet - data.clear(); - writeVarInt(data, 0x00); // packet ID writePacketToSocket(data); // send status packet } @@ -162,4 +160,6 @@ void McClient::writePacketToSocket(QByteArray &data) { // write it to the socket socket.write(dataWithSize); socket.flush(); + + data.clear(); } From b0778e7a1fcf62493638aa9caafcb17a8860a3a1 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 00:42:25 +0100 Subject: [PATCH 54/71] chore --- launcher/tasks/Task.h | 12 ++++++------ launcher/ui/pages/instance/McClient.cpp | 1 + launcher/ui/pages/instance/McClient.h | 2 +- launcher/ui/pages/instance/McResolver.cpp | 1 - 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 0ec818684..91ec06679 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -139,8 +139,10 @@ class Task : public QObject, public QRunnable { void progress(qint64 current, qint64 total); //! called when a task has eother succeeded, aborted or failed. void finished(); - void succeeded(); // called when a task has succeeded - void aborted(); // called when a task has been aborted by calling abort() + //! called when a task has succeeded + void succeeded(); + //! called when a task has been aborted by calling abort() + void aborted(); void failed(QString reason); void status(QString status); void details(QString details); @@ -170,10 +172,8 @@ class Task : public QObject, public QRunnable { } protected: - /*! - * The task subclass must implement this method. This method is called to start to run the task. - * The task is not finished when this method returns. the subclass must manually call emitSucceeded() or emitFailed() instead. - */ + //! The task subclass must implement this method. This method is called to start to run the task. + //! The task is not finished when this method returns. the subclass must manually call emitSucceeded() or emitFailed() instead. virtual void executeTask() = 0; protected slots: diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 92db3ba34..47f7a2b67 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -10,6 +10,7 @@ // 7 first bits #define SEGMENT_BITS 0x7F +// last bit #define CONTINUE_BIT 0x80 McClient::McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), domain(domain), ip(ip), port(port) {} diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 9c3a9a977..374adbddf 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -30,7 +30,7 @@ private: void writeVarInt(QByteArray &data, int value); int readVarInt(QByteArray &data); char readByte(QByteArray &data); - // write number with specified size in big endian format + //! write number with specified size in big endian format void writeFixedInt(QByteArray &data, int value, int size); void writeString(QByteArray &data, const std::string &value); diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 8b5b2e887..42bc3ad2c 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -34,7 +34,6 @@ void McResolver::pingWithDomainSRV(QString domain, int port) { return; } - const auto& firstRecord = records.at(0); QString domain = firstRecord.target(); int port = firstRecord.port(); From 01db826ec92bd41164514518d25b1941b58e5180 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 00:58:08 +0100 Subject: [PATCH 55/71] Reset online players UI component when refreshing --- launcher/ui/pages/instance/ServersPage.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 1018c6bd3..b6ca9fe07 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -459,9 +459,16 @@ class ServersModel : public QAbstractListModel { currentQueryTask = new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); int row = 0; for (Server &server : m_servers) { + // reset current players + server.m_currentPlayers = {}; + emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); + + // Start task to query server status auto [domain, port] = server.splitAddress(); auto *task = new ServerPingTask(domain, port); currentQueryTask->addTask(Task::Ptr(task)); + + // Update the model when the task is done connect(task, &Task::finished, this, [this, task, row, &server]() { server.m_currentPlayers = task->m_outputOnlinePlayers; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); From fd1cb1b58bef3cc0a51f0adcc957740886522e3d Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 01:01:32 +0100 Subject: [PATCH 56/71] DCO Remediation Commit for iTrooz I, iTrooz , hereby add my Signed-off-by to this commit: fac521a312d1f6055b29d12da8b8773adaa24afb I, iTrooz , hereby add my Signed-off-by to this commit: 43a54cafef95a3b4c2181f4d3d1e2d3876b8e7f9 I, iTrooz , hereby add my Signed-off-by to this commit: ee35ac5afdb2a6409b7ad52fde38c411d112f064 I, iTrooz , hereby add my Signed-off-by to this commit: 99ac11bc408b5102a7c9d6d0c003e854d5e7fdef I, iTrooz , hereby add my Signed-off-by to this commit: 8fa1dff17dc4d7953fbf7f0961a33e751183b06b I, iTrooz , hereby add my Signed-off-by to this commit: 2f70115be5555c7c9ad46765f61e6b1cc965e8b4 I, iTrooz , hereby add my Signed-off-by to this commit: ea2a2349f8ebd353ac4ba4296f9514c3f8419c58 I, iTrooz , hereby add my Signed-off-by to this commit: 87c9066a2b218aef5296ab8ad4dddfa483dcd9cb I, iTrooz , hereby add my Signed-off-by to this commit: fe28a051d533bd34681c6306deab5b93b13dc5e9 I, iTrooz , hereby add my Signed-off-by to this commit: 0a379a05ff03b2c55fcc4342c84f90fff64bb9a9 I, iTrooz , hereby add my Signed-off-by to this commit: 6a7678a6e9548501b84ed3a2d81a9cabe6955703 I, iTrooz , hereby add my Signed-off-by to this commit: cba7e2dc362238cbfa9bcc680d164c828cd39836 I, iTrooz , hereby add my Signed-off-by to this commit: 8cf0c2029ccccf40cbf21fd122bea233732911bc I, iTrooz , hereby add my Signed-off-by to this commit: 0d830e56e9c888b860e03ee4a9e44239d67a4d4a I, iTrooz , hereby add my Signed-off-by to this commit: b35cffb3477b997a4dc779696aeb0acb79880d51 I, iTrooz , hereby add my Signed-off-by to this commit: 1f094b98039b4f8c79bc402246babd4f821116db I, iTrooz , hereby add my Signed-off-by to this commit: 2d06e0a11194a3a4c379ddd518b857c4c2b98f54 I, iTrooz , hereby add my Signed-off-by to this commit: c3543b104b5f13778677e2244ab93faf6850cb99 I, iTrooz , hereby add my Signed-off-by to this commit: 8b7040d416b46d8011b97b71505778026c2c5ece I, iTrooz , hereby add my Signed-off-by to this commit: b8035ca0783ba42efbc7c11cf329c6f4372abbc5 I, iTrooz , hereby add my Signed-off-by to this commit: 9d5727e36b22d2a2eeb17f6faf0bf5b2c01421fd I, iTrooz , hereby add my Signed-off-by to this commit: 7cf24586c2a9ff8e5dd33a390f83b7f098266881 I, iTrooz , hereby add my Signed-off-by to this commit: 7d04f0ee758cb3c0c9c4273de67424269d63507c I, iTrooz , hereby add my Signed-off-by to this commit: 60fb922ba2ed56703e5d64626f015e57d5cb0de5 I, iTrooz , hereby add my Signed-off-by to this commit: a79a66c177194f1761f353e00bc53f1dc653eea3 I, iTrooz , hereby add my Signed-off-by to this commit: 1fb0fe0171fa2db09b296a62d636bab1ea026009 I, iTrooz , hereby add my Signed-off-by to this commit: 5cfb5a6f0db81e850ed3299759ffc131d049f289 I, iTrooz , hereby add my Signed-off-by to this commit: 9ce5eaaa0c8e8ca74dce42e2d287e865905efdf4 I, iTrooz , hereby add my Signed-off-by to this commit: 0c6f78dee2c16820624e2f55e12d903392baad75 I, iTrooz , hereby add my Signed-off-by to this commit: 7c8d2c9b55526db21a9656a65adbe4b2cfabd892 I, iTrooz , hereby add my Signed-off-by to this commit: 24b9815763ce1f81b30c4d422ee0d2dc5660dede I, iTrooz , hereby add my Signed-off-by to this commit: 7d2da194184180846362af7c5d8e3726db190b88 I, iTrooz , hereby add my Signed-off-by to this commit: ca6d66970ea52ae95c064babf42f74a1d953717b I, iTrooz , hereby add my Signed-off-by to this commit: 520d6b0b42eae5b7fb5757cc07d96b53d2f6e7e8 I, iTrooz , hereby add my Signed-off-by to this commit: 4fad298d675102df39fadd0837bc36450068a7dc I, iTrooz , hereby add my Signed-off-by to this commit: 087ab70143c69de8e8b9c5006393137f42b41f1c I, iTrooz , hereby add my Signed-off-by to this commit: 7c61fec8e0e753de1b7215e45b0a4272551ffac4 I, iTrooz , hereby add my Signed-off-by to this commit: fef8ee2d1b4f866437b30ec4ea8fd5cae2426e9c I, iTrooz , hereby add my Signed-off-by to this commit: 3a9c03098267fd64a27b9bfe62dddccbdffc6cf0 I, iTrooz , hereby add my Signed-off-by to this commit: f05548f3a36c0badcedec86d6fc5dd7a973bd36e I, iTrooz , hereby add my Signed-off-by to this commit: 66f36195d8d2d4c362e94dc56239feb42e31fd36 I, iTrooz , hereby add my Signed-off-by to this commit: ca52d00b8040e27860f931bdf5a6e7ce92c08413 I, iTrooz , hereby add my Signed-off-by to this commit: 26f50f9b818e68fc21c8ae06fc313bfc2e0da6e9 I, iTrooz , hereby add my Signed-off-by to this commit: 873232ebe3b3e41a97f908018dd5ff7fd14be10e I, iTrooz , hereby add my Signed-off-by to this commit: 8b90a9f2b3e3d58b9725eda081cf209781c13aa6 I, iTrooz , hereby add my Signed-off-by to this commit: dbb88ca7df6594f65cba5c5e5bd821a0c2605c00 I, iTrooz , hereby add my Signed-off-by to this commit: 6f9be258dc87752c3d8d80cac44570575ecba7b0 I, iTrooz , hereby add my Signed-off-by to this commit: d124e2e0cba25e914bb885a4efd785294790227c I, iTrooz , hereby add my Signed-off-by to this commit: cfb0c97262f2f6467f1f35460938a9160cb2484e I, iTrooz , hereby add my Signed-off-by to this commit: 5eb417ff2be159330558999082ecb64e3ee8f003 I, iTrooz , hereby add my Signed-off-by to this commit: 3fb6764ea48e942a655aafbbce9fc4a226839eff I, iTrooz , hereby add my Signed-off-by to this commit: ae7d3379e4ed2d8aee51026f37b7344026f97de7 I, iTrooz , hereby add my Signed-off-by to this commit: 09782745c6f1e1ad0edfd4222f432d679d10c17b I, iTrooz , hereby add my Signed-off-by to this commit: b0778e7a1fcf62493638aa9caafcb17a8860a3a1 I, iTrooz , hereby add my Signed-off-by to this commit: 01db826ec92bd41164514518d25b1941b58e5180 Signed-off-by: iTrooz From 4aaf7b9b0959ac8b07046fd3f97fd24c0f9df4a9 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 14:07:22 +0100 Subject: [PATCH 57/71] chore Signed-off-by: iTrooz --- launcher/tasks/Task.h | 2 +- launcher/ui/pages/instance/ServersPage.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 91ec06679..b5d9bec65 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -179,7 +179,7 @@ class Task : public QObject, public QRunnable { protected slots: //! The Task subclass must call this method when the task has succeeded virtual void emitSucceeded(); - //! **The Task subclass** must call this method when the task has succeeded. External code should call abort() instead. + //! **The Task subclass** must call this method when the task has aborted. External code should call abort() instead. virtual void emitAborted(); //! The Task subclass must call this method when the task has failed virtual void emitFailed(QString reason = ""); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index b6ca9fe07..f00821cbd 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -52,7 +52,6 @@ #include #include #include -#include #include static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things. @@ -469,8 +468,9 @@ class ServersModel : public QAbstractListModel { currentQueryTask->addTask(Task::Ptr(task)); // Update the model when the task is done - connect(task, &Task::finished, this, [this, task, row, &server]() { - server.m_currentPlayers = task->m_outputOnlinePlayers; + connect(task, &Task::finished, this, [this, task, row]() { + if (m_servers.size() < row) return; + m_servers[row].m_currentPlayers = task->m_outputOnlinePlayers; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); }); row++; From 1477d644004e6ae48f9ac93783464ee38afdec05 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 14:14:24 +0100 Subject: [PATCH 58/71] add `m_` prefix to class members Signed-off-by: iTrooz --- launcher/ui/pages/instance/McClient.cpp | 24 +++++++++++------------ launcher/ui/pages/instance/McClient.h | 8 ++++---- launcher/ui/pages/instance/McResolver.cpp | 4 ++-- launcher/ui/pages/instance/McResolver.h | 4 ++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 47f7a2b67..978eb9295 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -13,19 +13,19 @@ // last bit #define CONTINUE_BIT 0x80 -McClient::McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), domain(domain), ip(ip), port(port) {} +McClient::McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {} QJsonObject McClient::getStatusDataBlocking() { qDebug() << "Connecting to socket.."; - socket.connectToHost(ip, port); + m_socket.connectToHost(m_ip, m_port); - if (!socket.waitForConnected()) { + if (!m_socket.waitForConnected()) { throw Exception("Failed to connect to socket"); } qDebug() << "Connected to socket successfully"; sendRequest(); - if (!socket.waitForReadyRead()) { + if (!m_socket.waitForReadyRead()) { throw Exception("Socket didn't send anything to read"); } return readResponse(); @@ -48,9 +48,9 @@ void McClient::sendRequest() { QByteArray data; writeVarInt(data, 0x00); // packet ID writeVarInt(data, 0x760); // protocol version - writeVarInt(data, domain.size()); // server address length - writeString(data, domain.toStdString()); // server address - writeFixedInt(data, port, 2); // server port + writeVarInt(data, m_domain.size()); // server address length + writeString(data, m_domain.toStdString()); // server address + writeFixedInt(data, m_port, 2); // server port writeVarInt(data, 0x01); // next state writePacketToSocket(data); // send handshake packet @@ -61,18 +61,18 @@ void McClient::sendRequest() { void McClient::readBytesExactFromSocket(QByteArray &resp, int bytesToRead) { while (bytesToRead > 0) { qDebug() << bytesToRead << " bytes left to read"; - if (!socket.waitForReadyRead()) { + if (!m_socket.waitForReadyRead()) { throw Exception("Read timeout or error"); } - QByteArray chunk = socket.read(bytesToRead); + QByteArray chunk = m_socket.read(bytesToRead); resp.append(chunk); bytesToRead -= chunk.size(); } } QJsonObject McClient::readResponse() { - auto resp = socket.readAll(); + auto resp = m_socket.readAll(); int length = readVarInt(resp); // finish ready response @@ -159,8 +159,8 @@ void McClient::writePacketToSocket(QByteArray &data) { dataWithSize.append(data); // write it to the socket - socket.write(dataWithSize); - socket.flush(); + m_socket.write(dataWithSize); + m_socket.flush(); data.clear(); } diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 374adbddf..4df879609 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -13,10 +13,10 @@ class McClient : public QObject { Q_OBJECT - QString domain; - QString ip; - short port; - QTcpSocket socket; + QString m_domain; + QString m_ip; + short m_port; + QTcpSocket m_socket; public: explicit McClient(QObject *parent, QString domain, QString ip, short port); diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 42bc3ad2c..80f93d9f8 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -5,10 +5,10 @@ #include "McResolver.h" -McResolver::McResolver(QObject *parent, QString domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} +McResolver::McResolver(QObject *parent, QString domain, int port): QObject(parent), m_constrDomain(domain), m_constrPort(port) {} void McResolver::ping() { - pingWithDomainSRV(constrDomain, constrPort); + pingWithDomainSRV(m_constrDomain, m_constrPort); } void McResolver::pingWithDomainSRV(QString domain, int port) { diff --git a/launcher/ui/pages/instance/McResolver.h b/launcher/ui/pages/instance/McResolver.h index 06947a03c..e2840fd8a 100644 --- a/launcher/ui/pages/instance/McResolver.h +++ b/launcher/ui/pages/instance/McResolver.h @@ -8,8 +8,8 @@ class McResolver : public QObject { Q_OBJECT - QString constrDomain; - int constrPort; + QString m_constrDomain; + int m_constrPort; public: explicit McResolver(QObject *parent, QString domain, int port); From ba0bd5fd741a4c17c9f874f606ff6a052cde788d Mon Sep 17 00:00:00 2001 From: iTrooz Date: Fri, 29 Nov 2024 19:42:09 +0100 Subject: [PATCH 59/71] Use shared pointer to store server query task Signed-off-by: iTrooz --- launcher/ui/pages/instance/ServersPage.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index f00821cbd..721013635 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -455,7 +455,9 @@ class ServersModel : public QAbstractListModel { qDebug() << "Aborted previous server query task"; } - currentQueryTask = new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + currentQueryTask = ConcurrentTask::Ptr( + new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()) + ); int row = 0; for (Server &server : m_servers) { // reset current players @@ -476,15 +478,9 @@ class ServersModel : public QAbstractListModel { row++; } - // make task delete itself when done - auto *c = currentQueryTask; - connect(currentQueryTask, &ConcurrentTask::finished, [c]() { - c->deleteLater(); - }); - - // Also delete it from the model, if the model itself hasn't been deleted - connect(currentQueryTask, &ConcurrentTask::finished, this, [this, c]() { - if (c == currentQueryTask) currentQueryTask = nullptr; + // Destroy task when done + connect(currentQueryTask.get(), &ConcurrentTask::finished, this, [this]() { + currentQueryTask = nullptr; }); currentQueryTask->start(); @@ -577,7 +573,7 @@ class ServersModel : public QAbstractListModel { QList m_servers; QFileSystemWatcher* m_watcher = nullptr; QTimer m_saveTimer; - ConcurrentTask *currentQueryTask = nullptr; + ConcurrentTask::Ptr currentQueryTask = nullptr; }; ServersPage::ServersPage(InstancePtr inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::ServersPage) From 4e48015868beb256b08cc7df43221121d9ea3e1e Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 5 Dec 2024 20:40:09 +0100 Subject: [PATCH 60/71] currentQueryTask -> m_currentQueryTask Signed-off-by: iTrooz --- launcher/ui/pages/instance/ServersPage.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 721013635..e613f015f 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -450,12 +450,12 @@ class ServersModel : public QAbstractListModel { void queryServersStatus() { // Abort the currently running task if present - if (currentQueryTask != nullptr) { - currentQueryTask->abort(); + if (m_currentQueryTask != nullptr) { + m_currentQueryTask->abort(); qDebug() << "Aborted previous server query task"; } - currentQueryTask = ConcurrentTask::Ptr( + m_currentQueryTask = ConcurrentTask::Ptr( new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()) ); int row = 0; @@ -467,7 +467,7 @@ class ServersModel : public QAbstractListModel { // Start task to query server status auto [domain, port] = server.splitAddress(); auto *task = new ServerPingTask(domain, port); - currentQueryTask->addTask(Task::Ptr(task)); + m_currentQueryTask->addTask(Task::Ptr(task)); // Update the model when the task is done connect(task, &Task::finished, this, [this, task, row]() { @@ -479,11 +479,11 @@ class ServersModel : public QAbstractListModel { } // Destroy task when done - connect(currentQueryTask.get(), &ConcurrentTask::finished, this, [this]() { - currentQueryTask = nullptr; + connect(m_currentQueryTask.get(), &ConcurrentTask::finished, this, [this]() { + m_currentQueryTask = nullptr; }); - currentQueryTask->start(); + m_currentQueryTask->start(); } public slots: @@ -573,7 +573,7 @@ class ServersModel : public QAbstractListModel { QList m_servers; QFileSystemWatcher* m_watcher = nullptr; QTimer m_saveTimer; - ConcurrentTask::Ptr currentQueryTask = nullptr; + ConcurrentTask::Ptr m_currentQueryTask = nullptr; }; ServersPage::ServersPage(InstancePtr inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::ServersPage) From f9e450ace2a831daad12d3900246ddeb364e4eb8 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 5 Dec 2024 20:41:07 +0100 Subject: [PATCH 61/71] do not destroy the ping ConcurrentTask directly at the end of the task Signed-off-by: iTrooz --- launcher/ui/pages/instance/ServersPage.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index e613f015f..8583130d4 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -478,11 +478,6 @@ class ServersModel : public QAbstractListModel { row++; } - // Destroy task when done - connect(m_currentQueryTask.get(), &ConcurrentTask::finished, this, [this]() { - m_currentQueryTask = nullptr; - }); - m_currentQueryTask->start(); } From 43376b1c40dbc1963c4c95f07763e57dc052e9da Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 25 Jan 2025 19:06:52 +0100 Subject: [PATCH 62/71] remove unused defines in McClient.h Signed-off-by: iTrooz --- launcher/ui/pages/instance/McClient.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 4df879609..4e54bf27e 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -6,9 +6,6 @@ #include -#define SEGMENT_BITS 0x7F -#define CONTINUE_BIT 0x80 - // Client for the Minecraft protocol class McClient : public QObject { Q_OBJECT From fe8f755b43f9a5d8d4a8c41cbb7073bb4dbf3410 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 25 Jan 2025 20:08:44 +0100 Subject: [PATCH 63/71] remove waitForConnected() and waitForReadyRead() and use signals ineatd Signed-off-by: iTrooz --- launcher/ui/pages/instance/McClient.cpp | 85 +++++++++---------- launcher/ui/pages/instance/McClient.h | 18 +++- launcher/ui/pages/instance/ServerPingTask.cpp | 36 ++++---- 3 files changed, 71 insertions(+), 68 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 978eb9295..474490018 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -15,33 +15,21 @@ McClient::McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {} -QJsonObject McClient::getStatusDataBlocking() { +void McClient::getStatusData() { qDebug() << "Connecting to socket.."; - m_socket.connectToHost(m_ip, m_port); - if (!m_socket.waitForConnected()) { - throw Exception("Failed to connect to socket"); - } - qDebug() << "Connected to socket successfully"; - sendRequest(); + connect(&m_socket, &QTcpSocket::connected, this, [this]() { + qDebug() << "Connected to socket successfully"; + sendRequest(); - if (!m_socket.waitForReadyRead()) { - throw Exception("Socket didn't send anything to read"); - } - return readResponse(); -} - -QFuture McClient::getOnlinePlayers() { - return QtConcurrent::run([this]() { - try { - auto status = getStatusDataBlocking(); - auto players = Json::requireObject(status, "players"); - return Json::requireInteger(players, "online"); - } catch (const Exception &e) { - qDebug() << "Error: " << e.what(); - return -1; - } + connect(&m_socket, &QTcpSocket::readyRead, this, &McClient::readRawResponse); }); + + connect(&m_socket, &QTcpSocket::errorOccurred, this, [this]() { + emitFail("Socket disconnected"); + }); + + m_socket.connectToHost(m_ip, m_port); } void McClient::sendRequest() { @@ -58,32 +46,25 @@ void McClient::sendRequest() { writePacketToSocket(data); // send status packet } -void McClient::readBytesExactFromSocket(QByteArray &resp, int bytesToRead) { - while (bytesToRead > 0) { - qDebug() << bytesToRead << " bytes left to read"; - if (!m_socket.waitForReadyRead()) { - throw Exception("Read timeout or error"); +// Accumulate data until we have a full response, then call parseResponse() +void McClient::readRawResponse() { + m_resp.append(m_socket.readAll()); + if (m_wantedRespLength == 0 && m_resp.size() >= 5) { + m_wantedRespLength = readVarInt(m_resp); + } + + if (m_wantedRespLength != 0 && m_resp.size() >= m_wantedRespLength) { + if (m_resp.size() > m_wantedRespLength) { + qDebug() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " << m_resp.size() << " received)"; } - - QByteArray chunk = m_socket.read(bytesToRead); - resp.append(chunk); - bytesToRead -= chunk.size(); + parseResponse(); } } -QJsonObject McClient::readResponse() { - auto resp = m_socket.readAll(); - int length = readVarInt(resp); - - // finish ready response - readBytesExactFromSocket(resp, length-resp.size()); - - if (length != resp.size()) { - qDebug() << "Warning: Packet length doesn't match actual packet size (" << length << " expected vs " << resp.size() << " received)"; - } +void McClient::parseResponse() { qDebug() << "Received response successfully"; - int packetID = readVarInt(resp); + int packetID = readVarInt(m_resp); if (packetID != 0x00) { throw Exception( QString("Packet ID doesn't match expected value (0x00 vs 0x%1)") @@ -91,11 +72,11 @@ QJsonObject McClient::readResponse() { ); } - Q_UNUSED(readVarInt(resp)); // json length + Q_UNUSED(readVarInt(m_resp)); // json length // 'resp' should now be the JSON string - QJsonDocument doc = QJsonDocument::fromJson(resp); - return doc.object(); + QJsonDocument doc = QJsonDocument::fromJson(m_resp); + emitSucceed(doc.object()); } // From https://wiki.vg/Protocol#VarInt_and_VarLong @@ -164,3 +145,15 @@ void McClient::writePacketToSocket(QByteArray &data) { data.clear(); } + + +void McClient::emitFail(QString error) { + qDebug() << "Minecraft server ping for status error:" << error; + emit failed(); + emit finished(); +} + +void McClient::emitSucceed(QJsonObject data) { + emit succeeded(data); + emit finished(); +} diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 4e54bf27e..820ce255b 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -15,14 +15,16 @@ class McClient : public QObject { short m_port; QTcpSocket m_socket; + unsigned m_wantedRespLength = 0; + QByteArray m_resp; + public: explicit McClient(QObject *parent, QString domain, QString ip, short port); - QFuture getOnlinePlayers(); + void getStatusData(); private: - QJsonObject getStatusDataBlocking(); void sendRequest(); - QJsonObject readResponse(); - void readBytesExactFromSocket(QByteArray &resp, int bytesToRead); + void readRawResponse(); + void parseResponse(); void writeVarInt(QByteArray &data, int value); int readVarInt(QByteArray &data); @@ -32,4 +34,12 @@ private: void writeString(QByteArray &data, const std::string &value); void writePacketToSocket(QByteArray &data); + + void emitFail(QString error); + void emitSucceed(QJsonObject data); + +signals: + void succeeded(QJsonObject data); + void failed(); + void finished(); }; diff --git a/launcher/ui/pages/instance/ServerPingTask.cpp b/launcher/ui/pages/instance/ServerPingTask.cpp index 260405dc0..0574dfca1 100644 --- a/launcher/ui/pages/instance/ServerPingTask.cpp +++ b/launcher/ui/pages/instance/ServerPingTask.cpp @@ -3,6 +3,11 @@ #include "ServerPingTask.h" #include "McResolver.h" #include "McClient.h" +#include + +unsigned getOnlinePlayers(QJsonObject data) { + return Json::requireInteger(Json::requireObject(data, "players"), "online"); +} void ServerPingTask::executeTask() { qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port); @@ -14,26 +19,21 @@ void ServerPingTask::executeTask() { // Now that we have the IP and port, query the server McClient *client = new McClient(nullptr, m_domain, ip, port); - auto onlineFuture = client->getOnlinePlayers(); - // Wait for query to finish - QFutureWatcher *watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcher::finished, this, [this, client, onlineFuture, watcher]() { - client->deleteLater(); - watcher->deleteLater(); - - int online = onlineFuture.result(); - if (online == -1) { - qDebug() << "Failed to get online players"; - emitFailed(); - return; - } else { - qDebug() << "Online players: " << online; - m_outputOnlinePlayers = online; - emitSucceeded(); - } + QObject::connect(client, &McClient::succeeded, this, [this](QJsonObject data) { + m_outputOnlinePlayers = getOnlinePlayers(data); + qDebug() << "Online players: " << m_outputOnlinePlayers; + emitSucceeded(); }); - watcher->setFuture(onlineFuture); + QObject::connect(client, &McClient::failed, this, [this]() { + emitFailed(); + }); + + // Delete McClient object when done + QObject::connect(client, &McClient::finished, this, [this, client]() { + client->deleteLater(); + }); + client->getStatusData(); }); // Delete McResolver object when done From cf2b413f295dbafa970e676169a4437c95a08596 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 25 Jan 2025 20:10:26 +0100 Subject: [PATCH 64/71] forward McClient error as Task error Signed-off-by: iTrooz --- launcher/ui/pages/instance/McClient.cpp | 2 +- launcher/ui/pages/instance/McClient.h | 2 +- launcher/ui/pages/instance/ServerPingTask.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 474490018..05d4ac31a 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -149,7 +149,7 @@ void McClient::writePacketToSocket(QByteArray &data) { void McClient::emitFail(QString error) { qDebug() << "Minecraft server ping for status error:" << error; - emit failed(); + emit failed(error); emit finished(); } diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 820ce255b..55d0350d1 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -40,6 +40,6 @@ private: signals: void succeeded(QJsonObject data); - void failed(); + void failed(QString error); void finished(); }; diff --git a/launcher/ui/pages/instance/ServerPingTask.cpp b/launcher/ui/pages/instance/ServerPingTask.cpp index 0574dfca1..f0dfb8cd7 100644 --- a/launcher/ui/pages/instance/ServerPingTask.cpp +++ b/launcher/ui/pages/instance/ServerPingTask.cpp @@ -25,8 +25,8 @@ void ServerPingTask::executeTask() { qDebug() << "Online players: " << m_outputOnlinePlayers; emitSucceeded(); }); - QObject::connect(client, &McClient::failed, this, [this]() { - emitFailed(); + QObject::connect(client, &McClient::failed, this, [this](QString error) { + emitFailed(error); }); // Delete McClient object when done From 439c565961c695f09a5f0b591de06fa2be25a350 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 25 Jan 2025 20:14:15 +0100 Subject: [PATCH 65/71] forward McResolver error as Task error Signed-off-by: iTrooz --- launcher/ui/pages/instance/McResolver.cpp | 4 ++-- launcher/ui/pages/instance/McResolver.h | 2 +- launcher/ui/pages/instance/ServerPingTask.cpp | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 80f93d9f8..48c2a72fd 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -62,8 +62,8 @@ void McResolver::pingWithDomainA(QString domain, int port) { } void McResolver::emitFail(QString error) { - qDebug() << "Ping error:" << error; - emit failed(); + qDebug() << "DNS resolver error:" << error; + emit failed(error); emit finished(); } diff --git a/launcher/ui/pages/instance/McResolver.h b/launcher/ui/pages/instance/McResolver.h index e2840fd8a..06b4b7b38 100644 --- a/launcher/ui/pages/instance/McResolver.h +++ b/launcher/ui/pages/instance/McResolver.h @@ -23,6 +23,6 @@ private: signals: void succeeded(QString ip, int port); - void failed(); + void failed(QString error); void finished(); }; diff --git a/launcher/ui/pages/instance/ServerPingTask.cpp b/launcher/ui/pages/instance/ServerPingTask.cpp index f0dfb8cd7..3ec9308ca 100644 --- a/launcher/ui/pages/instance/ServerPingTask.cpp +++ b/launcher/ui/pages/instance/ServerPingTask.cpp @@ -35,6 +35,9 @@ void ServerPingTask::executeTask() { }); client->getStatusData(); }); + QObject::connect(resolver, &McResolver::failed, this, [this](QString error) { + emitFailed(error); + }); // Delete McResolver object when done QObject::connect(resolver, &McResolver::finished, [resolver]() { From 7c82cd82d7216299ca7a2497f4bc318f3532650a Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 25 Jan 2025 20:19:19 +0100 Subject: [PATCH 66/71] use m_responseReadState to avoid calling parseResponse() (as a failsafe for malicious/bad server responses) Signed-off-by: iTrooz --- launcher/ui/pages/instance/McClient.cpp | 12 +++++++++--- launcher/ui/pages/instance/McClient.h | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 05d4ac31a..3ed6a7665 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -46,18 +46,24 @@ void McClient::sendRequest() { writePacketToSocket(data); // send status packet } -// Accumulate data until we have a full response, then call parseResponse() +// Accumulate data until we have a full response, then call parseResponse() once void McClient::readRawResponse() { + if (m_responseReadState == 2) { + return; + } + m_resp.append(m_socket.readAll()); - if (m_wantedRespLength == 0 && m_resp.size() >= 5) { + if (m_responseReadState == 0 && m_resp.size() >= 5) { m_wantedRespLength = readVarInt(m_resp); + m_responseReadState = 1; } - if (m_wantedRespLength != 0 && m_resp.size() >= m_wantedRespLength) { + if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) { if (m_resp.size() > m_wantedRespLength) { qDebug() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " << m_resp.size() << " received)"; } parseResponse(); + m_responseReadState = 2; } } diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 55d0350d1..11983eaa8 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -15,6 +15,10 @@ class McClient : public QObject { short m_port; QTcpSocket m_socket; + // 0: did not start reading the response yet + // 1: read the response length, still reading the response + // 2: finished reading the response + unsigned m_responseReadState = 0; unsigned m_wantedRespLength = 0; QByteArray m_resp; From b06c4341d822324835c96ad927c06056ee3f0936 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 25 Jan 2025 20:22:09 +0100 Subject: [PATCH 67/71] update documentation Signed-off-by: iTrooz --- launcher/ui/pages/instance/McClient.cpp | 1 - launcher/ui/pages/instance/McClient.h | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 3ed6a7665..cae345d4c 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -46,7 +46,6 @@ void McClient::sendRequest() { writePacketToSocket(data); // send status packet } -// Accumulate data until we have a full response, then call parseResponse() once void McClient::readRawResponse() { if (m_responseReadState == 2) { return; diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 11983eaa8..59834dfb7 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -24,9 +24,11 @@ class McClient : public QObject { public: explicit McClient(QObject *parent, QString domain, QString ip, short port); + //! Read status data of the server, and calls the succeeded() signal with the parsed JSON data void getStatusData(); private: void sendRequest(); + //! Accumulate data until we have a full response, then call parseResponse() once void readRawResponse(); void parseResponse(); From 0b9d4784d8e002da5ff4d662c620d256d5eeab74 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 25 Jan 2025 20:26:52 +0100 Subject: [PATCH 68/71] Show socket error in McClient Signed-off-by: iTrooz --- launcher/ui/pages/instance/McClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index cae345d4c..a06c67ad8 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -26,7 +26,7 @@ void McClient::getStatusData() { }); connect(&m_socket, &QTcpSocket::errorOccurred, this, [this]() { - emitFail("Socket disconnected"); + emitFail("Socket disconnected: " + m_socket.errorString()); }); m_socket.connectToHost(m_ip, m_port); From bb20848449db46602aecaf3c42b2578257e105e2 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 25 Jan 2025 21:01:21 +0100 Subject: [PATCH 69/71] use more sensible protocol version Signed-off-by: iTrooz --- launcher/ui/pages/instance/McClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index a06c67ad8..90813ac18 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -35,7 +35,7 @@ void McClient::getStatusData() { void McClient::sendRequest() { QByteArray data; writeVarInt(data, 0x00); // packet ID - writeVarInt(data, 0x760); // protocol version + writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1) writeVarInt(data, m_domain.size()); // server address length writeString(data, m_domain.toStdString()); // server address writeFixedInt(data, m_port, 2); // server port From 34233a93a2b1d93d24383ecbba92cc03031791fa Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 13 Feb 2025 00:55:35 +0100 Subject: [PATCH 70/71] MinecraftTarget::parse() to parse IP Signed-off-by: iTrooz --- launcher/ui/pages/instance/ServersPage.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 8583130d4..4bc2e6998 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -90,15 +90,6 @@ struct Server { } } - std::tuple splitAddress() const { - auto parts = m_address.split(":"); - if (parts.size() == 1) { - return std::make_tuple(parts[0], 25565); - } else { - return std::make_tuple(parts[0], parts[1].toInt()); - } - } - void serialize(nbt::tag_compound& server) { server.insert("name", m_name.trimmed().toUtf8().toStdString()); @@ -465,8 +456,8 @@ class ServersModel : public QAbstractListModel { emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); // Start task to query server status - auto [domain, port] = server.splitAddress(); - auto *task = new ServerPingTask(domain, port); + auto target = MinecraftTarget::parse(server.m_address, false); + auto *task = new ServerPingTask(target.address, target.port); m_currentQueryTask->addTask(Task::Ptr(task)); // Update the model when the task is done From 93286789c5679fd6cbf14bf9ca39e1cce7036e9d Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sat, 1 Mar 2025 20:12:31 +0100 Subject: [PATCH 71/71] fix typo Signed-off-by: iTrooz --- launcher/tasks/Task.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index b5d9bec65..503d6a6b6 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -137,7 +137,7 @@ class Task : public QObject, public QRunnable { signals: void started(); void progress(qint64 current, qint64 total); - //! called when a task has eother succeeded, aborted or failed. + //! called when a task has either succeeded, aborted or failed. void finished(); //! called when a task has succeeded void succeeded();