mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-05-08 10:34:39 +02:00
add my classes
This commit is contained in:
parent
fac521a312
commit
43a54cafef
169
launcher/ui/pages/instance/McClient.hpp
Normal file
169
launcher/ui/pages/instance/McClient.hpp
Normal file
@ -0,0 +1,169 @@
|
||||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#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();
|
||||
}
|
||||
};
|
85
launcher/ui/pages/instance/McResolver.hpp
Normal file
85
launcher/ui/pages/instance/McResolver.hpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include <QObject>
|
||||
#include <QDnsLookup>
|
||||
#include <QtNetwork/qtcpsocket.h>
|
||||
#include <QHostInfo>
|
||||
|
||||
// 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<QDnsLookup *>(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();
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user