/******************************************************************** Minecraft: Pocket Edition - Decompilation Project Copyright (C) 2023 iProgramInCpp The following code is licensed under the BSD 1 clause license. SPDX-License-Identifier: BSD-1-Clause ********************************************************************/ #include "ServerSideNetworkHandler.hpp" // This lets you make the server shut up and not log events in the debug console. #define VERBOSE_SERVER #if defined(ORIGINAL_CODE) || defined(VERBOSE_SERVER) #define puts_ignorable(str) puts(str) #define printf_ignorable(str, ...) printf(str, __VA_ARGS__) #else #define puts_ignorable(str) #define printf_ignorable(str, ...) #endif ServerSideNetworkHandler::ServerSideNetworkHandler(Minecraft* minecraft, RakNetInstance* rakNetInstance) { m_pMinecraft = minecraft; m_pRakNetInstance = rakNetInstance; allowIncomingConnections(false); m_pRakNetPeer = m_pRakNetInstance->getPeer(); setupCommands(); } ServerSideNetworkHandler::~ServerSideNetworkHandler() { if (m_pLevel) m_pLevel->removeListener(this); for (auto plrKVP : m_onlinePlayers) delete plrKVP.second; m_onlinePlayers.clear(); } void ServerSideNetworkHandler::levelGenerated(Level* level) { m_pLevel = level; if (m_pMinecraft->m_pLocalPlayer) { m_pMinecraft->m_pLocalPlayer->m_guid = m_pRakNetPeer->GetMyGUID(); } level->addListener(this); allowIncomingConnections(m_pMinecraft->m_options.m_bServerVisibleDefault); m_onlinePlayers.insert_or_assign(m_pMinecraft->m_pLocalPlayer->m_guid, new OnlinePlayer(m_pMinecraft->m_pLocalPlayer, m_pMinecraft->m_pLocalPlayer->m_guid)); } void ServerSideNetworkHandler::onNewClient(const RakNet::RakNetGUID& guid) { printf_ignorable("onNewClient, client guid: %s\n", guid.ToString()); } void ServerSideNetworkHandler::onDisconnect(const RakNet::RakNetGUID& guid) { puts_ignorable("onDisconnect"); OnlinePlayer* pOnlinePlayer = getPlayerByGUID(guid); if (!pOnlinePlayer) return; Player* pPlayer = pOnlinePlayer->m_pPlayer; // erase it from the map m_onlinePlayers.erase(m_onlinePlayers.find(guid)); // it better be in our map // tell everyone that they left the game displayGameMessage(pPlayer->m_name + " disconnected from the game"); m_pRakNetInstance->send(new RemoveEntityPacket(pPlayer->m_EntityID)); // remove it from our world m_pLevel->removeEntity(pPlayer); // delete the online player's entry. delete pOnlinePlayer; } void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, LoginPacket* packet) { if (!m_bAllowIncoming) return; puts_ignorable("LoginPacket"); // if they're already online, fail if (getPlayerByGUID(guid)) { printf("That player is already in the world!\n"); return; } Player* pPlayer = new Player(m_pLevel); pPlayer->m_guid = guid; pPlayer->m_name = std::string(packet->m_str.C_String()); m_onlinePlayers.insert_or_assign(guid, new OnlinePlayer(pPlayer, guid)); StartGamePacket sgp; sgp.field_4 = m_pLevel->getSeed(); sgp.field_8 = m_pLevel->getLevelData()->field_20; sgp.field_C = pPlayer->m_EntityID; sgp.field_10 = pPlayer->m_pos.x; sgp.field_14 = pPlayer->m_pos.y - pPlayer->field_84; sgp.field_18 = pPlayer->m_pos.z; sgp.m_version = 1; sgp.m_time = m_pLevel->getTime(); RakNet::BitStream sgpbs; sgp.write(&sgpbs); m_pRakNetPeer->Send(&sgpbs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, guid, false); // send the connecting player info about all other players in the world for (Player* player : m_pLevel->m_players) { AddPlayerPacket app(player->m_guid, RakNet::RakString(player->m_name.c_str()), player->m_EntityID, player->m_pos.x, player->m_pos.y - player->field_84, player->m_pos.z); RakNet::BitStream appbs; app.write(&appbs); m_pRakNetPeer->Send(&appbs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, guid, false); } m_pLevel->addEntity(pPlayer); m_pMinecraft->m_gui.addMessage(pPlayer->m_name + " joined the game"); AddPlayerPacket app(guid, RakNet::RakString(pPlayer->m_name.c_str()), pPlayer->m_EntityID, pPlayer->m_pos.x, pPlayer->m_pos.y - pPlayer->field_84, pPlayer->m_pos.z); RakNet::BitStream appbs; app.write(&appbs); m_pRakNetPeer->Send(&appbs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, guid, true); } void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, MessagePacket* packet) { OnlinePlayer* pOP = getPlayerByGUID(guid); if (!pOP) { printf("MessagePacket: That jerk %s doesn't actually exist\n", guid.ToString()); return; } // don't let players send empty messages std::string msg(packet->m_str.C_String()); if (msg.empty()) return; if (msg[0] == '/') { printf("CMD: %s: %s\n", pOP->m_pPlayer->m_name.c_str(), msg.c_str()); std::stringstream ss(msg); ss.get(); // skip the / std::vector parms; std::string currparm, cmdname; // get cmd name std::getline(ss, cmdname, ' '); // get cmd parms while (std::getline(ss, currparm, ' ')) parms.push_back(currparm); CommandFunction func; CommandMap::iterator iter = m_commands.find(cmdname); if (iter == m_commands.end()) { sendMessage(pOP, "Unknown command. Type /? for a list of commands."); return; } func = iter->second; (this->*func)(pOP, parms); return; } printf("MSG: <%s> %s\n", pOP->m_pPlayer->m_name.c_str(), msg.c_str()); // send everyone the message std::string gameMessage = "<" + pOP->m_pPlayer->m_name + "> " + msg; displayGameMessage(gameMessage); } void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, MovePlayerPacket* packet) { //not in the original puts_ignorable("MovePlayerPacket"); Entity* pEntity = m_pLevel->getEntity(packet->m_id); if (pEntity) pEntity->lerpTo(packet->m_x, packet->m_y, packet->m_z, packet->m_yaw, packet->m_pitch, 3); redistributePacket(packet, guid); } void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, PlaceBlockPacket* packet) { puts_ignorable("PlaceBlockPacket"); Mob* pMob = (Mob*)m_pLevel->getEntity(packet->m_playerID); if (!pMob) return; TileID tile = packet->m_tile; int face = packet->m_face; int x = packet->m_x; int y = packet->m_y; int z = packet->m_z; if (!m_pLevel->mayPlace(tile, x, y, z, true)) return; if (m_pLevel->setTile(x, y, z, tile)) { Tile::tiles[tile]->setPlacedOnFace(m_pLevel, x, y, z, face); Tile::tiles[tile]->setPlacedBy(m_pLevel, x, y, z, pMob); const Tile::SoundType* pSound = Tile::tiles[tile]->m_pSound; m_pLevel->playSound(float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f, "step." + pSound->m_name, 0.5f * (pSound->field_18 + 1.0f), pSound->field_1C * 0.8f); } redistributePacket(packet, guid); } void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, RemoveBlockPacket* packet) { puts_ignorable("RemoveBlockPacket"); Player* pPlayer = (Player*)m_pLevel->getEntity(packet->m_playerID); if (!pPlayer) return; int x = packet->m_x; int y = packet->m_y; int z = packet->m_z; Tile* pTile = Tile::tiles[m_pLevel->getTile(x, y, z)]; int data = m_pLevel->getData(x, y, z); bool setTileResult = m_pLevel->setTile(x, y, z, TILE_AIR); if (pTile && setTileResult) { const Tile::SoundType* pSound = pTile->m_pSound; m_pMinecraft->m_pSoundEngine->play("step." + pSound->m_name, float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f, 0.5f * (pSound->field_18 + 1.0f), pSound->field_1C * 0.8f); // redistribute the packet only if needed redistributePacket(packet, guid); } } void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, PlayerEquipmentPacket* packet) { Player* pPlayer = (Player*)m_pLevel->getEntity(packet->m_playerID); if (!pPlayer) { LogMsg("No player with id %d", packet->m_playerID); return; } #ifndef ORIGINAL_CODE if (!Item::items[packet->m_itemID]) { LogMsg("That item %d doesn't actually exist!", packet->m_itemID); return; } #endif if (pPlayer->m_guid == m_pRakNetPeer->GetMyGUID()) { puts("Attempted to modify local player's inventory"); return; } pPlayer->m_pInventory->selectItemById(packet->m_itemID); pPlayer->m_pInventory->selectSlot(0); redistributePacket(packet, guid); } void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, RequestChunkPacket* packet) { puts_ignorable("RequestChunkPacket"); LevelChunk* pChunk = m_pLevel->getChunk(packet->m_x, packet->m_z); if (!pChunk) { LogMsg("No chunk at %d, %d", packet->m_x, packet->m_z); return; } // @NOTE: this allows the client to request empty chunks. Is that okay? ChunkDataPacket cdp(pChunk->m_chunkX, pChunk->m_chunkZ, pChunk); RakNet::BitStream bs; cdp.write(&bs); m_pRakNetPeer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, guid, false); } void ServerSideNetworkHandler::tileBrightnessChanged(int x, int y, int z) { } void ServerSideNetworkHandler::tileChanged(int x, int y, int z) { UpdateBlockPacket ubp; int tile = m_pLevel->getTile(x, y, z); int data = m_pLevel->getData(x, y, z); ubp.m_x = x; ubp.m_y = y; ubp.m_z = z; ubp.m_tile = uint8_t(tile); ubp.m_data = uint8_t(data); RakNet::BitStream bs; ubp.write(&bs); m_pRakNetPeer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::AddressOrGUID(), true); } void ServerSideNetworkHandler::allowIncomingConnections(bool b) { if (b) { m_pRakNetInstance->announceServer(m_pMinecraft->m_options.m_playerName); } else { m_pRakNetInstance->announceServer(""); } m_bAllowIncoming = b; } void ServerSideNetworkHandler::displayGameMessage(const std::string& msg) { m_pMinecraft->m_gui.addMessage(msg); m_pRakNetInstance->send(new MessagePacket(msg)); } void ServerSideNetworkHandler::sendMessage(const RakNet::RakNetGUID& guid, const std::string& msg) { if (m_pRakNetPeer->GetMyGUID() == guid) { m_pMinecraft->m_gui.addMessage(msg); return; } m_pRakNetInstance->send(guid, new MessagePacket(msg)); } void ServerSideNetworkHandler::sendMessage(OnlinePlayer* player, const std::string& msg) { sendMessage(player->m_guid, msg); } void ServerSideNetworkHandler::redistributePacket(Packet* packet, const RakNet::RakNetGUID& source) { RakNet::BitStream bs; packet->write(&bs); m_pRakNetPeer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, source, true); } OnlinePlayer* ServerSideNetworkHandler::getPlayerByGUID(const RakNet::RakNetGUID& guid) { auto iter = m_onlinePlayers.find(guid); if (iter == m_onlinePlayers.end()) return nullptr; return iter->second; } ///////////////// In-Game Commands ///////////////// void ServerSideNetworkHandler::setupCommands() { m_commands["?"] = &ServerSideNetworkHandler::commandHelp; m_commands["help"] = &ServerSideNetworkHandler::commandHelp; m_commands["stats"] = &ServerSideNetworkHandler::commandStats; m_commands["time"] = &ServerSideNetworkHandler::commandTime; } void ServerSideNetworkHandler::commandHelp(OnlinePlayer* player, const std::vector& parms) { std::stringstream ss; ss << ">> Available commands:"; for (CommandMap::iterator it = m_commands.begin(); it != m_commands.end(); ++it) { ss << " /" << it->first; } sendMessage(player, ss.str()); } void ServerSideNetworkHandler::commandStats(OnlinePlayer* player, const std::vector& parms) { if (!m_pLevel) return; std::stringstream ss; ss << "Server uptime: " << getTimeS() << " seconds. Host's name: " << m_pMinecraft->m_pUser->field_0; sendMessage(player, ss.str()); } void ServerSideNetworkHandler::commandTime(OnlinePlayer* player, const std::vector& parms) { if (!m_pLevel) return; std::stringstream ss; ss << "In-game time: "; ss << m_pLevel->getTime(); sendMessage(player, ss.str()); }