mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-05-04 16:44:35 +02:00
refactor header and code into 2 files
This commit is contained in:
parent
8b7040d416
commit
b8035ca078
@ -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
|
||||||
|
156
launcher/ui/pages/instance/McClient.cpp
Normal file
156
launcher/ui/pages/instance/McClient.cpp
Normal 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();
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
72
launcher/ui/pages/instance/McResolver.cpp
Normal file
72
launcher/ui/pages/instance/McResolver.cpp
Normal 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);
|
||||||
|
}
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user