refactor header and code into 2 files

This commit is contained in:
iTrooz 2024-11-17 19:50:52 +01:00
parent 8b7040d416
commit b8035ca078
No known key found for this signature in database
GPG Key ID: 8B83F77667B1BC6A
5 changed files with 249 additions and 208 deletions

View File

@ -918,7 +918,9 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/ServersPage.h ui/pages/instance/ServersPage.h
ui/pages/instance/WorldListPage.cpp ui/pages/instance/WorldListPage.cpp
ui/pages/instance/WorldListPage.h ui/pages/instance/WorldListPage.h
ui/pages/instance/McClient.cpp
ui/pages/instance/McClient.hpp ui/pages/instance/McClient.hpp
ui/pages/instance/McResolver.cpp
ui/pages/instance/McResolver.hpp ui/pages/instance/McResolver.hpp
# GUI - global settings pages # GUI - global settings pages

View File

@ -0,0 +1,156 @@
#include <QObject>
#include <QTcpSocket>
#include <QJsonDocument>
#include <QJsonObject>
#include <Exception.h>
#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();
}

View File

@ -18,150 +18,19 @@ class McClient : public QObject {
QTcpSocket socket; QTcpSocket socket;
public: public:
explicit McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), domain(domain), ip(ip), port(port) {} explicit McClient(QObject *parent, QString domain, QString ip, short port);
QJsonObject getStatusData();
QJsonObject getStatusData() { int getOnlinePlayers();
qDebug() << "Connecting to socket.."; void sendRequest();
socket.connectToHost(ip, port); void readBytesExactFromSocket(QByteArray &resp, int bytesToRead);
QJsonObject readResponse();
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();
}
private: private:
// From https://wiki.vg/Protocol#VarInt_and_VarLong void writeVarInt(QByteArray &data, int value);
void writeVarInt(QByteArray &data, int value) { int readVarInt(QByteArray &data);
while (true) { char readByte(QByteArray &data);
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;
}
// 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 writeFixedInt(QByteArray &data, int value, int size);
for (int i = size - 1; i >= 0; i--) { void writeString(QByteArray &data, const std::string &value);
data.append((value >> (i * 8)) & 0xFF);
}
}
void writeString(QByteArray &data, const std::string &value) { void writePacketToSocket(QByteArray &data);
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();
}
}; };

View File

@ -0,0 +1,72 @@
#include <QObject>
#include <QDnsLookup>
#include <QtNetwork/qtcpsocket.h>
#include <QHostInfo>
#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<QDnsLookup *>(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);
}

View File

@ -1,4 +1,5 @@
#include <QObject> #include <QObject>
#include <QString>
#include <QDnsLookup> #include <QDnsLookup>
#include <QtNetwork/qtcpsocket.h> #include <QtNetwork/qtcpsocket.h>
#include <QHostInfo> #include <QHostInfo>
@ -11,73 +12,14 @@ class MCResolver : public QObject {
int constrPort; int constrPort;
public: public:
explicit MCResolver(QObject *parent, QString domain, int port): QObject(parent), constrDomain(domain), constrPort(port) {} explicit MCResolver(QObject *parent, QString domain, int port);
void ping();
void ping() {
pingWithDomainSRV(constrDomain, constrPort);
}
private: private:
void pingWithDomainSRV(QString domain, int port);
void pingWithDomainSRV(QString domain, int port) { void pingWithDomainA(QString domain, int port);
QDnsLookup *lookup = new QDnsLookup(this); void emitFail(std::string error);
lookup->setName(QString("_minecraft._tcp.%1").arg(domain)); void emitSucceed(QString ip, int port);
lookup->setType(QDnsLookup::SRV);
connect(lookup, &QDnsLookup::finished, this, [&, domain, port]() {
QDnsLookup *lookup = qobject_cast<QDnsLookup *>(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);
}
signals: signals:
void succeed(QString ip, int port); void succeed(QString ip, int port);