diff --git a/DbMinecraft.pro b/DbMinecraft.pro new file mode 100644 index 0000000..afc8a0d --- /dev/null +++ b/DbMinecraft.pro @@ -0,0 +1,15 @@ +QT = core network + +CONFIG += c++1z + +DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000 + +SOURCES += main.cpp \ + client.cpp \ + mcdatastream.cpp \ + packets.cpp + +HEADERS += \ + client.h \ + mcdatastream.h \ + packets.h diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000..5bf2e7a --- /dev/null +++ b/client.cpp @@ -0,0 +1,257 @@ +#include "client.h" + +#include +#include +#include +#include + +Client::Client(QTcpSocket *socket, QObject *parent) : + QObject(parent), m_socket(socket), m_dataStream(m_socket), + m_packetSize(0), m_state(HandshakingState) +{ + m_socket->setParent(this); + + connect(m_socket, &QIODevice::readyRead, this, &Client::readyRead); + connect(m_socket, &QAbstractSocket::disconnected, this, &Client::disconnected); + + qDebug() << m_socket->peerPort(); +} + +void Client::readyRead() +{ + while(m_socket->bytesAvailable()) + { + if(!m_packetSize) + { + m_packetSize = m_dataStream.readVar(); + qDebug() << "packet size" << m_packetSize; + } + + if(m_socket->bytesAvailable() < m_packetSize) + { + qWarning() << "packet not fully available" << m_socket->bytesAvailable(); + return; + } + + qint32 bytesRead; + const auto type = m_dataStream.readVar(bytesRead); + m_packetSize -= bytesRead; + const auto buffer = m_socket->read(m_packetSize); + Q_ASSERT(buffer.size() == m_packetSize); + m_packetSize = 0; + + switch(m_state) + { + case HandshakingState: + readPacketHandshaking(packets::handshaking::serverbound::PacketType(type), buffer); + break; + case StatusState: + readPacketStatus(packets::status::serverbound::PacketType(type), buffer); + break; + case LoginState: + readPacketLogin(packets::login::serverbound::PacketType(type), buffer); + break; + case PlayState: + readPacketPlay(packets::play::serverbound::PacketType(type), buffer); + break; + default: + qWarning() << "unhandled state" << m_state << type << buffer; + } + } +} + +void Client::disconnected() +{ + qDebug() << m_socket->peerPort(); + deleteLater(); +} + +void Client::readPacketHandshaking(packets::handshaking::serverbound::PacketType type, const QByteArray &buffer) +{ + qDebug() << type; + + McDataStream dataStream(const_cast(&buffer), QIODevice::ReadOnly); + + switch(type) + { + using namespace packets::handshaking; + case serverbound::PacketHandshake: + { + serverbound::Handshake packet(dataStream); + m_state = packet.nextState; + break; + } + default: + qWarning() << "unknown type!"; + } +} + +void Client::readPacketStatus(const packets::status::serverbound::PacketType type, const QByteArray &buffer) +{ + qDebug() << type; + + McDataStream dataStream(const_cast(&buffer), QIODevice::ReadOnly); + + switch(type) + { + using namespace packets::status; + case serverbound::PacketRequest: + { + { + serverbound::Request packet(dataStream); + } + { + clientbound::Response packet; + packet.jsonResponse = + "{" + " \"version\": {" + " \"name\": \"1.13.1\"," + " \"protocol\": 401" + " }," + " \"players\": {" + " \"max\": 1000," + " \"online\": 2000," + " \"sample\": [" + " {" + " \"name\": \"0xFEEDC0DE64\"," + " \"id\": \"6ebf7396-b6da-40b1-b7d9-9b7961450d5a\"" + " }" + " ]" + " }, " + " \"description\": {" + " \"text\": \"Mein monster server in C++\"" + " }," + " \"favicon\": \"\"" + "}"; + packet.serialize(m_dataStream); + } + break; + } + case serverbound::PacketPing: + { + qint64 payload; + { + serverbound::Ping packet(dataStream); + payload = packet.payload; + } + { + clientbound::Pong packet; + packet.payload = payload; + packet.serialize(m_dataStream); + } + break; + } + default: + qWarning() << "unknown type!"; + } +} + +void Client::readPacketLogin(const packets::login::serverbound::PacketType type, const QByteArray &buffer) +{ + qDebug() << type; + + McDataStream dataStream(const_cast(&buffer), QIODevice::ReadOnly); + + switch(type) + { + using namespace packets::login; + case serverbound::PacketLogin: + { + QString name; + { + serverbound::Login packet(dataStream); + name = packet.name; + } + qDebug() << "Name" << name; + { + clientbound::LoginSuccess packet; + const auto uuid = QUuid::createUuid().toString(); + packet.uuid = uuid.mid(1, uuid.length() - 2); + packet.username = name; + packet.serialize(m_dataStream); + } + m_state = PlayState; + { + packets::play::clientbound::JoinGame packet; + packet.entityid = 1; + packet.gamemode = 0; + packet.dimension = 0; + packet.difficulty = 2; + packet.maxPlayers = 255; + packet.levelType = QStringLiteral("default"); + packet.reducedDebugInfo = false; + packet.serialize(m_dataStream); + } + { + packets::play::clientbound::PluginMessage packet; + packet.channel = QStringLiteral("minecraft:brand"); + packet.data = QByteArrayLiteral("bullshit"); + packet.serialize(m_dataStream); + } + { + packets::play::clientbound::ServerDifficulty packet; + packet.difficulty = 2; + packet.serialize(m_dataStream); + } + { + packets::play::clientbound::SpawnPosition packet; + packet.location = std::make_tuple(100, 64, 100); + packet.serialize(m_dataStream); + } + { + packets::play::clientbound::PlayerAbilities packet; + packet.flags = 0x0F; + packet.flyingSpeed = 1.; + packet.fieldOfViewModifier = 60.; + packet.serialize(m_dataStream); + } + break; + } + default: + qWarning() << "unknown type!"; + } +} + +void Client::readPacketPlay(const packets::play::serverbound::PacketType type, const QByteArray &buffer) +{ + qDebug() << type; + + McDataStream dataStream(const_cast(&buffer), QIODevice::ReadOnly); + + switch(type) + { + using namespace packets::play; + case serverbound::PacketClientSettings: + { + { + serverbound::ClientSettings packet(dataStream); + qDebug() << "locale" << packet.locale; + qDebug() << "viewDistance" << packet.viewDistance; + qDebug() << "chatMode" << packet.chatMode; + qDebug() << "chatColors" << packet.chatColors; + qDebug() << "displayedSkinParts" << packet.displayedSkinParts; + qDebug() << "mainHand" << packet.mainHand; + } + { + clientbound::PlayerPositionAndLook packet; + packet.x = 50.; + packet.y = 64.; + packet.z = 50.; + packet.yaw = 0.; + packet.pitch = 0.; + packet.flags = 0; + packet.teleportId = 0; + packet.serialize(m_dataStream); + } + break; + } + case serverbound::PacketPluginMessage: + { + serverbound::PluginMessage packet(dataStream); + qDebug() << "channel" << packet.channel; + break; + } + default: + qWarning() << "unknown type!"; + } +} diff --git a/client.h b/client.h new file mode 100644 index 0000000..8973654 --- /dev/null +++ b/client.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "mcdatastream.h" +#include "packets.h" + +class QTcpSocket; + +class Client : public QObject +{ + Q_OBJECT + +public: + explicit Client(QTcpSocket *socket, QObject *parent = nullptr); + +private Q_SLOTS: + void readyRead(); + void disconnected(); + +private: + void readPacketHandshaking(const packets::handshaking::serverbound::PacketType type, const QByteArray &buffer); + void readPacketStatus(const packets::status::serverbound::PacketType type, const QByteArray &buffer); + void readPacketLogin(const packets::login::serverbound::PacketType type, const QByteArray &buffer); + void readPacketPlay(const packets::play::serverbound::PacketType type, const QByteArray &buffer); + + QTcpSocket *m_socket; + McDataStream m_dataStream; + + qint32 m_packetSize; + + SocketState m_state; +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e1c3f29 --- /dev/null +++ b/main.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +#include "client.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} " + "[" + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + "] " + "%{function}(): " + "%{message}")); + + QTcpServer server; + + QObject::connect(&server, &QTcpServer::newConnection, [&server](){ + new Client(server.nextPendingConnection()); + }); + + if(!server.listen(QHostAddress::Any, 25565)) + qFatal("could not start listening %s", server.errorString().toUtf8().constData()); + + qDebug() << "started listening"; + + return a.exec(); +} diff --git a/mcdatastream.cpp b/mcdatastream.cpp new file mode 100644 index 0000000..cd5da16 --- /dev/null +++ b/mcdatastream.cpp @@ -0,0 +1,173 @@ +#include "mcdatastream.h" + +#include + +#include + +McDataStream::McDataStream() : + QDataStream() +{ + setByteOrder(QDataStream::BigEndian); +} + +McDataStream::McDataStream(QIODevice *device) : + QDataStream(device) +{ + setByteOrder(QDataStream::BigEndian); +} + +McDataStream::McDataStream(QByteArray *byteArray, QIODevice::OpenMode flags) : + QDataStream(byteArray, flags) +{ + setByteOrder(QDataStream::BigEndian); +} + +McDataStream::McDataStream(const QByteArray &byteArray) : + QDataStream(byteArray) +{ + setByteOrder(QDataStream::BigEndian); +} + +McDataStream::Position McDataStream::readPosition() +{ + quint64 val; + *this >> val; + const qint32 x = val >> 38; + const qint16 y = (val >> 26) & 0xFFF; + const qint32 z = val << 38 >> 38; + return std::make_tuple(x, y, z); +} + +void McDataStream::writePosition(const Position &position) +{ + const quint64 val = ((quint64(std::get<0>(position)) & 0x3FFFFFF) << 38) | + ((quint64(std::get<1>(position)) & 0xFFF) << 26) | + (quint64(std::get<2>(position)) & 0x3FFFFFF); + *this << val; +} + +float McDataStream::readFloat() +{ + const auto precision = floatingPointPrecision(); + setFloatingPointPrecision(SinglePrecision); + float value; + *this >> value; + setFloatingPointPrecision(precision); + return value; +} + +void McDataStream::writeFloat(float value) +{ + const auto precision = floatingPointPrecision(); + setFloatingPointPrecision(SinglePrecision); + *this << value; + setFloatingPointPrecision(precision); +} + +float McDataStream::readDouble() +{ + const auto precision = floatingPointPrecision(); + setFloatingPointPrecision(DoublePrecision); + double value; + *this >> value; + setFloatingPointPrecision(precision); + return value; +} + +void McDataStream::writeDouble(double value) +{ + const auto precision = floatingPointPrecision(); + setFloatingPointPrecision(DoublePrecision); + *this << value; + setFloatingPointPrecision(precision); +} + +template<> +qint32 McDataStream::readVar(qint32 &bytesRead) +{ + bytesRead = 0; + qint32 result = 0; + qint8 read; + do { + *this >> read; + qint32 value = read & 0b01111111; + result |= value << (7 * bytesRead); + + bytesRead++; + if (bytesRead > 5) { + qFatal("VarInt is too big"); + } + } while ((read & 0b10000000) != 0); + + return result; +} + +template<> +qint64 McDataStream::readVar(qint32 &bytesRead) +{ + bytesRead = 0; + qint64 result = 0; + qint8 read; + do { + *this >> read; + qint32 value = read & 0b01111111; + result |= value << (7 * bytesRead); + + bytesRead++; + if (bytesRead > 10) { + qFatal("VarInt is too big"); + } + } while ((read & 0b10000000) != 0); + + return result; +} + +template<> +QString McDataStream::readVar(qint32 &bytesRead) +{ + const auto length = readVar(bytesRead); + + auto data = std::unique_ptr(new char[length]); + + { + const auto rawLength = readRawData(data.get(), length); + bytesRead += rawLength; + Q_ASSERT(length == rawLength); + } + + return QString::fromUtf8(data.get(), length); +} + +template<> +void McDataStream::writeVar(qint32 value) +{ + do { + qint8 temp = value & 0b01111111; + value >>= 7; + if (value != 0) { + temp |= 0b10000000; + } + *this << temp; + } while (value != 0); +} + +template<> +void McDataStream::writeVar(qint64 value) +{ + do { + qint8 temp = value & 0b01111111; + value >>= 7; + if (value != 0) { + temp |= 0b10000000; + } + *this << temp; + } while (value != 0); +} + +template<> +void McDataStream::writeVar(QString value) +{ + const auto bytes = value.toUtf8(); + writeVar(bytes.length()); + writeRawData(bytes.constData(), bytes.length()); +} diff --git a/mcdatastream.h b/mcdatastream.h new file mode 100644 index 0000000..ee83ba6 --- /dev/null +++ b/mcdatastream.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include + +class McDataStream : public QDataStream +{ +public: + McDataStream(); + explicit McDataStream(QIODevice *device); + McDataStream(QByteArray *byteArray, QIODevice::OpenMode flags); + McDataStream(const QByteArray &byteArray); + + typedef std::tuple Position; + Position readPosition(); + void writePosition(const Position &position); + + float readFloat(); + void writeFloat(float value); + + float readDouble(); + void writeDouble(double value); + + template + T readVar(); + + template + T readVar(qint32 &bytesRead); + + template + void writeVar(T value); +}; + +template +T McDataStream::readVar() +{ + qint32 bytesRead; + return readVar(bytesRead); +} + +template<> +qint32 McDataStream::readVar(qint32 &bytesRead); + +template<> +qint64 McDataStream::readVar(qint32 &bytesRead); + +template<> +QString McDataStream::readVar(qint32 &bytesRead); + +template<> +void McDataStream::writeVar(qint32 value); + +template<> +void McDataStream::writeVar(qint64 value); + +template<> +void McDataStream::writeVar(QString value); diff --git a/packets.cpp b/packets.cpp new file mode 100644 index 0000000..9e92565 --- /dev/null +++ b/packets.cpp @@ -0,0 +1,150 @@ +#include "packets.h" + +#include "mcdatastream.h" + +packets::handshaking::serverbound::Handshake::Handshake(McDataStream &stream) +{ + protocolVersion = stream.readVar(); + serverAddress = stream.readVar(); + stream >> serverPort; + const auto socketState = stream.readVar(); + Q_ASSERT(socketState == 1 || socketState == 2); + nextState = SocketState(socketState); +} + +packets::status::serverbound::Request::Request(McDataStream &stream) +{ + Q_UNUSED(stream); +} + +packets::status::serverbound::Ping::Ping(McDataStream &stream) +{ + stream >> payload; +} + +void packets::status::clientbound::Response::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketResponse); + tempStream.writeVar(jsonResponse); + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} + +void packets::status::clientbound::Pong::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketPong); + tempStream << payload; + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} + +packets::login::serverbound::Login::Login(McDataStream &stream) +{ + name = stream.readVar(); +} + +void packets::login::clientbound::LoginSuccess::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketLoginSuccess); + tempStream.writeVar(uuid); + tempStream.writeVar(username); + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} + +packets::play::serverbound::ClientSettings::ClientSettings(McDataStream &stream) +{ + locale = stream.readVar(); + stream >> viewDistance; + chatMode = stream.readVar(); + stream >> chatColors + >> displayedSkinParts; + mainHand = stream.readVar(); +} + +packets::play::serverbound::PluginMessage::PluginMessage(McDataStream &stream) +{ + channel = stream.readVar(); + //TODO read to end of buffer +} + +void packets::play::clientbound::ServerDifficulty::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketServerDifficulty); + tempStream << difficulty; + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} + +void packets::play::clientbound::PluginMessage::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketPluginMessage); + tempStream.writeVar(channel); + buffer.append(data); + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} + +void packets::play::clientbound::JoinGame::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketJoinGame); + tempStream << entityid + << gamemode + << dimension + << difficulty + << maxPlayers; + tempStream.writeVar(levelType); + tempStream << reducedDebugInfo; + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} + +void packets::play::clientbound::PlayerAbilities::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketPlayerAbilities); + tempStream << flags; + tempStream.writeFloat(flyingSpeed); + tempStream.writeFloat(fieldOfViewModifier); + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} + +void packets::play::clientbound::PlayerPositionAndLook::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketPlayerPositionAndLook); + tempStream.writeDouble(x); + tempStream.writeDouble(y); + tempStream.writeDouble(z); + tempStream.writeFloat(yaw); + tempStream.writeFloat(pitch); + tempStream << flags; + tempStream.writeVar(teleportId); + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} + +void packets::play::clientbound::SpawnPosition::serialize(McDataStream &stream) +{ + QByteArray buffer; + McDataStream tempStream(&buffer, QIODevice::WriteOnly); + tempStream.writeVar(PacketSpawnPosition); + tempStream.writePosition(location); + stream.writeVar(buffer.length()); + stream.writeRawData(buffer.constData(), buffer.length()); +} diff --git a/packets.h b/packets.h new file mode 100644 index 0000000..ccaa68c --- /dev/null +++ b/packets.h @@ -0,0 +1,165 @@ +#pragma once + +#include +#include + +#include + +class McDataStream; + +enum SocketState { HandshakingState, StatusState, LoginState, PlayState, ClosedState }; + +namespace packets { + namespace handshaking { + namespace serverbound { + enum PacketType { PacketHandshake }; + + struct Handshake { + Handshake(McDataStream &stream); + + qint32 protocolVersion; + QString serverAddress; + quint16 serverPort; + SocketState nextState; + }; + } + + namespace clientbound { + } + } + + namespace status { + namespace serverbound { + enum PacketType { PacketRequest, PacketPing }; + + struct Request { + Request(McDataStream &stream); + }; + + struct Ping { + Ping(McDataStream &stream); + + qint64 payload; + }; + } + + namespace clientbound { + enum PacketType { PacketResponse, PacketPong }; + + struct Response { + QString jsonResponse; + + void serialize(McDataStream &stream); + }; + + struct Pong { + qint64 payload; + + void serialize(McDataStream &stream); + }; + } + } + + namespace login { + namespace serverbound { + enum PacketType { PacketLogin }; + + struct Login { + Login(McDataStream &stream); + + QString name; + }; + } + + namespace clientbound { + enum PacketType { PacketLoginSuccess = 0x02 }; + + struct LoginSuccess { + QString uuid; + QString username; + + void serialize(McDataStream &stream); + }; + } + } + + namespace play { + namespace serverbound { + enum PacketType { PacketClientSettings = 0x04, PacketPluginMessage = 0x0A }; + + struct ClientSettings { + ClientSettings(McDataStream &stream); + + QString locale; + qint8 viewDistance; + qint32 chatMode; + bool chatColors; + quint8 displayedSkinParts; + qint32 mainHand; + }; + + struct PluginMessage { + PluginMessage(McDataStream &stream); + + QString channel; + QByteArray data; + }; + } + + namespace clientbound { + enum PacketType { PacketServerDifficulty = 0x0D, PacketPluginMessage = 0x19, PacketJoinGame = 0x25, PacketPlayerAbilities = 0x2E, PacketPlayerPositionAndLook = 0x32, + PacketSpawnPosition = 0x49 }; + + struct ServerDifficulty { + quint8 difficulty; + + void serialize(McDataStream &stream); + }; + + struct PluginMessage { + QString channel; + QByteArray data; + + void serialize(McDataStream &stream); + }; + + struct JoinGame { + qint32 entityid; + quint8 gamemode; + qint32 dimension; + quint8 difficulty; + quint8 maxPlayers; + QString levelType; + bool reducedDebugInfo; + + void serialize(McDataStream &stream); + }; + + struct PlayerAbilities { + qint8 flags; + float flyingSpeed; + float fieldOfViewModifier; + + void serialize(McDataStream &stream); + }; + + struct PlayerPositionAndLook { + double x; + double y; + double z; + float yaw; + float pitch; + qint8 flags; + qint32 teleportId; + + void serialize(McDataStream &stream); + }; + + struct SpawnPosition { + std::tuple location; + + void serialize(McDataStream &stream); + }; + } + } +}