diff --git a/core/include/vf/Protocol.h b/core/include/vf/Protocol.h new file mode 100644 index 0000000..7cdaaf8 --- /dev/null +++ b/core/include/vf/Protocol.h @@ -0,0 +1,197 @@ +#pragma once + +#include +#include +#include +#include +#include "Block.h" +#include "Chunk.h" +#include "Codec.h" + +namespace vf { + +enum class PacketId : uint8_t { + Handshake = 0, + LoginStart = 1, + JoinGame = 2, + ChunkData = 3, + BlockChange = 4, + MultiBlockChange = 5, + EntityMove = 6, + PlayerState = 7, + KeepAlive = 8, +}; + +// Wire writer: varints (unsigned LEB128 and zigzag signed), fixed scalars, +// length-prefixed strings, a 26/12/26 packed block position, and a 128-bit +// uuid split into two 64-bit halves. +class PacketWriter { +public: + void writeVarInt(int32_t v) { writer.putVarI32(v); } + void writeVarUint(uint32_t v) { writer.putVarU32(v); } + void writeVarLong(int64_t v) { writer.putVarI64(v); } + void writeU8(uint8_t v) { writer.putU8(v); } + void writeU16(uint16_t v) { writer.putU16(v); } + void writeI32(int32_t v) { writer.putU32(static_cast(v)); } + void writeI64(int64_t v) { writer.putU64(static_cast(v)); } + void writeBool(bool v) { writer.putU8(v ? 1 : 0); } + void writeFloat(float v) { writer.putFloat(v); } + void writeDouble(double v) { writer.putDouble(v); } + void writeString(const std::string& s) { writer.putString(s); } + void writeBytes(const uint8_t* p, std::size_t n) { writer.putBytes(p, n); } + void writePosition(int32_t x, int32_t y, int32_t z); + void writeUuid(uint64_t hi, uint64_t lo); + + std::size_t size() const { return writer.size(); } + const ByteBuffer& bytes() const { return writer.bytes(); } + +private: + ByteWriter writer; +}; + +// Wire reader mirroring PacketWriter. Every read is bounds checked; ok() stays +// false once any read has run past the end. +class PacketReader { +public: + PacketReader(const uint8_t* data, std::size_t size) : reader(data, size) {} + + bool readVarInt(int32_t& out) { return reader.getVarI32(out); } + bool readVarUint(uint32_t& out) { return reader.getVarU32(out); } + bool readVarLong(int64_t& out) { return reader.getVarI64(out); } + bool readU8(uint8_t& out) { return reader.getU8(out); } + bool readU16(uint16_t& out) { return reader.getU16(out); } + bool readI32(int32_t& out); + bool readI64(int64_t& out); + bool readBool(bool& out); + bool readFloat(float& out) { return reader.getFloat(out); } + bool readDouble(double& out) { return reader.getDouble(out); } + bool readString(std::string& out) { return reader.getString(out); } + bool readBytes(std::vector& out, std::size_t n); + bool readPosition(int32_t& x, int32_t& y, int32_t& z); + bool readUuid(uint64_t& hi, uint64_t& lo); + + bool ok() const { return reader.ok(); } + std::size_t remaining() const { return reader.remaining(); } + +private: + ByteReader reader; +}; + +struct Handshake { + static constexpr PacketId id = PacketId::Handshake; + int32_t protocolVersion = 0; + std::string serverAddress; + uint16_t serverPort = 0; + int32_t nextState = 0; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +struct LoginStart { + static constexpr PacketId id = PacketId::LoginStart; + std::string name; + uint64_t uuidHi = 0; + uint64_t uuidLo = 0; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +struct JoinGame { + static constexpr PacketId id = PacketId::JoinGame; + int32_t entityId = 0; + uint8_t gameMode = 0; + int32_t dimension = 0; + uint64_t seedHash = 0; + uint8_t maxPlayers = 0; + std::string levelType; + int32_t viewDistance = 0; + bool reducedDebugInfo = false; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +// A full chunk: coordinate plus the palette and bit-packed indices, carried on +// the wire as a Codec-compressed blob. +struct ChunkData { + static constexpr PacketId id = PacketId::ChunkData; + int32_t chunkX = 0; + int32_t chunkY = 0; + int32_t chunkZ = 0; + std::vector palette; + int bitWidth = 1; + std::vector packed; + + void setFromChunk(int32_t cx, int32_t cy, int32_t cz, const Chunk& chunk); + void toChunk(Chunk& out) const; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +struct BlockChange { + static constexpr PacketId id = PacketId::BlockChange; + int32_t x = 0; + int32_t y = 0; + int32_t z = 0; + Block block = Block::Air; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +struct MultiBlockChange { + static constexpr PacketId id = PacketId::MultiBlockChange; + struct Change { + uint16_t localIndex = 0; + Block block = Block::Air; + }; + int32_t chunkX = 0; + int32_t chunkY = 0; + int32_t chunkZ = 0; + std::vector changes; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +struct EntityMove { + static constexpr PacketId id = PacketId::EntityMove; + int32_t entityId = 0; + float x = 0, y = 0, z = 0; + float yaw = 0, pitch = 0; + bool onGround = false; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +struct PlayerState { + static constexpr PacketId id = PacketId::PlayerState; + float x = 0, y = 0, z = 0; + float yaw = 0, pitch = 0; + float health = 0; + int32_t food = 0; + int32_t xpLevel = 0; + uint8_t selectedSlot = 0; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +struct KeepAlive { + static constexpr PacketId id = PacketId::KeepAlive; + int64_t token = 0; + void encode(PacketWriter& w) const; + bool decode(PacketReader& r); +}; + +// Length-prefixed framing: varint(1 + payload) + packetId + payload. +void writeFramed(ByteWriter& stream, PacketId id, const PacketWriter& body); + +struct Frame { + PacketId id = PacketId::Handshake; + ByteBuffer payload; +}; + +// Pulls every complete frame out of a possibly-glued stream. `consumed` is set +// to the number of bytes fully parsed, so a trailing partial frame can be kept +// for the next read. +void splitFrames(const uint8_t* data, std::size_t size, std::vector& frames, + std::size_t& consumed); + +} // namespace vf