#include "DedicatedServer.h" #include "config/ServerProperties.h" #include "data/Registry.h" #include "PacketReader.h" #include #include using std::string; using Feather::Config::ServerProperties; namespace Feather { DedicatedServer::DedicatedServer(ServerProperties* properties) : m_properties(properties), m_listener(properties->serverPort.GetValue(), this), m_status() { m_status.descriptionText = properties->motd.GetValue(); m_status.maxPlayers = properties->maxPlayers; Registry::Init(); m_worldManager.LoadWorld(m_properties->levelName); while (1) { auto [clients, clientsLock] = m_clients.borrow(); for (auto& client : clients) { auto [data, clientLock] = client.GetTCPClient()->GetData().borrow(); if (data.empty()) continue; uint32_t offset = 0; while (offset != data.size()) { auto reader = PacketReader(&data[offset]); Protocol::ProcessPacket(*this, client, reader, client.GetContext().GetState()); offset += reader.Size(); } data.clear(); } // TODO: Move to tick thread !! { using namespace Protocol; using namespace std::chrono; // TODO: high_resolution_clock on mingw is innacurate and slow static auto lastKeepAlive = high_resolution_clock::now(); auto now = high_resolution_clock::now(); milliseconds td = duration_cast(now - lastKeepAlive); if (td >= 1000ms) { lastKeepAlive = now; Play::ClientboundKeepAlive keepAlive = { // MC probably sends time since epoch .id = duration_cast(now.time_since_epoch()).count(), }; for (auto &client : clients) { client.SendMessage(keepAlive); } } } } } DedicatedServer::~DedicatedServer() { } void DedicatedServer::OnClientConnect(Network::TCPClientHandle&& client) { const auto& address = client->GetAddress(); Log::Info("New connection from {}:{}", address.ip, address.port); auto [clients, lock] = m_clients.borrow(); clients.emplace_back(std::move(client)); } void DedicatedServer::OnClientDisconnect(const Network::TCPClient* client) { const auto& address = client->GetAddress(); Log::Info("Disconnected from {}:{}", address.ip, address.port); auto [clients, lock] = m_clients.borrow(); clients.remove_if([&](MinecraftClient& other) { return other.GetTCPClient().get() == client; }); } using namespace Protocol; void DedicatedServer::HandleUnknownPacket(MinecraftClient &client, int32_t id, const PacketReader &packet) { Log::Trace("Got unknown packet with ID {} from client.", id); } void DedicatedServer::HandleLegacyPing(MinecraftClient& client) { Log::Info("Got legacy server list ping."); } #pragma region Handshake & Status template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Handholding::ServerboundHandshake& handshake) { Log::Info("Client Intention Packet: version={}, serverIp={}, port={}, intention={}\n", handshake.protocolVersion, handshake.serverIP.data(), handshake.port, handshake.intention ); client.GetContext().SetState(handshake.intention); } template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Status::ServerboundRequest& request) { Log::Info("Client sent STATUS_PING_REQUEST"); Status::ClientboundResponse message = { .jsonResponse = m_status.GetServerStatusJSON() }; client.SendMessage(message); } template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Status::ServerboundPing& ping) { Log::Info("Client sent STATUS_PING: {}", ping.timestamp); Status::ClientboundPong message = { .timestamp = ping.timestamp }; client.SendMessage(message); } #pragma endregion template <> void DedicatedServer::HandlePacket(MinecraftClient& client,const Login::ServerboundStart& start) { //Login::ClientboundSuccess success = //{ // .uuid = { 4658857991808325907ull, 7518717155607718277ull }, // .username = start.username //}; // TODO: This is copying... client.SetUsername(std::string(start.username)); std::string uuid = "ecb99913-96a8-40a7-8529-a2ca6ad95768"; Login::ClientboundSuccess success = { .uuid = uuid, .username = start.username }; client.SendMessage(success); client.GetContext().SetState(ProtocolState::Play); std::string type = "default"; Play::ClientboundJoinGame join = { .entityId = 0, .gamemode = 0, .dimension = 0, .seedHash = 0, .maxPlayers = uint8_t(m_properties->maxPlayers.GetValue()), .levelType = type, .viewDistance = m_properties->viewDistance, .reducedDebugInfo = false, .enableRespawnScreen = true }; client.SendMessage(join); Play::ClientboundSpawnPosition spawnPos = { .location = BlockPos(0, 64, 0), }; client.SendMessage(spawnPos); // SEND CHUNKS auto levelData = m_worldManager.GetOverworld()->GetLevelData(); ChunkPos spawnChunk = BlockPos(levelData.spawnX, levelData.spawnY, levelData.spawnZ); int32_t viewDistance = m_properties->viewDistance; for (int32_t x = spawnChunk.x - viewDistance; x < spawnChunk.x + viewDistance; x++) { for (int32_t z = spawnChunk.z - viewDistance; z < spawnChunk.z + viewDistance; z++) { NetworkMessage chunkData(5KB); // Packet ID chunkData.WriteVarInt(0x22); // Chunk X, Z chunkData.Write(x); chunkData.Write(z); // Full chunk chunkData.Write(true); Chunk* chunk = m_worldManager.GetOverworld()->GetChunk({ x, z }); if (!chunk) continue; NBT::CompoundTag* chunkNBT = chunk->nbt.get(); int32_t sectionsBits = 0; NetworkMessage sections(3KB); for (int i = 0; i < Chunk::NUM_SECTIONS; i++) { const ChunkSection& sect = chunk->sections[i]; //Log::Trace("Chunk: Got Section {} with local palette size {}", i, sect.localPalette.Size()); sectionsBits |= 1 << i; // Block Count: fudge sections.Write(1024); const uint8_t bitsPerBlock = sect.blockStates.bits; sections.Write(bitsPerBlock); const size_t dataLength = ChunkSection::NUM_BLOCKS * bitsPerBlock / bitsizeof(uint64); if (bitsPerBlock <= 8) { // Palette sections.WriteVarInt(sect.blockStates.palette.Size()); for (size_t j = 0; j < sect.blockStates.palette.Size(); j++) sections.WriteVarInt(sect.blockStates.palette.ByID(j)); } // Block State Data sections.WriteVarInt(dataLength); { auto [data, lock] = sect.blockStates.data.borrow(); // Can't just copy because endian-ness! for (size_t i = 0; i < dataLength; i++) sections.Write(data[i]); } } //Log::Trace("Section bits: {:#b}", sectionsBits); chunkData.WriteVarInt(sectionsBits); // Heightmaps // TODO: Use chunk->heightmaps NBT::DataBuffer buf = chunkNBT->Get("Heightmaps").GetData(); chunkData.WriteData(buf.data, buf.length); // Biomes NBT::IntArrayTag biomes = chunkNBT->Get("Biomes"); for (uint32_t i = 0; i < 1024; i++) chunkData.Write(biomes[i]); sections.Finalize(); chunkData.WriteSubMessage(sections); // Block Ents chunkData.WriteVarInt(0); chunkData.Finalize(); client.SendMessage(chunkData); } } // UPDATE POSITION using RelativeFlags = Play::ClientboundPlayerPositionAndLook::RelativeFlags; Play::ClientboundPlayerPositionAndLook playerPos = { .x = double(levelData.spawnX), .y = double(levelData.spawnY), .z = double(levelData.spawnZ), .xRot = 0, .yRot = 0, .flags = RelativeFlags(0), .id = 0, }; client.SendMessage(playerPos); } template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Play::ServerboundKeepAlive& keepAlive) { // TODO: check these and kick clients that don't send em right or at all } template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Play::ServerboundChatMessage& chat) { Log::Info("{}: {}", client.GetUsername(), chat.message); std::string json = fmt::format(R"({{ "text": "<{}> {}" }})", client.GetUsername(), chat.message); Play::ClientboundChatMessage message = { .jsonData = json, .position = 0 }; // m_clients is already locked for us! auto& clients = m_clients.steal(); for (auto& client : clients) client.SendMessage(message); } template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Play::ServerboundPlayerAction& action) { using Action = Play::ServerboundPlayerAction::Action; switch (action.action) { case Action::StartBreakBlock: break; case Action::AbortBreakBlock: break; case Action::FinishedBreakBlock: m_worldManager.GetOverworld()->SetBlock(action.pos, World::GLOBAL_PALETTE.GetDefault()); break; case Action::DropItemStack: break; case Action::DropItem: break; case Action::ReleaseUseItem: break; case Action::SwapItemHands: break; } } }