/******************************************************************** 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" #include "common/Utils.hpp" #include "world/entity/TripodCamera.hpp" #include "world/entity/PrimedTnt.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) LOG_I(str) #define printf_ignorable(str, ...) LOG_I(str, __VA_ARGS__) #else #define puts_ignorable(str) #define printf_ignorable(str, ...) #endif ServerSideNetworkHandler::ServerSideNetworkHandler(Minecraft* minecraft, RakNetInstance* rakNetInstance) { m_pMinecraft = minecraft; m_pLevel = nullptr; m_pRakNetInstance = rakNetInstance; allowIncomingConnections(false); m_pRakNetPeer = m_pRakNetInstance->getPeer(); m_bAllowIncoming = false; setupCommands(); } ServerSideNetworkHandler::~ServerSideNetworkHandler() { if (m_pLevel) m_pLevel->removeListener(this); for (OnlinePlayerMap::iterator it = m_onlinePlayers.begin(); it != m_onlinePlayers.end(); ++it) delete it->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->getOptions()->m_bServerVisibleDefault); m_onlinePlayers[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", 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)) { LOG_E("That player is already in the world!"); return; } Player* pPlayer = new Player(m_pLevel); pPlayer->m_guid = guid; pPlayer->m_name = std::string(packet->m_str.C_String()); m_onlinePlayers[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 = 2; sgp.m_time = m_pLevel->getTime(); RakNet::BitStream *sgpbs = nullptr; 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 (int i = 0; i < int(m_pLevel->m_players.size()); i++) { Player* player = m_pLevel->m_players[i]; 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); if (m_pMinecraft->m_pGameMode->isCreativeType()) pPlayer->m_pInventory->prepareCreativeInventory(); else pPlayer->m_pInventory->prepareSurvivalInventory(); m_pMinecraft->m_pGui->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) { LOG_W("MessagePacket: That jerk %s doesn't actually exist", 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] == '/') { LOG_I("CMD: %s: %s", 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; } LOG_I("MSG: <%s> %s", 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) { 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; printf_ignorable("PlaceBlockPacket: %d", tile); 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) { LOG_W("No player with id %d", packet->m_playerID); return; } #ifndef ORIGINAL_CODE if (!Item::items[packet->m_itemID]) { LOG_W("That item %d doesn't actually exist!", packet->m_itemID); return; } #endif if (pPlayer->m_guid == m_pRakNetPeer->GetMyGUID()) { LOG_W("Attempted to modify local player's inventory"); return; } pPlayer->m_pInventory->selectItemById(packet->m_itemID, C_MAX_HOTBAR_ITEMS); redistributePacket(packet, guid); } void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, RequestChunkPacket* packet) { puts_ignorable("RequestChunkPacket"); if (packet->m_x == -9999) { m_pRakNetInstance->send(guid, new LevelDataPacket(m_pLevel)); return; } LevelChunk* pChunk = m_pLevel->getChunk(packet->m_x, packet->m_z); if (!pChunk) { LOG_E("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->getOptions()->m_playerName); } else { m_pRakNetInstance->announceServer(""); } m_bAllowIncoming = b; } void ServerSideNetworkHandler::displayGameMessage(const std::string& msg) { m_pMinecraft->m_pGui->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_pGui->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) { OnlinePlayerMap::iterator 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; m_commands["seed"] = &ServerSideNetworkHandler::commandSeed; m_commands["tp"] = &ServerSideNetworkHandler::commandTP; m_commands["summon"] = &ServerSideNetworkHandler::commandSummon; } static inline float convertCoord(const std::string value, float base) { float val; std::stringstream ss(value); if (ss.peek() == '~') { ss.ignore(); ss >> val; val += base; } else { ss >> val; } return val; } static inline Vec3 convertCoords(const std::vector value, int offset, Vec3 pos) { Vec3 coord; coord.x = convertCoord(value[0 + offset], pos.x); coord.y = convertCoord(value[1 + offset], pos.y); coord.z = convertCoord(value[2 + offset], pos.z); return coord; } 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.\n"; ss << "Host's name: " << m_pMinecraft->m_pUser->field_0 << "\n"; int nPlayers = int(m_onlinePlayers.size()); if (nPlayers == 1) ss << "There is 1 player online."; else ss << "There are " << nPlayers << " players online."; sendMessage(player, ss.str()); } void ServerSideNetworkHandler::commandTime(OnlinePlayer* player, const std::vector& parms) { if (!m_pLevel) return; if (!parms.empty()) { int t = 0; if (!sscanf(parms[0].c_str(), "%d", &t)) { sendMessage(player, "Usage: /time [new time value]"); return; } m_pLevel->setTime(t); sendMessage(player, "Time has been set to " + parms[0]); return; } std::stringstream ss; ss << "In-game time: "; ss << m_pLevel->getTime(); ss << ". Day " << m_pLevel->getTime() / 24000; sendMessage(player, ss.str()); } void ServerSideNetworkHandler::commandSeed(OnlinePlayer* player, const std::vector& parms) { if (!m_pLevel) return; std::stringstream ss; ss << "World generation seed: "; ss << m_pLevel->getSeed(); sendMessage(player, ss.str()); } void ServerSideNetworkHandler::commandTP(OnlinePlayer* player, const std::vector& parms) { if (!m_pLevel) return; if (parms.size() != 3) { sendMessage(player, "Usage: /tp "); return; } if (player->m_pPlayer != this->m_pMinecraft->m_pLocalPlayer) { sendMessage(player, "Sorry, only the host can use this command at the moment"); return; } Vec3 playerPos = player->m_pPlayer->getPos(1.0f); Vec3 pos = convertCoords(parms, 0, playerPos); std::stringstream ss; ss << "Teleported to " << pos.x << ", " << pos.y << ", " << pos.z; player->m_pPlayer->setPos(pos.x, pos.y, pos.z); sendMessage(player, ss.str()); } void ServerSideNetworkHandler::commandSummon(OnlinePlayer* player, const std::vector& parms) { if (!m_pLevel) return; if (parms.size() < 4) { sendMessage(player, "Usage: /summon "); return; } std::string entity = parms[0]; Entity *ent; Vec3 playerPos = player->m_pPlayer->getPos(1.0f); Vec3 pos; if (parms.size() > 1) { pos = convertCoords(parms, 1, playerPos); } std::stringstream ss; if (entity == "camera") { TripodCamera *cam = new TripodCamera(m_pLevel, player->m_pPlayer, pos.x, pos.y, pos.z); if (parms.size() > 4) cam->m_iTimer = std::stoi(parms[4]) * 20; ent = cam; } else if (entity == "tnt") { PrimedTnt *tnt = new PrimedTnt(m_pLevel, pos.x, pos.y, pos.z); if (parms.size() > 4) tnt->m_fuseTimer = std::stoi(parms[4]) * 20; else tnt->m_fuseTimer = 0; // as in original ent = tnt; } if (!ent) { ss << "Unknown entity " << '"' << entity << '"'; sendMessage(player, ss.str()); return; } m_pLevel->addEntity(ent); ss << "Summoned " << entity << " at " << pos.x << ", " << pos.y << ", " << pos.z; sendMessage(player, ss.str()); }