Splitted different client states into classes
This commit is contained in:
@@ -5,11 +5,21 @@ CONFIG += c++1z
|
||||
DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
||||
|
||||
SOURCES += main.cpp \
|
||||
client.cpp \
|
||||
closedclient.cpp \
|
||||
handshakingclient.cpp \
|
||||
loginclient.cpp \
|
||||
mcdatastream.cpp \
|
||||
packets.cpp
|
||||
packets.cpp \
|
||||
playclient.cpp \
|
||||
server.cpp \
|
||||
statusclient.cpp
|
||||
|
||||
HEADERS += \
|
||||
client.h \
|
||||
closedclient.h \
|
||||
handshakingclient.h \
|
||||
loginclient.h \
|
||||
mcdatastream.h \
|
||||
packets.h
|
||||
packets.h \
|
||||
playclient.h \
|
||||
server.h \
|
||||
statusclient.h
|
||||
|
45
client.h
45
client.h
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "mcdatastream.h"
|
||||
#include "packets.h"
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class Client : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Client(QTcpSocket *socket, QObject *parent = nullptr);
|
||||
|
||||
SocketState state() const { return m_state; }
|
||||
|
||||
void keepAlive();
|
||||
void sendChatMessage();
|
||||
void trialDisconnect();
|
||||
|
||||
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;
|
||||
|
||||
const QDateTime m_connectedSince;
|
||||
QDateTime m_lastKeepAliveSent;
|
||||
QDateTime m_lastKeepAliveReceived;
|
||||
QDateTime m_lastChatMessage;
|
||||
};
|
49
closedclient.cpp
Normal file
49
closedclient.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "closedclient.h"
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
|
||||
#include "server.h"
|
||||
|
||||
ClosedClient::ClosedClient(QTcpSocket &socket, Server &server) :
|
||||
QObject{&server}, m_socket{socket}, m_server{server}, m_dataStream{&m_socket}
|
||||
{
|
||||
m_socket.setParent(this);
|
||||
|
||||
connect(&m_socket, &QIODevice::readyRead, this, &ClosedClient::readyRead);
|
||||
connect(&m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater);
|
||||
|
||||
QTimer::singleShot(1000, this, &QObject::deleteLater);
|
||||
}
|
||||
|
||||
void ClosedClient::readyRead()
|
||||
{
|
||||
while(m_socket.bytesAvailable())
|
||||
{
|
||||
if(!m_packetSize)
|
||||
{
|
||||
m_packetSize = m_dataStream.readVar<qint32>();
|
||||
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<qint32>(bytesRead);
|
||||
m_packetSize -= bytesRead;
|
||||
const auto buffer = m_socket.read(m_packetSize);
|
||||
Q_ASSERT(buffer.size() == m_packetSize);
|
||||
m_packetSize = 0;
|
||||
|
||||
readPacket(packets::closed::serverbound::PacketType(type), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void ClosedClient::readPacket(packets::closed::serverbound::PacketType type, const QByteArray &buffer)
|
||||
{
|
||||
qWarning() << "does not support receiving any packets" << type << buffer;
|
||||
}
|
32
closedclient.h
Normal file
32
closedclient.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "mcdatastream.h"
|
||||
#include "packets.h"
|
||||
|
||||
class QTcpSocket;
|
||||
class QByteArray;
|
||||
|
||||
class Server;
|
||||
|
||||
class ClosedClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ClosedClient(QTcpSocket &socket, Server &server);
|
||||
|
||||
private slots:
|
||||
void readyRead();
|
||||
|
||||
private:
|
||||
void readPacket(packets::closed::serverbound::PacketType type, const QByteArray &buffer);
|
||||
|
||||
QTcpSocket &m_socket;
|
||||
Server &m_server;
|
||||
|
||||
McDataStream m_dataStream;
|
||||
|
||||
qint32 m_packetSize{};
|
||||
};
|
76
handshakingclient.cpp
Normal file
76
handshakingclient.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "handshakingclient.h"
|
||||
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "server.h"
|
||||
#include "statusclient.h"
|
||||
#include "loginclient.h"
|
||||
|
||||
HandshakingClient::HandshakingClient(QTcpSocket &socket, Server &server) :
|
||||
QObject{&server}, m_socket{socket}, m_server{server}, m_dataStream{&m_socket}
|
||||
{
|
||||
m_socket.setParent(this);
|
||||
|
||||
connect(&m_socket, &QIODevice::readyRead, this, &HandshakingClient::readyRead);
|
||||
connect(&m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater);
|
||||
}
|
||||
|
||||
void HandshakingClient::readyRead()
|
||||
{
|
||||
while(m_socket.bytesAvailable())
|
||||
{
|
||||
if(!m_packetSize)
|
||||
{
|
||||
m_packetSize = m_dataStream.readVar<qint32>();
|
||||
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<qint32>(bytesRead);
|
||||
m_packetSize -= bytesRead;
|
||||
const auto buffer = m_socket.read(m_packetSize);
|
||||
Q_ASSERT(buffer.size() == m_packetSize);
|
||||
m_packetSize = 0;
|
||||
|
||||
readPacket(packets::handshaking::serverbound::PacketType(type), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void HandshakingClient::readPacket(packets::handshaking::serverbound::PacketType type, const QByteArray &buffer)
|
||||
{
|
||||
McDataStream dataStream{const_cast<QByteArray *>(&buffer), QIODevice::ReadOnly};
|
||||
|
||||
switch(type)
|
||||
{
|
||||
using namespace packets::handshaking;
|
||||
case serverbound::PacketType::Handshake:
|
||||
{
|
||||
qDebug() << type;
|
||||
serverbound::Handshake packet{dataStream};
|
||||
switch (packet.nextState)
|
||||
{
|
||||
using namespace serverbound;
|
||||
case Handshake::SocketState::StatusState:
|
||||
new StatusClient{m_socket, m_server};
|
||||
break;
|
||||
case Handshake::SocketState::LoginState:
|
||||
new LoginClient{m_socket, m_server};
|
||||
break;
|
||||
default:
|
||||
qCritical() << "client requested new state" << packet.nextState << "which is not allowed/invalid";
|
||||
}
|
||||
|
||||
deleteLater();
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning() << "unknown packet type" << type;
|
||||
}
|
||||
}
|
32
handshakingclient.h
Normal file
32
handshakingclient.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "mcdatastream.h"
|
||||
#include "packets.h"
|
||||
|
||||
class QTcpSocket;
|
||||
class QByteArray;
|
||||
|
||||
class Server;
|
||||
|
||||
class HandshakingClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HandshakingClient(QTcpSocket &socket, Server &parent);
|
||||
|
||||
private slots:
|
||||
void readyRead();
|
||||
|
||||
private:
|
||||
void readPacket(packets::handshaking::serverbound::PacketType type, const QByteArray &buffer);
|
||||
|
||||
QTcpSocket &m_socket;
|
||||
Server &m_server;
|
||||
|
||||
McDataStream m_dataStream;
|
||||
|
||||
qint32 m_packetSize{};
|
||||
};
|
74
loginclient.cpp
Normal file
74
loginclient.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "loginclient.h"
|
||||
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "server.h"
|
||||
#include "playclient.h"
|
||||
|
||||
LoginClient::LoginClient(QTcpSocket &socket, Server &server) :
|
||||
QObject{&server}, m_socket{socket}, m_server{server}, m_dataStream{&m_socket}
|
||||
{
|
||||
m_socket.setParent(this);
|
||||
|
||||
connect(&m_socket, &QIODevice::readyRead, this, &LoginClient::readyRead);
|
||||
connect(&m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater);
|
||||
}
|
||||
|
||||
void LoginClient::readyRead()
|
||||
{
|
||||
while(m_socket.bytesAvailable())
|
||||
{
|
||||
if(!m_packetSize)
|
||||
{
|
||||
m_packetSize = m_dataStream.readVar<qint32>();
|
||||
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<qint32>(bytesRead);
|
||||
m_packetSize -= bytesRead;
|
||||
const auto buffer = m_socket.read(m_packetSize);
|
||||
Q_ASSERT(buffer.size() == m_packetSize);
|
||||
m_packetSize = 0;
|
||||
|
||||
readPacket(packets::login::serverbound::PacketType(type), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void LoginClient::readPacket(packets::login::serverbound::PacketType type, const QByteArray &buffer)
|
||||
{
|
||||
McDataStream dataStream(const_cast<QByteArray *>(&buffer), QIODevice::ReadOnly);
|
||||
|
||||
switch(type)
|
||||
{
|
||||
using namespace packets::login;
|
||||
case serverbound::PacketType::Login:
|
||||
{
|
||||
qDebug() << type;
|
||||
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);
|
||||
}
|
||||
new PlayClient{m_socket, m_server};
|
||||
deleteLater();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning() << "unknown packet type" << type;
|
||||
}
|
||||
}
|
32
loginclient.h
Normal file
32
loginclient.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "mcdatastream.h"
|
||||
#include "packets.h"
|
||||
|
||||
class QTcpSocket;
|
||||
class QByteArray;
|
||||
|
||||
class Server;
|
||||
|
||||
class LoginClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LoginClient(QTcpSocket &socket, Server &server);
|
||||
|
||||
private slots:
|
||||
void readyRead();
|
||||
|
||||
private:
|
||||
void readPacket(packets::login::serverbound::PacketType type, const QByteArray &buffer);
|
||||
|
||||
QTcpSocket &m_socket;
|
||||
Server &m_server;
|
||||
|
||||
McDataStream m_dataStream;
|
||||
|
||||
qint32 m_packetSize{};
|
||||
};
|
44
main.cpp
44
main.cpp
@@ -1,11 +1,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <qlogging.h>
|
||||
#include <QTcpServer>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
#include "client.h"
|
||||
#include "server.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
@@ -22,43 +18,7 @@ int main(int argc, char *argv[])
|
||||
"%{function}(): "
|
||||
"%{message}"));
|
||||
|
||||
QList<QPointer<Client>> clients;
|
||||
|
||||
QTimer timer;
|
||||
timer.setInterval(100);
|
||||
QObject::connect(&timer, &QTimer::timeout, [&clients](){
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
for (auto iter = std::begin(clients); iter != std::end(clients); )
|
||||
{
|
||||
if ((*iter).isNull())
|
||||
{
|
||||
iter = clients.erase(iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &client = **iter;
|
||||
if (client.state() == PlayState)
|
||||
{
|
||||
client.keepAlive();
|
||||
client.sendChatMessage();
|
||||
client.trialDisconnect();
|
||||
}
|
||||
|
||||
iter++;
|
||||
}
|
||||
});
|
||||
timer.start();
|
||||
|
||||
QTcpServer server;
|
||||
|
||||
QObject::connect(&server, &QTcpServer::newConnection, [&server,&clients](){
|
||||
clients.append(new Client(server.nextPendingConnection()));
|
||||
});
|
||||
|
||||
if(!server.listen(QHostAddress::Any, 25565))
|
||||
qFatal("could not start listening %s", server.errorString().toUtf8().constData());
|
||||
|
||||
qDebug() << "started listening";
|
||||
Server server;
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
#include "packets.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "mcdatastream.h"
|
||||
|
||||
packets::handshaking::serverbound::Handshake::Handshake(McDataStream &stream)
|
||||
@@ -79,6 +81,7 @@ packets::play::serverbound::InteractEntity::InteractEntity(McDataStream &stream)
|
||||
targetX = stream.readFloat();
|
||||
targetY = stream.readFloat();
|
||||
targetZ = stream.readFloat();
|
||||
[[fallthrough]];
|
||||
case Interact:
|
||||
hand = Hand(stream.readVar<qint32>());
|
||||
}
|
||||
|
29
packets.h
29
packets.h
@@ -9,8 +9,6 @@
|
||||
|
||||
class McDataStream;
|
||||
|
||||
enum SocketState { HandshakingState, StatusState, LoginState, PlayState, ClosedState };
|
||||
|
||||
namespace packets {
|
||||
namespace handshaking {
|
||||
namespace serverbound {
|
||||
@@ -20,16 +18,27 @@ namespace packets {
|
||||
Q_ENUM_NS(PacketType)
|
||||
|
||||
struct Handshake {
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
Handshake(McDataStream &stream);
|
||||
|
||||
qint32 protocolVersion;
|
||||
QString serverAddress;
|
||||
quint16 serverPort;
|
||||
|
||||
enum class SocketState { HandshakingState, StatusState, LoginState, PlayState, ClosedState };
|
||||
Q_ENUM(SocketState)
|
||||
|
||||
SocketState nextState;
|
||||
};
|
||||
}
|
||||
|
||||
namespace clientbound {
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class PacketType {};
|
||||
Q_ENUM_NS(PacketType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,4 +229,20 @@ namespace packets {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace closed {
|
||||
namespace serverbound {
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class PacketType {};
|
||||
Q_ENUM_NS(PacketType)
|
||||
}
|
||||
|
||||
namespace clientbound {
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class PacketType {};
|
||||
Q_ENUM_NS(PacketType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
186
playclient.cpp
Normal file
186
playclient.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "playclient.h"
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "server.h"
|
||||
#include "closedclient.h"
|
||||
|
||||
PlayClient::PlayClient(QTcpSocket &socket, Server &server) :
|
||||
QObject{&server}, m_socket{socket}, m_server{server}, m_dataStream{&m_socket}
|
||||
{
|
||||
m_server.add(*this);
|
||||
|
||||
m_socket.setParent(this);
|
||||
|
||||
connect(&m_socket, &QIODevice::readyRead, this, &PlayClient::readyRead);
|
||||
connect(&m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater);
|
||||
|
||||
{
|
||||
packets::play::clientbound::JoinGame packet;
|
||||
packet.entityid = 1;
|
||||
packet.gamemode = packets::play::clientbound::JoinGame::Creative;
|
||||
packet.dimension = packets::play::clientbound::JoinGame::Overworld;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
PlayClient::~PlayClient()
|
||||
{
|
||||
m_server.remove(*this);
|
||||
}
|
||||
|
||||
void PlayClient::keepAlive()
|
||||
{
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
if (!m_lastKeepAliveSent.isValid() || m_lastKeepAliveSent.secsTo(now) >= 1)
|
||||
{
|
||||
packets::play::clientbound::KeepAlive packet;
|
||||
packet.keepAliveId = 0;
|
||||
packet.serialize(m_dataStream);
|
||||
m_lastKeepAliveSent = now;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayClient::sendChatMessage()
|
||||
{
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
if (!m_lastChatMessage.isValid() || m_lastChatMessage.secsTo(now) >= 2)
|
||||
{
|
||||
packets::play::clientbound::ChatMessage packet;
|
||||
packet.jsonData = "{"
|
||||
"\"text\": \"Chat message\", "
|
||||
"\"bold\": \"true\" "
|
||||
"}";
|
||||
packet.position = packets::play::clientbound::ChatMessage::Chat;
|
||||
packet.serialize(m_dataStream);
|
||||
m_lastChatMessage = now;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayClient::trialDisconnect()
|
||||
{
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
if (m_connectedSince.secsTo(now) >= 20)
|
||||
{
|
||||
packets::play::clientbound::Disconnect packet;
|
||||
packet.reason = "{"
|
||||
"\"text\": \"Your trial has ended.\", "
|
||||
"\"bold\": \"true\" "
|
||||
"}";
|
||||
packet.serialize(m_dataStream);
|
||||
m_socket.flush();
|
||||
new ClosedClient{m_socket, m_server};
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayClient::readyRead()
|
||||
{
|
||||
while(m_socket.bytesAvailable())
|
||||
{
|
||||
if(!m_packetSize)
|
||||
{
|
||||
m_packetSize = m_dataStream.readVar<qint32>();
|
||||
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<qint32>(bytesRead);
|
||||
m_packetSize -= bytesRead;
|
||||
const auto buffer = m_socket.read(m_packetSize);
|
||||
Q_ASSERT(buffer.size() == m_packetSize);
|
||||
m_packetSize = 0;
|
||||
|
||||
readPacket(packets::play::serverbound::PacketType(type), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayClient::readPacket(packets::play::serverbound::PacketType type, const QByteArray &buffer)
|
||||
{
|
||||
McDataStream dataStream(const_cast<QByteArray *>(&buffer), QIODevice::ReadOnly);
|
||||
|
||||
switch(type)
|
||||
{
|
||||
using namespace packets::play;
|
||||
case serverbound::PacketType::ClientSettings:
|
||||
{
|
||||
qDebug() << type;
|
||||
{
|
||||
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::PacketType::InteractEntity:
|
||||
{
|
||||
qDebug() << type;
|
||||
serverbound::InteractEntity packet{dataStream};
|
||||
qDebug() << "entityId" << packet.entityId;
|
||||
qDebug() << "type" << packet.type;
|
||||
// qDebug() << "targetX" << packet.targetX;
|
||||
// qDebug() << "targetY" << packet.targetY;
|
||||
// qDebug() << "targetZ" << packet.targetZ;
|
||||
// qDebug() << "hand" << packet.hand;
|
||||
break;
|
||||
}
|
||||
case serverbound::PacketType::PluginMessage:
|
||||
{
|
||||
qDebug() << type;
|
||||
serverbound::PluginMessage packet{dataStream};
|
||||
qDebug() << "channel" << packet.channel;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning() << "unknown packet type" << type;
|
||||
}
|
||||
}
|
42
playclient.h
Normal file
42
playclient.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "mcdatastream.h"
|
||||
#include "packets.h"
|
||||
|
||||
class QTcpSocket;
|
||||
class QByteArray;
|
||||
|
||||
class Server;
|
||||
|
||||
class PlayClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlayClient(QTcpSocket &socket, Server &server);
|
||||
~PlayClient() override;
|
||||
|
||||
void keepAlive();
|
||||
void sendChatMessage();
|
||||
void trialDisconnect();
|
||||
|
||||
private slots:
|
||||
void readyRead();
|
||||
|
||||
private:
|
||||
void readPacket(packets::play::serverbound::PacketType type, const QByteArray &buffer);
|
||||
|
||||
QTcpSocket &m_socket;
|
||||
Server &m_server;
|
||||
|
||||
McDataStream m_dataStream;
|
||||
|
||||
qint32 m_packetSize{};
|
||||
|
||||
const QDateTime m_connectedSince{QDateTime::currentDateTime()};
|
||||
QDateTime m_lastKeepAliveSent;
|
||||
QDateTime m_lastKeepAliveReceived;
|
||||
QDateTime m_lastChatMessage;
|
||||
};
|
47
server.cpp
Normal file
47
server.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "server.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include "playclient.h"
|
||||
#include "handshakingclient.h"
|
||||
|
||||
Server::Server(QObject *parent) :
|
||||
QObject{parent}
|
||||
{
|
||||
m_timer.setInterval(100);
|
||||
connect(&m_timer, &QTimer::timeout, this, &Server::timeout);
|
||||
m_timer.start();
|
||||
|
||||
connect(&m_server, &QTcpServer::newConnection, this, &Server::newConnection);
|
||||
|
||||
if(!m_server.listen(QHostAddress::Any, 25565))
|
||||
qFatal("could not start listening %s", m_server.errorString().toUtf8().constData());
|
||||
}
|
||||
|
||||
void Server::add(PlayClient &playClient)
|
||||
{
|
||||
m_playClients.insert(&playClient);
|
||||
}
|
||||
|
||||
void Server::remove(PlayClient &playClient)
|
||||
{
|
||||
m_playClients.erase(&playClient);
|
||||
}
|
||||
|
||||
void Server::timeout()
|
||||
{
|
||||
for (auto client : m_playClients)
|
||||
{
|
||||
client->keepAlive();
|
||||
client->sendChatMessage();
|
||||
client->trialDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void Server::newConnection()
|
||||
{
|
||||
auto * const connection = m_server.nextPendingConnection();
|
||||
if (connection)
|
||||
new HandshakingClient{*connection, *this};
|
||||
//clients.push_back();
|
||||
}
|
31
server.h
Normal file
31
server.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QTcpServer>
|
||||
#include <QPointer>
|
||||
|
||||
#include <set>
|
||||
|
||||
class PlayClient;
|
||||
|
||||
class Server : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Server(QObject *parent = nullptr);
|
||||
|
||||
void add(PlayClient &playClient);
|
||||
void remove(PlayClient &playClient);
|
||||
|
||||
private slots:
|
||||
void timeout();
|
||||
void newConnection();
|
||||
|
||||
private:
|
||||
QTimer m_timer;
|
||||
QTcpServer m_server;
|
||||
|
||||
std::set<PlayClient*> m_playClients;
|
||||
};
|
@@ -1,69 +1,21 @@
|
||||
#include "client.h"
|
||||
#include "statusclient.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTcpSocket>
|
||||
#include <QUuid>
|
||||
#include <QTimer>
|
||||
|
||||
Client::Client(QTcpSocket *socket, QObject *parent) :
|
||||
QObject(parent), m_socket(socket), m_dataStream(m_socket),
|
||||
m_packetSize(0), m_state(HandshakingState), m_connectedSince(QDateTime::currentDateTime())
|
||||
#include "server.h"
|
||||
|
||||
StatusClient::StatusClient(QTcpSocket &socket, Server &server) :
|
||||
QObject{&server}, m_socket{socket}, m_server{server}, m_dataStream{&m_socket}
|
||||
{
|
||||
m_socket->setParent(this);
|
||||
m_socket.setParent(this);
|
||||
|
||||
connect(m_socket, &QIODevice::readyRead, this, &Client::readyRead);
|
||||
connect(m_socket, &QAbstractSocket::disconnected, this, &Client::disconnected);
|
||||
|
||||
qDebug() << m_socket->peerPort();
|
||||
connect(&m_socket, &QIODevice::readyRead, this, &StatusClient::readyRead);
|
||||
connect(&m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater);
|
||||
}
|
||||
|
||||
void Client::keepAlive()
|
||||
void StatusClient::readyRead()
|
||||
{
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
if (!m_lastKeepAliveSent.isValid() || m_lastKeepAliveSent.secsTo(now) >= 1)
|
||||
{
|
||||
packets::play::clientbound::KeepAlive packet;
|
||||
packet.keepAliveId = 0;
|
||||
packet.serialize(m_dataStream);
|
||||
m_lastKeepAliveSent = now;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::sendChatMessage()
|
||||
{
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
if (!m_lastChatMessage.isValid() || m_lastChatMessage.secsTo(now) >= 2)
|
||||
{
|
||||
packets::play::clientbound::ChatMessage packet;
|
||||
packet.jsonData = "{"
|
||||
"\"text\": \"Chat message\", "
|
||||
"\"bold\": \"true\" "
|
||||
"}";
|
||||
packet.position = packets::play::clientbound::ChatMessage::Chat;
|
||||
packet.serialize(m_dataStream);
|
||||
m_lastChatMessage = now;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::trialDisconnect()
|
||||
{
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
if (m_connectedSince.secsTo(now) >= 20)
|
||||
{
|
||||
packets::play::clientbound::Disconnect packet;
|
||||
packet.reason = "{"
|
||||
"\"text\": \"Your trial has ended.\", "
|
||||
"\"bold\": \"true\" "
|
||||
"}";
|
||||
packet.serialize(m_dataStream);
|
||||
m_socket->flush();
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void Client::readyRead()
|
||||
{
|
||||
while(m_socket->bytesAvailable())
|
||||
while(m_socket.bytesAvailable())
|
||||
{
|
||||
if(!m_packetSize)
|
||||
{
|
||||
@@ -71,69 +23,25 @@ void Client::readyRead()
|
||||
qDebug() << "packet size" << m_packetSize;
|
||||
}
|
||||
|
||||
if(m_socket->bytesAvailable() < m_packetSize)
|
||||
if(m_socket.bytesAvailable() < m_packetSize)
|
||||
{
|
||||
qWarning() << "packet not fully available" << m_socket->bytesAvailable();
|
||||
qWarning() << "packet not fully available" << m_socket.bytesAvailable();
|
||||
return;
|
||||
}
|
||||
|
||||
qint32 bytesRead;
|
||||
const auto type = m_dataStream.readVar<qint32>(bytesRead);
|
||||
m_packetSize -= bytesRead;
|
||||
const auto buffer = m_socket->read(m_packetSize);
|
||||
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;
|
||||
}
|
||||
readPacket(packets::status::serverbound::PacketType(type), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::disconnected()
|
||||
void StatusClient::readPacket(packets::status::serverbound::PacketType type, const QByteArray &buffer)
|
||||
{
|
||||
qDebug() << m_socket->peerPort();
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void Client::readPacketHandshaking(packets::handshaking::serverbound::PacketType type, const QByteArray &buffer)
|
||||
{
|
||||
qDebug() << type;
|
||||
|
||||
McDataStream dataStream(const_cast<QByteArray *>(&buffer), QIODevice::ReadOnly);
|
||||
|
||||
switch(type)
|
||||
{
|
||||
using namespace packets::handshaking;
|
||||
case serverbound::PacketType::Handshake:
|
||||
{
|
||||
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<QByteArray *>(&buffer), QIODevice::ReadOnly);
|
||||
|
||||
switch(type)
|
||||
@@ -141,8 +49,9 @@ void Client::readPacketStatus(const packets::status::serverbound::PacketType typ
|
||||
using namespace packets::status;
|
||||
case serverbound::PacketType::Request:
|
||||
{
|
||||
qDebug() << type;
|
||||
{
|
||||
serverbound::Request packet(dataStream);
|
||||
serverbound::Request packet{dataStream};
|
||||
}
|
||||
{
|
||||
clientbound::Response packet;
|
||||
@@ -173,9 +82,10 @@ void Client::readPacketStatus(const packets::status::serverbound::PacketType typ
|
||||
}
|
||||
case serverbound::PacketType::Ping:
|
||||
{
|
||||
qDebug() << type;
|
||||
qint64 payload;
|
||||
{
|
||||
serverbound::Ping packet(dataStream);
|
||||
serverbound::Ping packet{dataStream};
|
||||
payload = packet.payload;
|
||||
}
|
||||
{
|
||||
@@ -186,127 +96,6 @@ void Client::readPacketStatus(const packets::status::serverbound::PacketType typ
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning() << "unknown type!";
|
||||
}
|
||||
}
|
||||
|
||||
void Client::readPacketLogin(const packets::login::serverbound::PacketType type, const QByteArray &buffer)
|
||||
{
|
||||
qDebug() << type;
|
||||
|
||||
McDataStream dataStream(const_cast<QByteArray *>(&buffer), QIODevice::ReadOnly);
|
||||
|
||||
switch(type)
|
||||
{
|
||||
using namespace packets::login;
|
||||
case serverbound::PacketType::Login:
|
||||
{
|
||||
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 = packets::play::clientbound::JoinGame::Creative;
|
||||
packet.dimension = packets::play::clientbound::JoinGame::Overworld;
|
||||
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<QByteArray *>(&buffer), QIODevice::ReadOnly);
|
||||
|
||||
switch(type)
|
||||
{
|
||||
using namespace packets::play;
|
||||
case serverbound::PacketType::ClientSettings:
|
||||
{
|
||||
{
|
||||
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::PacketType::InteractEntity:
|
||||
{
|
||||
serverbound::InteractEntity packet(dataStream);
|
||||
qDebug() << "entityId" << packet.entityId;
|
||||
qDebug() << "type" << packet.type;
|
||||
// qDebug() << "targetX" << packet.targetX;
|
||||
// qDebug() << "targetY" << packet.targetY;
|
||||
// qDebug() << "targetZ" << packet.targetZ;
|
||||
// qDebug() << "hand" << packet.hand;
|
||||
break;
|
||||
}
|
||||
case serverbound::PacketType::PluginMessage:
|
||||
{
|
||||
serverbound::PluginMessage packet(dataStream);
|
||||
qDebug() << "channel" << packet.channel;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning() << "unknown type!";
|
||||
qWarning() << "unknown packet type" << type;
|
||||
}
|
||||
}
|
32
statusclient.h
Normal file
32
statusclient.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "mcdatastream.h"
|
||||
#include "packets.h"
|
||||
|
||||
class QTcpSocket;
|
||||
class QByteArray;
|
||||
|
||||
class Server;
|
||||
|
||||
class StatusClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StatusClient(QTcpSocket &socket, Server &server);
|
||||
|
||||
private slots:
|
||||
void readyRead();
|
||||
|
||||
private:
|
||||
void readPacket(packets::status::serverbound::PacketType type, const QByteArray &buffer);
|
||||
|
||||
QTcpSocket &m_socket;
|
||||
Server &m_server;
|
||||
|
||||
McDataStream m_dataStream;
|
||||
|
||||
qint32 m_packetSize{};
|
||||
};
|
Reference in New Issue
Block a user