RyanHub – file viewer
filename: src/network/protocol/PacketCodec.cpp
branch: feature/world
back to repo
/*
 *
 *               _____  _                 _
 *              /  ___|| |               | |
 *              \ `--. | |_  _ __   __ _ | |_   ___   ___
 *               `--. \| __|| '__| / _` || __| / _ \ / __|
 *              /\__/ /| |_ | |   | (_| || |_ | (_) |\__ \
 *              \____/  \__||_|    \__,_| \__| \___/ |___/
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Copyright (C) 2025 Armen Deroian
 *
 */

#include "PacketCodec.h"

#include "../session/NetworkClient.h"
#include "network/Network.h"
#include "network/session/SessionAuth.h"
#include "nlohmann/detail/input/parser.hpp"
#include "PacketSerialization.h"
#include "Server.h"
#include "utils/config/Config.h"
#include "utils/UUID.h"

void stratos::ClientHandshake::decrypt(PacketBuffer& buffer) {
    protocolVersion = buffer.readVarInt();
    serverAddress   = buffer.readString(255);
    serverPort      = buffer.readUnsignedShort();

    // Read the intent
    intent = buffer.readVarIntEnum<HandshakeIntent>([](const int& value) -> HandshakeIntent {
        switch (value) {
        case 0x01:
            return HandshakeIntent::Status;
        case 0x02:
            return HandshakeIntent::Login;
        case 0x03:
            return HandshakeIntent::Transfer;
        default:
            throw PacketSerializationException("Unknown intent value: " + std::to_string(value));
        }
    });
}
void stratos::LegacyServerListPing::decrypt(PacketBuffer& buffer) {
    payload = buffer.readByte();

    // None of this matters, but we need to read the payload to avoid deserialization errors
    buffer.readByte();          // Packet identifier
    buffer.readStringUTF16BE(); // MC|Pinghost
    buffer.readShort();         // Remaining length
    buffer.readByte();          // Protocol version
    buffer.readStringUTF16BE(); // Server address
    buffer.readInt();           // Server port
}
void stratos::LegacyServerListPong::encrypt(PacketBuffer& buffer) {
    buffer.writeByte(0xFF); // Kick packet identifier
    const std::string payload =
        "§1\0" + std::to_string(protocolVersion) + "\0" + version + "\0" + motd + "\0" + "\0" + std::to_string(onlinePlayers) + "\0" + std::to_string(maxPlayers);
    buffer.writeStringUTF16BE(payload);
}
void stratos::StatusResponse::encrypt(PacketBuffer& buffer) {
    buffer.writeString(jsonResponse, 32767);
}
void stratos::PongResponse::encrypt(PacketBuffer& buffer) {
    buffer.writeLong(timestamp);
}
void stratos::StatusRequest::decrypt(PacketBuffer& buffer) {}
void stratos::PingRequest::decrypt(PacketBuffer& buffer) {
    timestamp = buffer.readLong();
}
void stratos::LoginStart::decrypt(PacketBuffer& buffer) {
    name = buffer.readString(16);
    uuid = buffer.readUUID();
}
void stratos::EncryptionResponse::decrypt(PacketBuffer& buffer) {
    sharedSecret = buffer.readByteArray(buffer.readVarInt());
    verifyToken  = buffer.readByteArray(buffer.readVarInt());
}
void stratos::LoginPluginResponse::decrypt(PacketBuffer& buffer) {
    messageId = buffer.readVarInt();
    data      = buffer.readPrefixedOptionalInferredByteArray();
}
void stratos::LoginAcknowledge::decrypt(PacketBuffer& buffer) {}
void stratos::LoginCookieResponse::decrypt(PacketBuffer& buffer) {
    cookie = buffer.readIdentifier();
    payload = buffer.readPrefixedOptionalPrefixedByteArray();
}
void stratos::LoginDisconnect::encrypt(PacketBuffer& buffer) {
    buffer.writeString(reason, 32767);
}
void stratos::EncryptionRequest::encrypt(PacketBuffer& buffer) {
    buffer.writeString(serverId, 20);
    buffer.writePrefixedByteArray(publicKey);
    buffer.writePrefixedByteArray(verifyToken);
    buffer.writeBoolean(shouldAuthenticate);
}
void stratos::LoginSuccess::encrypt(PacketBuffer& buffer) {
    buffer.writeUUID(uuid);
    buffer.writeString(username, 16);
    buffer.writeLoginProperty(properties);
}
void stratos::SetCompression::encrypt(PacketBuffer& buffer) {
    buffer.writeVarInt(threshold);
}
void stratos::LoginPluginRequest::encrypt(PacketBuffer& buffer) {
    buffer.writeVarInt(messageId);
    buffer.writeIdentifier(channel);
    buffer.writeByteArray(data);
}
void stratos::LoginCookieRequest::encrypt(PacketBuffer& buffer) {
    buffer.writeIdentifier(cookie);
}
void stratos::ConfigurationClientInformation::decrypt(PacketBuffer& buffer) {
    locale = buffer.readString(16);
    viewDistance = buffer.readByte();
    chatMode = buffer.readVarIntEnum<ChatMode>([](const int& value) -> ChatMode {
        switch (value) {
        case 0:
            return ChatMode::Enabled;
        case 1:
            return ChatMode::CommandsOnly;
        case 2:
            return ChatMode::Hidden;
        default:
            throw PacketSerializationException("Unknown chat mode value: " + std::to_string(value));
        }
    });
    chatColors = buffer.readBoolean();
    skinParts = buffer.readUnsignedByte();
    mainHand = buffer.readVarIntEnum<Hand>([](const int& value) -> Hand {
        switch (value) {
        case 0:
            return Hand::Left;
        case 1:
            return Hand::Right;
        default:
            throw PacketSerializationException("Unknown hand value: " + std::to_string(value));
        }
    });
    enableTextFiltering = buffer.readBoolean();
    allowServerListing = buffer.readBoolean();
    particleStatus = buffer.readVarIntEnum<ParticleStatus>([](const int& value) -> ParticleStatus {
        switch (value) {
        case 0:
            return ParticleStatus::All;
        case 1:
            return ParticleStatus::Decreased;
        case 2:
            return ParticleStatus::Minimal;
        default:
            throw PacketSerializationException("Unknown particle status value: " + std::to_string(value));
        }
    });
}
void stratos::ConfigurationCookieResponse::decrypt(PacketBuffer& buffer) {
    key = buffer.readIdentifier();
    value = buffer.readPrefixedOptionalPrefixedByteArray();
}
void stratos::ConfigurationServerPluginMessage::decrypt(PacketBuffer& buffer) {
    channel = buffer.readIdentifier();
    data    = buffer.readInferredByteArray();
}
void stratos::AcknowledgeFinishConfiguration::decrypt(PacketBuffer& buffer) {}
void stratos::ConfigurationServerboundKeepAlive::decrypt(PacketBuffer& buffer) {
    id = buffer.readLong();
}
void stratos::ConfigurationPong::decrypt(PacketBuffer& buffer) {
    id = buffer.readInt();
}
void stratos::ConfigurationResourcePackResponse::decrypt(PacketBuffer& buffer) {
    uuid = buffer.readUUID();
    result = buffer.readVarIntEnum<ResourcePackResult>([](const int& value) -> ResourcePackResult {
        switch (value) {
        case 0:
            return ResourcePackResult::SuccessfullyDownloaded;
        case 1:
            return ResourcePackResult::Declined;
        case 2:
            return ResourcePackResult::FailedDownload;
        case 3:
            return ResourcePackResult::Accepted;
        case 4:
            return ResourcePackResult::Downloaded;
        case 5:
            return ResourcePackResult::InvalidURL;
        case 6:
            return ResourcePackResult::FailedReload;
        case 7:
            return ResourcePackResult::Discarded;
        default:
            throw PacketSerializationException("Unknown resource pack result value: " + std::to_string(value));
        }
    });
}
void stratos::ServerboundKnownPacks::decrypt(PacketBuffer& buffer) {
    knownPacks = buffer.readPrefixedResourcePackHeaderArray();
}
void stratos::ConfigurationCookieRequest::encrypt(PacketBuffer& buffer) {
    buffer.writeIdentifier(key);
}
void stratos::ConfigurationClientboundPluginMessage::encrypt(PacketBuffer& buffer) {
    buffer.writeIdentifier(channel);
    buffer.writeByteArray(data);
}
void stratos::ConfigurationDisconnect::encrypt(PacketBuffer& buffer) {
    buffer.writeString(reason, 32767);
}
void stratos::FinishConfiguration::encrypt(PacketBuffer& buffer) {}
void stratos::ConfigurationClientboundKeepAlive::encrypt(PacketBuffer& buffer) {
    buffer.writeLong(id);
}
void stratos::ConfigurationPing::encrypt(PacketBuffer& buffer) {
    buffer.writeInt(id);
}
void stratos::ResetChat::encrypt(PacketBuffer& buffer) {}
void stratos::RegistryDataPacket::encrypt(PacketBuffer& buffer) {
    buffer.writeIdentifier(registryKey);
    buffer.writePrefixedRegistryEntryArray(entries);
}
void stratos::ConfigurationRemoveRemoveResourcePack::encrypt(PacketBuffer& buffer) {
    buffer.writePrefixedOptionalUUID(uuid);
}
void stratos::ConfigurationAddResourcePack::encrypt(PacketBuffer& buffer) {
    buffer.writeUUID(uuid);
    buffer.writeString(url, 32767);
    buffer.writeString(hash, 40);
    buffer.writeBoolean(forced);
    buffer.writePrefixedOptionalString(prompt, 32767);
}
void stratos::ConfigurationStoreCookie::encrypt(PacketBuffer& buffer) {
    buffer.writeIdentifier(key);
    buffer.writePrefixedByteArray(value);
}
void stratos::ConfigurationTransfer::encrypt(PacketBuffer& buffer) {
    buffer.writeString(host, 255);
    buffer.writeVarInt(port);
}
void stratos::FeatureFlags::encrypt(PacketBuffer& buffer) {
    buffer.writePrefixedIdentifierArray(features);
}
void stratos::ConfigurationUpdateTags::encrypt(PacketBuffer& buffer) {
    buffer.writePrefixedRegistryTagDataArray(tags);
}
void stratos::ClientboundKnownPacks::encrypt(PacketBuffer& buffer) {
    buffer.writePrefixedResourcePackHeaderArray(knownPackets);
}
void stratos::ConfigurationCustomReportDetails::encrypt(PacketBuffer& buffer) {
    buffer.writePrefixedReportDetailArray(details);
}
void stratos::ConfigurationServerLinks::encrypt(PacketBuffer& buffer) {
    buffer.writePrefixedServerLinkArray(links);
}
void stratos::GameEventPacket::encrypt(PacketBuffer& buffer) {
    buffer.writeUnsignedByte(static_cast<uint8_t>(event));
    buffer.writeFloat(data);
}
void stratos::SynchronizePlayerPosition::encrypt(PacketBuffer& buffer) {
    buffer.writeVarInt(teleportId);
    buffer.writeDouble(x);
    buffer.writeDouble(y);
    buffer.writeDouble(z);
    buffer.writeDouble(velocityX);
    buffer.writeDouble(velocityY);
    buffer.writeDouble(velocityZ);
    buffer.writeFloat(yaw);
    buffer.writeFloat(pitch);
    buffer.writeInt(flags);
}
void stratos::LoginPlay::encrypt(PacketBuffer& buffer) {
    buffer.writeInt(entityId);
    buffer.writeBoolean(isHardcore);
    buffer.writePrefixedIdentifierArray(dimensions);
    buffer.writeVarInt(maxPlayers);
    buffer.writeVarInt(viewDistance);
    buffer.writeVarInt(simulationDistance);
    buffer.writeBoolean(reducedDebugInfo);
    buffer.writeBoolean(enableRespawnScreen);
    buffer.writeBoolean(doLimitedCrafting);
    buffer.writeVarInt(dimensionType);
    buffer.writeIdentifier(dimensionName);
    buffer.writeLong(hashedSeed);
    buffer.writeByte(gamemode);
    buffer.writeByte(previousGamemode);
    buffer.writeBoolean(debug);
    buffer.writeBoolean(flat);
    buffer.writeBoolean(hasDeathLocation);
    if (hasDeathLocation) {
        buffer.writeIdentifier(deathDimension.value());
        buffer.writePosition(deathPosition.value());
    }
    buffer.writeVarInt(portalCooldown);
    buffer.writeVarInt(seaLevel);
    buffer.writeBoolean(enforcesSecureChat);
}
void stratos::SetCenterChunk::encrypt(PacketBuffer& buffer) {
    buffer.writeVarInt(chunkX);
    buffer.writeVarInt(chunkZ);
}
void stratos::SetDefaultSpawnPosition::encrypt(PacketBuffer& buffer) {
    buffer.writePosition(location);
    buffer.writeFloat(angle);
}
bool stratos::HandshakePacketHandler::handle(ClientHandshake& packet) {
    switch (packet.intent) {
    case HandshakeIntent::Status:
        connection->changeState(Status);
        break;
    case HandshakeIntent::Login:
    case HandshakeIntent::Transfer: // TODO: Figure out how to handle transfer
        connection->changeState(Login);
    default:;
    }
    return true;
}
bool stratos::HandshakePacketHandler::handle(LegacyServerListPing& packet) {
    connection->sendPacket(std::make_unique<LegacyServerListPong>(47, PROTOCOL_VERSION_STRING, server->getMotd(), server->getOnlinePlayers(), server->getServerProperties()->getProperty("max-players").value().get().asInt()));
    connection->disconnect();
    return true;
}
bool stratos::StatusPacketHandler::handle(StatusRequest& packet) {
    if (connection->getState() == ProtocolState::Status) {
        constexpr auto STATUS_RESPONSE_FORMAT = R"({{"version":{{"name":"{}","protocol":{}}},"players":{{"max":{},"online":{},"sample":[]}},"description":{{"text":"{}"}}}})";
        connection->sendPacket(std::make_unique<StatusResponse>(
            std::format(STATUS_RESPONSE_FORMAT, std::string(PROTOCOL_VERSION_STRING), PROTOCOL_VERSION, server->getMaxPlayers(), server->getOnlinePlayers(), server->getMotd())));
    }
    return true;
}
bool stratos::StatusPacketHandler::handle(PingRequest& packet) {
    if (connection->getState() == ProtocolState::Status)
        connection->sendPacket(std::make_unique<PongResponse>(packet.timestamp));
    return true;
}
bool stratos::LoginPacketHandler::handle(LoginStart& packet) {
    if (connection->getNetwork()->useEncryption()) {
        connection->updateSessionInfo({packet.name, packet.uuid});
        connection->encryptionKey = &connection->getNetwork()->getEncryptionKey();
        std::vector<uint8_t> token = connection->verifyToken = generateRandomBytes(16);
        connection->sendPacket(std::make_unique<EncryptionRequest>(server->getName(), encodeServerPublicKey(connection->encryptionKey), std::move(token), server->isOnlineMode()));
    } else {
        connection->updateSessionInfo({packet.name, generateOfflineUUID(packet.name)});
        connection->sendPacket(std::make_unique<LoginSuccess>(packet.uuid, std::move(packet.name), std::vector<LoginProperty>()));
    }
    return true;
}
bool stratos::LoginPacketHandler::handle(EncryptionResponse& packet) {
    if (const std::vector<uint8_t> decryptToken = rsaDecrypt(connection->encryptionKey, packet.verifyToken); decryptToken != connection->verifyToken) {
        connection->disconnect();
        return false;
    }
    connection->clientSecret = std::move(rsaDecrypt(connection->encryptionKey, packet.sharedSecret));
    connection->encryptionEnabled = false;

    if (server->isOnlineMode()) {
        authenticate(connection, server->getName(), connection->clientSecret, *connection->encryptionKey);
    } else {
        std::string username = connection->getSessionInfo()->get().username;
        connection->updateSessionInfo({username, generateOfflineUUID(username)});
        connection->sendPacket(std::make_unique<LoginSuccess>(connection->getSessionInfo()->get().uuid, std::move(username), std::vector<LoginProperty>()));
    }
    return true;
}
bool stratos::LoginPacketHandler::handle(LoginPluginResponse& packet) { return false; }
bool stratos::LoginPacketHandler::handle(LoginAcknowledge& packet) {
    connection->createNetworkSession();
    connection->changeState(Configuration);
    return true;
}
bool stratos::LoginPacketHandler::handle(LoginCookieResponse& packet) { return false; }
bool stratos::ConfigurationPacketHandler::handle(ConfigurationClientInformation& packet) {
    return PacketHandler::handle(packet);
}
bool stratos::ConfigurationPacketHandler::handle(ConfigurationCookieResponse& packet) {
    return PacketHandler::handle(packet);
}
bool stratos::ConfigurationPacketHandler::handle(ConfigurationServerPluginMessage& packet) {
    return PacketHandler::handle(packet);
}
bool stratos::ConfigurationPacketHandler::handle(AcknowledgeFinishConfiguration& packet) {
    session->changeState(Play);
    session->loginPlayer();
    return true;
}
bool stratos::ConfigurationPacketHandler::handle(ConfigurationServerboundKeepAlive& packet) {
    return PacketHandler::handle(packet);
}
bool stratos::ConfigurationPacketHandler::handle(ConfigurationPong& packet) {
    return PacketHandler::handle(packet);
}
bool stratos::ConfigurationPacketHandler::handle(ConfigurationResourcePackResponse& packet) {
    return PacketHandler::handle(packet);
}
bool stratos::ConfigurationPacketHandler::handle(ServerboundKnownPacks& packet) {
    return PacketHandler::handle(packet);
}