filename:
src/network/protocol/PacketSerialization.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 "PacketSerialization.h"
#include "utils/Types.h"
#include <codecvt>
#include <cstring>
#include <locale>
int8_t stratos::readByte(const ByteVec& buffer, size_t& offset) {
if (offset + sizeof(int8_t) > buffer.size()) throw PacketSerializationException("Buffer overflow: Not enough bytes to read byte.");
return static_cast<int8_t>(buffer[offset++]);
}
uint8_t stratos::readUnsignedByte(const ByteVec& buffer, size_t& offset) {
if (offset + sizeof(uint8_t) > buffer.size()) throw PacketSerializationException("Buffer overflow: Not enough bytes to read unsigned byte.");
return buffer[offset++];
}
int stratos::readVarInt(const ByteVec& buffer, size_t& offset) {
int value = 0;
int position = 0;
while (true) {
const uint8_t currentByte = readByte(buffer, offset);
value |= (currentByte & 0x7F) << position;
if ((currentByte & 0x80) == 0) break;
position += 7;
if (position >= 32) {
throw BufferOverflowException("VarInt is too big to fit in an int.");
}
}
return value;
}
std::string stratos::PacketBuffer::readStringUTF16BE() {
if (offset + 2 > buffer.size()) throw std::out_of_range("StringUTF16BE: not enough bytes for length");
const uint16_t length = static_cast<uint16_t>(buffer[offset++]) << 8 | static_cast<uint16_t>(buffer[offset++]);
if (const size_t byteLength = length * 2; offset + byteLength > buffer.size()) throw PacketSerializationException("StringUTF16BE: not enough bytes for string content");
std::u16string utf16;
utf16.reserve(length);
for (size_t i = 0; i < length; ++i) {
const char16_t ch = static_cast<char16_t>(buffer[offset++]) << 8 | static_cast<char16_t>(buffer[offset++]);
utf16.push_back(ch);
}
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
return convert.to_bytes(utf16);
}
stratos::utils::Identifier stratos::PacketBuffer::readIdentifier() {
std::string rawIdentifier = readString(32767);
const size_t colonPos = rawIdentifier.find(':');
if (colonPos == std::string::npos) throw PacketSerializationException("Identifier: missing namespace separator ':'");
return {
rawIdentifier.substr(0, colonPos), // namespace
rawIdentifier.substr(colonPos + 1) // name
};
}
stratos::math::Position stratos::PacketBuffer::readPosition() {
const int64_t rawPosition = readLong();
math::Position position;
position.x = static_cast<int32_t>(rawPosition >> 38);
position.y = static_cast<int32_t>(rawPosition << 52 >> 52);
position.z = static_cast<int32_t>(rawPosition << 26 >> 38);
return position;
}
stratos::math::Angle stratos::PacketBuffer::readAngle() {
return readUnsignedByte();
}
stratos::UUID stratos::PacketBuffer::readUUID() {
stratos::isReadable(buffer, offset, 16);
UUID uuid;
std::copy(buffer.begin() + offset, buffer.begin() + offset + 16, uuid.begin());
offset += 16;
return uuid;
}
std::vector<uint8_t> stratos::PacketBuffer::readPrefixedByteArray() {
return readByteArray(readVarInt());
}
std::vector<stratos::LoginProperty> stratos::PacketBuffer::readLoginProperty() {
const int length = readVarInt();
if (length < 0 || length > 16)
throw PacketSerializationException("LoginProperty: invalid length " + std::to_string(length));
std::vector<LoginProperty> properties;
properties.resize(length);
for (int i = 0; i < length; ++i)
properties[i] = {readString(16), readString(32767), readPrefixedOptionalString(1024)};
return properties;
}
std::optional<std::string> stratos::PacketBuffer::readPrefixedOptionalString(const int maxChars) {
return readBoolean() ? std::make_optional(readString(maxChars)) : std::nullopt;
}
std::vector<uint8_t> stratos::PacketBuffer::readInferredByteArray() {
std::vector<uint8_t> result;
while (offset < buffer.size()) result.push_back(readByte());
return result;
}
stratos::ResourcePackHeader stratos::PacketBuffer::readResourcePackHeader() {
return {readString(32767), readString(32767), readString(32767)};
}
std::optional<std::vector<uint8_t>> stratos::PacketBuffer::readPrefixedOptionalInferredByteArray() {
return readBoolean() ? std::make_optional(readInferredByteArray()) : std::nullopt;
}
std::optional<std::vector<uint8_t>> stratos::PacketBuffer::readPrefixedOptionalPrefixedByteArray() {
return readBoolean() ? std::make_optional(readPrefixedByteArray()) : std::nullopt;
}
std::vector<stratos::ResourcePackHeader> stratos::PacketBuffer::readPrefixedResourcePackHeaderArray() {
const int length = readVarInt();
std::vector<ResourcePackHeader> headers;
headers.reserve(length);
for (int i = 0; i < length; ++i) headers.push_back(readResourcePackHeader());
return headers;
}
void stratos::PacketBuffer::writeStringUTF16BE(const std::string& value) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
const std::u16string utf16 = convert.from_bytes(value);
const auto length = static_cast<uint16_t>(utf16.size());
buffer.push_back(length >> 8 & 0xFF); // big-endian
buffer.push_back(length & 0xFF);
for (const char16_t& ch : utf16) {
buffer.push_back(ch >> 8 & 0xFF); // high byte
buffer.push_back(ch & 0xFF); // low byte
}
}
void stratos::PacketBuffer::writeIdentifier(const utils::Identifier& identifier) {
const std::string fullIdent = identifier.namespaceName + ":" + identifier.name;
writeString(fullIdent, 32767);
}
void stratos::PacketBuffer::writePosition(const math::Position& position) {
writeLong((static_cast<int64_t>(position.x) & 0x3FFFFFFLL) << 38 | (static_cast<int64_t>(position.z) & 0x3FFFFFFLL) << 12 | static_cast<int64_t>(position.y) & 0xFFFL);
}
void stratos::PacketBuffer::writeAngle(const math::Angle& angle) {
writeUnsignedByte(angle);
}
void stratos::PacketBuffer::writeUUID(const UUID& uuid) {
for (const auto& byte : uuid) buffer.push_back(byte);
}
void stratos::PacketBuffer::writePrefixedByteArray(const std::vector<uint8_t>& bytes) {
writeVarInt(static_cast<int>(bytes.size()));
writeByteArray(bytes);
}
void stratos::PacketBuffer::writeLoginProperty(const std::vector<LoginProperty>& properties) {
writeVarInt(static_cast<int>(properties.size()));
for (const auto& [name, value, signature] : properties) {
writeString(name, 16);
writeString(value, 32767);
writePrefixedOptionalString(signature, 1024);
}
}
void stratos::PacketBuffer::writeResourcePackHeader(const ResourcePackHeader& header) {
writeString(header.packNamespace, 32767);
writeString(header.id, 32767);
writeString(header.version, 32767);
}
void stratos::PacketBuffer::writeRegistryEntry(const RegistryEntry& entry) {
writeIdentifier(entry.id);
writeBoolean(entry.data.has_value());
if (entry.data.has_value()) writeByteArray(entry.data.value());
}
void stratos::PacketBuffer::writeRegistryTagData(const RegistryTagData& data) {
writeIdentifier(data.registryKey);
writeVarInt(static_cast<int>(data.entries.size()));
for (const auto& [key, id] : data.entries) {
writeIdentifier(key);
writeVarInt(id);
}
}
void stratos::PacketBuffer::writeReportDetail(const ReportDetail& detail) {
writeString(detail.title, 128);
writeString(detail.description, 4096);
}
void stratos::PacketBuffer::writeServerLink(const ServerLink& link) {
link.isDefault ? writeVarInt(static_cast<int>(link.defaultLabel)) : writeString(link.customLabel, 32767);
writeString(link.url, 32767);
}
void stratos::PacketBuffer::writePrefixedOptionalString(const std::optional<std::string>& str, const int maxChars) {
if (str) {
writeBoolean(true);
writeString(*str, maxChars);
} else {
writeBoolean(false);
}
}
void stratos::PacketBuffer::writePrefixedOptionalUUID(const std::optional<UUID>& uuid) {
writeBoolean(uuid.has_value());
if (uuid.has_value()) writeUUID(uuid.value());
}
void stratos::PacketBuffer::writePrefixedOptionalByteArray(const std::optional<std::vector<uint8_t>>& bytes) {
if (bytes) {
writeBoolean(true);
writeByteArray(*bytes);
} else {
writeBoolean(false);
}
}
void stratos::PacketBuffer::writePrefixedOptionalPrefixedByteArray(const std::optional<std::vector<uint8_t>>& bytes) {
if (bytes) {
writeBoolean(true);
writePrefixedByteArray(*bytes);
} else {
writeBoolean(false);
}
}
void stratos::PacketBuffer::writePrefixedResourcePackHeaderArray(const std::vector<ResourcePackHeader>& headers) {
writeVarInt(static_cast<int>(headers.size()));
for (const auto& header : headers) writeResourcePackHeader(header);
}
void stratos::PacketBuffer::writePrefixedRegistryEntryArray(const std::vector<RegistryEntry>& entries) {
writeVarInt(static_cast<int>(entries.size()));
for (const auto& entry : entries) writeRegistryEntry(entry);
}
void stratos::PacketBuffer::writePrefixedRegistryTagDataArray(const std::vector<RegistryTagData>& data) {
writeVarInt(static_cast<int>(data.size()));
for (const auto& entry : data) {
writeRegistryTagData(entry);
}
}
void stratos::PacketBuffer::writePrefixedIdentifierArray(const std::vector<utils::Identifier>& identifiers) {
writeVarInt(static_cast<int>(identifiers.size()));
for (const auto& identifier : identifiers) writeIdentifier(identifier);
}
void stratos::PacketBuffer::writePrefixedReportDetailArray(const std::vector<ReportDetail>& details) {
writeVarInt(static_cast<int>(details.size()));
for (const auto& detail : details) writeReportDetail(detail);
}
void stratos::PacketBuffer::writePrefixedServerLinkArray(const std::vector<ServerLink>& serverLinks) {
writeVarInt(static_cast<int>(serverLinks.size()));
for (const auto& link : serverLinks) writeServerLink(link);
}