diff --git a/core/src/Protocol.cpp b/core/src/Protocol.cpp new file mode 100644 index 0000000..cef48d0 --- /dev/null +++ b/core/src/Protocol.cpp @@ -0,0 +1,329 @@ +#include "vf/Protocol.h" + +namespace vf { + +void PacketWriter::writePosition(int32_t x, int32_t y, int32_t z) { + uint64_t packed = ((static_cast(x) & 0x3FFFFFFull) << 38) | + ((static_cast(z) & 0x3FFFFFFull) << 12) | + (static_cast(y) & 0xFFFull); + writer.putU64(packed); +} + +void PacketWriter::writeUuid(uint64_t hi, uint64_t lo) { + writer.putU64(hi); + writer.putU64(lo); +} + +namespace { + +int32_t signExtend(uint64_t value, int bits) { + uint64_t signBit = 1ull << (bits - 1); + if (value & signBit) value |= ~((1ull << bits) - 1ull); + return static_cast(static_cast(value)); +} + +} // namespace + +bool PacketReader::readI32(int32_t& out) { + uint32_t raw; + if (!reader.getU32(raw)) return false; + out = static_cast(raw); + return true; +} + +bool PacketReader::readI64(int64_t& out) { + uint64_t raw; + if (!reader.getU64(raw)) return false; + out = static_cast(raw); + return true; +} + +bool PacketReader::readBool(bool& out) { + uint8_t raw; + if (!reader.getU8(raw)) return false; + out = raw != 0; + return true; +} + +bool PacketReader::readBytes(std::vector& out, std::size_t n) { + out.resize(n); + return reader.getBytes(out.data(), n); +} + +bool PacketReader::readPosition(int32_t& x, int32_t& y, int32_t& z) { + uint64_t packed; + if (!reader.getU64(packed)) return false; + x = signExtend(packed >> 38, 26); + z = signExtend((packed >> 12) & 0x3FFFFFFull, 26); + y = signExtend(packed & 0xFFFull, 12); + return true; +} + +bool PacketReader::readUuid(uint64_t& hi, uint64_t& lo) { + return reader.getU64(hi) && reader.getU64(lo); +} + +void Handshake::encode(PacketWriter& w) const { + w.writeVarInt(protocolVersion); + w.writeString(serverAddress); + w.writeU16(serverPort); + w.writeVarInt(nextState); +} + +bool Handshake::decode(PacketReader& r) { + return r.readVarInt(protocolVersion) && r.readString(serverAddress) && + r.readU16(serverPort) && r.readVarInt(nextState); +} + +void LoginStart::encode(PacketWriter& w) const { + w.writeString(name); + w.writeUuid(uuidHi, uuidLo); +} + +bool LoginStart::decode(PacketReader& r) { + return r.readString(name) && r.readUuid(uuidHi, uuidLo); +} + +void JoinGame::encode(PacketWriter& w) const { + w.writeVarInt(entityId); + w.writeU8(gameMode); + w.writeVarInt(dimension); + w.writeI64(static_cast(seedHash)); + w.writeU8(maxPlayers); + w.writeString(levelType); + w.writeVarInt(viewDistance); + w.writeBool(reducedDebugInfo); +} + +bool JoinGame::decode(PacketReader& r) { + int64_t seed; + if (!r.readVarInt(entityId) || !r.readU8(gameMode) || !r.readVarInt(dimension) || + !r.readI64(seed) || !r.readU8(maxPlayers) || !r.readString(levelType) || + !r.readVarInt(viewDistance) || !r.readBool(reducedDebugInfo)) + return false; + seedHash = static_cast(seed); + return true; +} + +void ChunkData::setFromChunk(int32_t cx, int32_t cy, int32_t cz, const Chunk& chunk) { + chunkX = cx; + chunkY = cy; + chunkZ = cz; + palette.clear(); + std::array(Block::Count)> indexOf; + indexOf.fill(-1); + std::vector indices; + indices.reserve(chunkVolume); + for (Block block : chunk.raw()) { + int& slot = indexOf[static_cast(block)]; + if (slot < 0) { + slot = static_cast(palette.size()); + palette.push_back(block); + } + indices.push_back(static_cast(slot)); + } + bitWidth = 1; + while ((static_cast(1) << bitWidth) < palette.size()) ++bitWidth; + + int perWord = 64 / bitWidth; + std::size_t wordCount = (indices.size() + static_cast(perWord) - 1) / + static_cast(perWord); + packed.assign(wordCount, 0); + uint64_t mask = bitWidth >= 64 ? ~0ull : ((1ull << bitWidth) - 1ull); + for (std::size_t i = 0; i < indices.size(); ++i) { + std::size_t word = i / static_cast(perWord); + int inWord = static_cast(i % static_cast(perWord)); + packed[word] |= (static_cast(indices[i]) & mask) << (inWord * bitWidth); + } +} + +void ChunkData::toChunk(Chunk& out) const { + int perWord = 64 / bitWidth; + uint64_t mask = bitWidth >= 64 ? ~0ull : ((1ull << bitWidth) - 1ull); + std::array& blocks = out.raw(); + for (std::size_t i = 0; i < blocks.size(); ++i) { + std::size_t word = i / static_cast(perWord); + int inWord = static_cast(i % static_cast(perWord)); + uint32_t index = word < packed.size() + ? static_cast((packed[word] >> (inWord * bitWidth)) & mask) + : 0; + blocks[i] = index < palette.size() ? palette[index] : Block::Air; + } +} + +void ChunkData::encode(PacketWriter& w) const { + w.writeVarInt(chunkX); + w.writeVarInt(chunkY); + w.writeVarInt(chunkZ); + + ByteWriter raw; + raw.putVarU32(static_cast(palette.size())); + for (Block block : palette) raw.putU8(static_cast(block)); + raw.putU8(static_cast(bitWidth)); + raw.putVarU32(static_cast(packed.size())); + for (uint64_t word : packed) raw.putU64(word); + + ByteBuffer compressed = compress(raw.bytes()); + w.writeVarUint(static_cast(compressed.size())); + w.writeBytes(compressed.data(), compressed.size()); +} + +bool ChunkData::decode(PacketReader& r) { + if (!r.readVarInt(chunkX) || !r.readVarInt(chunkY) || !r.readVarInt(chunkZ)) return false; + uint32_t compressedLen; + if (!r.readVarUint(compressedLen)) return false; + std::vector compressed; + if (!r.readBytes(compressed, compressedLen)) return false; + ByteBuffer raw; + if (!decompress(compressed.data(), compressed.size(), raw)) return false; + + ByteReader body(raw.data(), raw.size()); + uint32_t paletteSize; + if (!body.getVarU32(paletteSize)) return false; + palette.clear(); + palette.reserve(paletteSize); + for (uint32_t i = 0; i < paletteSize; ++i) { + uint8_t block; + if (!body.getU8(block)) return false; + palette.push_back(static_cast(block)); + } + uint8_t width; + if (!body.getU8(width) || width < 1 || width > 64) return false; + bitWidth = width; + uint32_t wordCount; + if (!body.getVarU32(wordCount)) return false; + packed.clear(); + packed.reserve(wordCount); + for (uint32_t i = 0; i < wordCount; ++i) { + uint64_t word; + if (!body.getU64(word)) return false; + packed.push_back(word); + } + return true; +} + +void BlockChange::encode(PacketWriter& w) const { + w.writePosition(x, y, z); + w.writeVarUint(static_cast(block)); +} + +bool BlockChange::decode(PacketReader& r) { + uint32_t rawBlock; + if (!r.readPosition(x, y, z) || !r.readVarUint(rawBlock)) return false; + block = static_cast(rawBlock); + return true; +} + +void MultiBlockChange::encode(PacketWriter& w) const { + w.writeVarInt(chunkX); + w.writeVarInt(chunkY); + w.writeVarInt(chunkZ); + w.writeVarUint(static_cast(changes.size())); + for (const Change& change : changes) { + w.writeU16(change.localIndex); + w.writeU8(static_cast(change.block)); + } +} + +bool MultiBlockChange::decode(PacketReader& r) { + if (!r.readVarInt(chunkX) || !r.readVarInt(chunkY) || !r.readVarInt(chunkZ)) return false; + uint32_t count; + if (!r.readVarUint(count)) return false; + changes.clear(); + changes.reserve(count); + for (uint32_t i = 0; i < count; ++i) { + Change change; + uint8_t block; + if (!r.readU16(change.localIndex) || !r.readU8(block)) return false; + change.block = static_cast(block); + changes.push_back(change); + } + return true; +} + +void EntityMove::encode(PacketWriter& w) const { + w.writeVarInt(entityId); + w.writeFloat(x); + w.writeFloat(y); + w.writeFloat(z); + w.writeFloat(yaw); + w.writeFloat(pitch); + w.writeBool(onGround); +} + +bool EntityMove::decode(PacketReader& r) { + return r.readVarInt(entityId) && r.readFloat(x) && r.readFloat(y) && r.readFloat(z) && + r.readFloat(yaw) && r.readFloat(pitch) && r.readBool(onGround); +} + +void PlayerState::encode(PacketWriter& w) const { + w.writeFloat(x); + w.writeFloat(y); + w.writeFloat(z); + w.writeFloat(yaw); + w.writeFloat(pitch); + w.writeFloat(health); + w.writeVarInt(food); + w.writeVarInt(xpLevel); + w.writeU8(selectedSlot); +} + +bool PlayerState::decode(PacketReader& r) { + return r.readFloat(x) && r.readFloat(y) && r.readFloat(z) && r.readFloat(yaw) && + r.readFloat(pitch) && r.readFloat(health) && r.readVarInt(food) && + r.readVarInt(xpLevel) && r.readU8(selectedSlot); +} + +void KeepAlive::encode(PacketWriter& w) const { w.writeVarLong(token); } + +bool KeepAlive::decode(PacketReader& r) { return r.readVarLong(token); } + +void writeFramed(ByteWriter& stream, PacketId id, const PacketWriter& body) { + uint32_t frameLength = static_cast(1 + body.size()); + stream.putVarU32(frameLength); + stream.putU8(static_cast(id)); + stream.putBytes(body.bytes().data(), body.bytes().size()); +} + +namespace { + +bool peekVarU32(const uint8_t* data, std::size_t size, std::size_t pos, uint32_t& value, + std::size_t& next) { + value = 0; + int shift = 0; + while (shift < 35) { + if (pos >= size) return false; + uint8_t byte = data[pos++]; + value |= static_cast(byte & 0x7Fu) << shift; + if ((byte & 0x80u) == 0) { + next = pos; + return true; + } + shift += 7; + } + return false; +} + +} // namespace + +void splitFrames(const uint8_t* data, std::size_t size, std::vector& frames, + std::size_t& consumed) { + frames.clear(); + std::size_t pos = 0; + for (;;) { + std::size_t afterLength = 0; + uint32_t frameLength = 0; + if (!peekVarU32(data, size, pos, frameLength, afterLength)) break; + if (frameLength == 0) break; + if (afterLength + frameLength > size) break; + + Frame frame; + frame.id = static_cast(data[afterLength]); + frame.payload.assign(data + afterLength + 1, data + afterLength + frameLength); + frames.push_back(std::move(frame)); + pos = afterLength + frameLength; + } + consumed = pos; +} + +} // namespace vf