From 5a9a3e380b935438afaf70388f66dbf55d6a9136 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sat, 26 Sep 2026 23:34:47 -0500 Subject: [PATCH] refactor: master packets as structs Every MASTER service packet is now an LUBitStream struct (docs/PacketArchitecture.md, PR 14) and the master server's switch is a dispatch map (PacketDispatcher), as are the master handlers of the world, chat and dashboard servers. - dNet/MasterPackets.h: RequestZoneTransfer, RequestZoneTransferResponse, ServerInfo, RequestSessionKey, SetSessionKey, SessionKeyResponse, NewSessionAlert, PlayerAdded / PlayerRemoved, CreatePrivateZone, RequestPrivateZone (passwords still cut to 50 characters when read), WorldReady, WorldReadyInfo (WORLD_READY to the dashboard), PrepZone, Shutdown, ShutdownResponse, WorldShutDown (SHUTDOWN_RESPONSE to the dashboard), ShutdownUniverse, AffirmTransferRequest/Response, RequestServerList, ServerListResponse, DashboardShutdown, ConfigReload, InstanceShutdown. The Send* functions are gone; MasterPackets::SendToMaster(msg) and SendTo(sysAddr, msg) send a struct. - The dashboard and instance migration structs (PlayerAction, DataChanged, Dashboard messages, MessageCapture, InstanceMigration) are LUBitStreams of the MASTER service now and moved to dNet/master/, included by MasterPackets.h. Their payloads are unchanged; master forwards them by re-serializing the struct instead of copying raw bytes. - InstanceManager, ZoneInstanceManager, MigrationCoordinator, dServer (server info, zone transfer response), auth (SET_SESSION_KEY), the world (session keys, player added and removed, world ready, shutdown response, affirmations, prep zone, shutdown universe) and the dashboard (server list, instance shutdown, config reload, announcements, player actions, message capture) send and read structs. - The login stamps are a `stamps` field of RequestZoneTransfer and RequestZoneTransferResponse (read leniently as before: a message without them reads as empty); master adds its stamps in the REQUEST_ZONE_TRANSFER handler and when it answers, as it did. - InstanceManager::GetInstanceBySysAddr takes a const address. Verified: tests/dGameTests/dNetTests/Legacy/MasterPacketsLegacy.h is a verbatim copy of the old writers and readers; MasterPacketsTests requires identical bytes for a grid of inputs, checks the old readers read what the structs write, round trips and truncation, hand written golden packets, that zone transfers without stamps still read, that the dashboard/migration structs write what "header + Serialize" wrote, and that the dispatcher drops truncated packets. No wire bytes changed. Co-Authored-By: Claude Opus 5.5 --- dChatServer/ChatServer.cpp | 30 +- dDashboardServer/DashboardServer.cpp | 350 +++--- dDashboardServer/routes/AccountModeration.cpp | 2 +- dDashboardServer/routes/Announcements.cpp | 2 +- dDashboardServer/routes/CharacterTools.cpp | 2 +- dDashboardServer/routes/ChatRoutes.cpp | 2 +- dDashboardServer/routes/EventsCalendar.cpp | 2 +- dDashboardServer/routes/Inspector.cpp | 8 +- dDashboardServer/routes/InspectorFormat.h | 2 +- dDashboardServer/routes/InstanceLoad.cpp | 5 +- dDashboardServer/routes/LiveWorld.cpp | 26 +- dDashboardServer/routes/MissionTools.cpp | 2 +- dDashboardServer/routes/ModerationTools.cpp | 2 +- dDashboardServer/routes/PlayerActions.cpp | 6 +- dDashboardServer/routes/PlayerActions.h | 2 +- dDashboardServer/routes/SettingsRoutes.cpp | 5 +- dDashboardServer/routes/VanityRoutes.cpp | 2 +- dDashboardServer/routes/WSRoutes.cpp | 2 +- dDashboardServer/routes/WorldView.cpp | 2 +- .../RocketLaunchpadControlComponent.cpp | 8 +- dGame/dGameMessages/GameMessageDecoder.cpp | 2 +- dGame/dUtilities/DashboardActions.cpp | 2 +- dGame/dUtilities/DashboardNotify.cpp | 15 +- dGame/dUtilities/MessageInspector.cpp | 8 +- .../SlashCommands/DEVGMCommands.cpp | 5 +- dGame/dUtilities/WorldMigration.cpp | 16 +- dGame/dUtilities/WorldMigration.h | 2 +- dMasterServer/InstanceManager.cpp | 66 +- dMasterServer/InstanceManager.h | 2 +- dMasterServer/MasterServer.cpp | 1032 +++++++---------- dMasterServer/MigrationCoordinator.cpp | 16 +- dMasterServer/MigrationCoordinator.h | 4 +- dNet/AuthPackets.cpp | 10 +- dNet/MasterPackets.cpp | 345 ++++-- dNet/MasterPackets.h | 282 ++++- dNet/ZoneInstanceManager.cpp | 46 +- dNet/ZoneInstanceManager.h | 6 +- dNet/dServer.cpp | 20 +- dNet/{ => master}/DashboardMessages.h | 18 +- dNet/{ => master}/DataChanged.h | 10 +- dNet/{ => master}/InstanceMigration.h | 34 +- dNet/{ => master}/MessageCapture.h | 18 +- dNet/{ => master}/PlayerAction.h | 18 +- dWorldServer/WorldServer.cpp | 184 ++- docs/PacketArchitecture.md | 3 + tests/dCommonTests/InstanceMigrationTests.cpp | 2 +- tests/dGameTests/dNetTests/CMakeLists.txt | 1 + .../dNetTests/CommonAuthPacketsTests.cpp | 16 +- .../dNetTests/Legacy/MasterPacketsLegacy.h | 392 +++++++ .../dNetTests/MasterPacketsTests.cpp | 472 ++++++++ tests/dWebTests/AlertsAndTwoFactorTests.cpp | 6 +- tests/dWebTests/MessageInspectorTests.cpp | 2 +- tests/dWebTests/PlayerActionTests.cpp | 4 +- tests/dWebTests/SecurityFixesTests.cpp | 2 +- 54 files changed, 2275 insertions(+), 1248 deletions(-) rename dNet/{ => master}/DashboardMessages.h (84%) rename dNet/{ => master}/DataChanged.h (83%) rename dNet/{ => master}/InstanceMigration.h (93%) rename dNet/{ => master}/MessageCapture.h (93%) rename dNet/{ => master}/PlayerAction.h (89%) create mode 100644 tests/dGameTests/dNetTests/Legacy/MasterPacketsLegacy.h create mode 100644 tests/dGameTests/dNetTests/MasterPacketsTests.cpp diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index a38ccb1ec..d780fb910 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -11,7 +11,7 @@ #include "Database.h" #include "dConfig.h" #include "dChatFilter.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "MessageType/Master.h" #include "Diagnostics.h" #include "AssetManager.h" @@ -273,20 +273,26 @@ namespace { }(); return handlers; } + + // Messages from master that dServer doesn't handle itself + const PacketDispatcher& MasterHandlers() { + static const auto handlers = [] { + PacketDispatcher handlers; + handlers.On(MessageType::Master::PLAYER_ACTION, [](const PlayerActionRequest& request, const SystemAddress&) { + // Words added or removed on the dashboard: web chat is checked with the same filter as the worlds + if (request.action == ePlayerAction::RELOAD_CHAT_FILTER && Game::chatFilter) { + Game::chatFilter->ReloadCustomWords(); + LOG("Reloaded the chat filter's words (changed on the dashboard)"); + } + }); + return handlers; + }(); + return handlers; + } } -// Messages from master that dServer doesn't handle itself void HandleMasterPacket(Packet* packet) { - if (packet->length < 4 || static_cast(packet->data[1]) != ServiceType::MASTER) return; - if (static_cast(packet->data[3]) != MessageType::Master::PLAYER_ACTION) return; - CINSTREAM_SKIP_HEADER; - PlayerActionRequest request; - if (!request.Deserialize(inStream)) return; - // Words added or removed on the dashboard: web chat is checked with the same filter as the worlds - if (request.action == ePlayerAction::RELOAD_CHAT_FILTER && Game::chatFilter) { - Game::chatFilter->ReloadCustomWords(); - LOG("Reloaded the chat filter's words (changed on the dashboard)"); - } + MasterHandlers().Dispatch(packet, ServiceType::MASTER); } void HandlePacket(Packet* packet) { diff --git a/dDashboardServer/DashboardServer.cpp b/dDashboardServer/DashboardServer.cpp index 1049b138e..a705e434b 100644 --- a/dDashboardServer/DashboardServer.cpp +++ b/dDashboardServer/DashboardServer.cpp @@ -17,6 +17,8 @@ #include "BinaryPathFinder.h" #include "ServiceType.h" #include "MessageType/Master.h" +#include "MasterPackets.h" +#include "PacketDispatcher.h" #include "Game.h" #include "BitStreamUtils.h" #include "dCommonVars.h" @@ -62,7 +64,7 @@ #include "ChallengeRoutes.h" #include "Inspector.h" #include "CDClientBrowser.h" -#include "MessageCapture.h" +#include "master/MessageCapture.h" #include "PublicRoutes.h" #include "Showcase.h" #include "FeaturedProperties.h" @@ -75,8 +77,8 @@ #include "WorldView.h" #include "PrometheusMetrics.h" #include "Background.h" -#include "DashboardMessages.h" -#include "DataChanged.h" +#include "master/DashboardMessages.h" +#include "master/DataChanged.h" #include "EmailService.h" #include "AuthMiddleware.h" #include "DashboardAuthService.h" @@ -181,229 +183,145 @@ namespace { } catch (const std::exception&) {} } - void HandleMasterPacket(Packet* packet) { - if (packet->length < 4) return; - if (static_cast(packet->data[1]) != ServiceType::MASTER) return; + void OnServerList(const MasterPackets::ServerListResponse& list, const SystemAddress&) { + std::lock_guard lock(ServerState::g_StatusMutex); + ServerState::g_AuthStatus.online = list.authOnline != 0; + ServerState::g_AuthStatus.lastSeen = std::chrono::steady_clock::now(); + ServerState::g_ChatStatus.online = list.chatOnline != 0; + ServerState::g_ChatStatus.lastSeen = std::chrono::steady_clock::now(); - switch (static_cast(packet->data[3])) { - case MessageType::Master::SERVER_LIST_RESPONSE: { - CINSTREAM_SKIP_HEADER; - - uint8_t authOnline = 0; - uint8_t chatOnline = 0; - uint32_t instanceCount = 0; - - inStream.Read(authOnline); - inStream.Read(chatOnline); - inStream.Read(instanceCount); - - std::lock_guard lock(ServerState::g_StatusMutex); - ServerState::g_AuthStatus.online = authOnline != 0; - ServerState::g_AuthStatus.lastSeen = std::chrono::steady_clock::now(); - ServerState::g_ChatStatus.online = chatOnline != 0; - ServerState::g_ChatStatus.lastSeen = std::chrono::steady_clock::now(); - - ServerState::g_WorldInstances.clear(); - for (uint32_t i = 0; i < instanceCount; i++) { - WorldInstanceInfo info; - LUString ip; - // Same types as MasterServer writes them (map and instance IDs are 16 bits) - LWOMAPID mapID = 0; - LWOINSTANCEID instanceID = 0; - inStream.Read(mapID); - inStream.Read(instanceID); - info.mapID = mapID; - info.instanceID = instanceID; - inStream.Read(info.cloneID); - inStream.Read(info.players); - inStream.Read(ip); - info.ip = ip.string; - inStream.Read(info.port); - uint8_t isPrivate = 0; - inStream.Read(isPrivate); - info.isPrivate = isPrivate != 0; - info.zoneName = GetZoneDisplayName(info.mapID); - AddPropertyDetails(info); - ServerState::g_WorldInstances.push_back(info); - } - - LOG_DEBUG("Received server list: auth=%s chat=%s worlds=%u", - authOnline ? "online" : "offline", - chatOnline ? "online" : "offline", - instanceCount); - break; - } - - case MessageType::Master::SERVER_INFO: { - CINSTREAM_SKIP_HEADER; - - uint32_t theirPort = 0; - uint32_t theirZoneID = 0; - uint32_t theirInstanceID = 0; - ServiceType theirServerType; - LUString theirIP; - - inStream.Read(theirPort); - inStream.Read(theirZoneID); - inStream.Read(theirInstanceID); - inStream.Read(theirServerType); - inStream.Read(theirIP); - - std::lock_guard lock(ServerState::g_StatusMutex); - switch (theirServerType) { - case ServiceType::AUTH: - if (theirIP.string == "offline") { - ServerState::g_AuthStatus.online = false; - } else { - ServerState::g_AuthStatus.online = true; - ServerState::g_AuthStatus.lastSeen = std::chrono::steady_clock::now(); - } - break; - case ServiceType::CHAT: - if (theirIP.string == "offline") { - ServerState::g_ChatStatus.online = false; - } else { - ServerState::g_ChatStatus.online = true; - ServerState::g_ChatStatus.lastSeen = std::chrono::steady_clock::now(); - } - break; - default: - break; - } - break; - } - - case MessageType::Master::WORLD_READY: { - CINSTREAM_SKIP_HEADER; - - LWOMAPID zoneID; - LWOINSTANCEID instanceID; - LWOCLONEID cloneID; - LUString ip; - uint32_t port; - uint8_t isPrivate; - - inStream.Read(zoneID); - inStream.Read(instanceID); - inStream.Read(cloneID); - inStream.Read(ip); - inStream.Read(port); - inStream.Read(isPrivate); - - std::lock_guard lock(ServerState::g_StatusMutex); + ServerState::g_WorldInstances.clear(); + for (const auto& instance : list.instances) { WorldInstanceInfo info; - info.mapID = zoneID; - info.instanceID = instanceID; - info.cloneID = cloneID; - info.players = 0; - info.ip = ip.string; - info.port = port; - info.isPrivate = isPrivate != 0; - info.zoneName = GetZoneDisplayName(zoneID); + info.mapID = instance.mapID; + info.instanceID = instance.instanceID; + info.cloneID = instance.cloneID; + info.players = instance.players; + info.ip = instance.ip.string; + info.port = instance.port; + info.isPrivate = instance.isPrivate != 0; + info.zoneName = GetZoneDisplayName(info.mapID); AddPropertyDetails(info); - // Master can report a world more than once (in the server list and when it becomes ready): replace, don't add - auto& instances = ServerState::g_WorldInstances; - const auto existing = std::ranges::find_if(instances, [&](const WorldInstanceInfo& w) { return w.mapID == zoneID && w.instanceID == instanceID; }); - if (existing != instances.end()) { - info.players = existing->players; - *existing = info; + ServerState::g_WorldInstances.push_back(info); + } + + LOG_DEBUG("Received server list: auth=%s chat=%s worlds=%u", + list.authOnline ? "online" : "offline", + list.chatOnline ? "online" : "offline", + static_cast(list.instances.size())); + } + + void OnServerInfo(const MasterPackets::ServerInfo& serverInfo, const SystemAddress&) { + std::lock_guard lock(ServerState::g_StatusMutex); + switch (serverInfo.serverType) { + case ServiceType::AUTH: + if (serverInfo.ip.string == "offline") { + ServerState::g_AuthStatus.online = false; } else { - instances.push_back(info); - } - - LOG("World ready: zone %i instance %i", zoneID, instanceID); - break; - } - - case MessageType::Master::PLAYER_ADDED: { - CINSTREAM_SKIP_HEADER; - - LWOMAPID zoneID; - LWOINSTANCEID instanceID; - inStream.Read(zoneID); - inStream.Read(instanceID); - - std::lock_guard lock(ServerState::g_StatusMutex); - for (auto& world : ServerState::g_WorldInstances) { - if (world.mapID == zoneID && world.instanceID == instanceID) { - world.players++; - break; - } + ServerState::g_AuthStatus.online = true; + ServerState::g_AuthStatus.lastSeen = std::chrono::steady_clock::now(); } break; - } - - case MessageType::Master::PLAYER_REMOVED: { - CINSTREAM_SKIP_HEADER; - - LWOMAPID zoneID; - LWOINSTANCEID instanceID; - inStream.Read(zoneID); - inStream.Read(instanceID); - - std::lock_guard lock(ServerState::g_StatusMutex); - for (auto& world : ServerState::g_WorldInstances) { - if (world.mapID == zoneID && world.instanceID == instanceID) { - if (world.players > 0) world.players--; - break; - } + case ServiceType::CHAT: + if (serverInfo.ip.string == "offline") { + ServerState::g_ChatStatus.online = false; + } else { + ServerState::g_ChatStatus.online = true; + ServerState::g_ChatStatus.lastSeen = std::chrono::steady_clock::now(); } break; - } - - case MessageType::Master::PLAYER_POSITIONS: { - CINSTREAM_SKIP_HEADER; - PlayerPositions positions; - if (positions.Deserialize(inStream)) LiveWorld::HandlePlayerPositions(positions); - break; - } - - case MessageType::Master::MESSAGE_CAPTURE_DATA: { - CINSTREAM_SKIP_HEADER; - MessageCaptureData data; - if (data.Deserialize(inStream)) Inspector::HandleData(data); - break; - } - - case MessageType::Master::DATA_CHANGED: { - CINSTREAM_SKIP_HEADER; - DataChanged changed; - if (changed.Deserialize(inStream)) BroadcastDataChanged(changed); - break; - } - - case MessageType::Master::PLAYER_ACTION_RESULT: { - CINSTREAM_SKIP_HEADER; - PlayerActionResult result; - if (result.Deserialize(inStream)) PlayerActions::HandleResult(result); - break; - } - - case MessageType::Master::SHUTDOWN_RESPONSE: { - CINSTREAM_SKIP_HEADER; - - LWOMAPID zoneID; - LWOINSTANCEID instanceID; - inStream.Read(zoneID); - inStream.Read(instanceID); - - std::lock_guard lock(ServerState::g_StatusMutex); - auto& instances = ServerState::g_WorldInstances; - instances.erase( - std::remove_if(instances.begin(), instances.end(), - [zoneID, instanceID](const WorldInstanceInfo& w) { - return w.mapID == zoneID && w.instanceID == instanceID; - }), - instances.end()); - - LOG("World shutdown: zone %i instance %i", zoneID, instanceID); - break; - } - default: break; } } + + void OnWorldReady(const MasterPackets::WorldReadyInfo& ready, const SystemAddress&) { + const LWOMAPID zoneID = ready.zoneID; + const LWOINSTANCEID instanceID = ready.instanceID; + + std::lock_guard lock(ServerState::g_StatusMutex); + WorldInstanceInfo info; + info.mapID = zoneID; + info.instanceID = instanceID; + info.cloneID = ready.cloneID; + info.players = 0; + info.ip = ready.ip.string; + info.port = ready.port; + info.isPrivate = ready.isPrivate != 0; + info.zoneName = GetZoneDisplayName(zoneID); + AddPropertyDetails(info); + // Master can report a world more than once (in the server list and when it becomes ready): replace, don't add + auto& instances = ServerState::g_WorldInstances; + const auto existing = std::ranges::find_if(instances, [&](const WorldInstanceInfo& w) { return w.mapID == zoneID && w.instanceID == instanceID; }); + if (existing != instances.end()) { + info.players = existing->players; + *existing = info; + } else { + instances.push_back(info); + } + + LOG("World ready: zone %i instance %i", zoneID, instanceID); + } + + void OnPlayerAdded(const MasterPackets::PlayerAdded& added, const SystemAddress&) { + std::lock_guard lock(ServerState::g_StatusMutex); + for (auto& world : ServerState::g_WorldInstances) { + if (world.mapID == added.zoneID && world.instanceID == added.instanceID) { + world.players++; + break; + } + } + } + + void OnPlayerRemoved(const MasterPackets::PlayerRemoved& removed, const SystemAddress&) { + std::lock_guard lock(ServerState::g_StatusMutex); + for (auto& world : ServerState::g_WorldInstances) { + if (world.mapID == removed.zoneID && world.instanceID == removed.instanceID) { + if (world.players > 0) world.players--; + break; + } + } + } + + void OnWorldShutDown(const MasterPackets::WorldShutDown& shutDown, const SystemAddress&) { + const LWOMAPID zoneID = shutDown.zoneID; + const LWOINSTANCEID instanceID = shutDown.instanceID; + + std::lock_guard lock(ServerState::g_StatusMutex); + auto& instances = ServerState::g_WorldInstances; + instances.erase( + std::remove_if(instances.begin(), instances.end(), + [zoneID, instanceID](const WorldInstanceInfo& w) { + return w.mapID == zoneID && w.instanceID == instanceID; + }), + instances.end()); + + LOG("World shutdown: zone %i instance %i", zoneID, instanceID); + } + + // Packets from master + const PacketDispatcher& MasterHandlers() { + static const auto handlers = [] { + PacketDispatcher handlers; + using MessageType::Master; + handlers.On(Master::SERVER_LIST_RESPONSE, OnServerList); + handlers.On(Master::SERVER_INFO, OnServerInfo); + handlers.On(Master::WORLD_READY, OnWorldReady); + handlers.On(Master::PLAYER_ADDED, OnPlayerAdded); + handlers.On(Master::PLAYER_REMOVED, OnPlayerRemoved); + handlers.On(Master::PLAYER_POSITIONS, [](const PlayerPositions& positions, const SystemAddress&) { LiveWorld::HandlePlayerPositions(positions); }); + handlers.On(Master::MESSAGE_CAPTURE_DATA, [](const MessageCaptureData& data, const SystemAddress&) { Inspector::HandleData(data); }); + handlers.On(Master::DATA_CHANGED, [](const DataChanged& changed, const SystemAddress&) { BroadcastDataChanged(changed); }); + handlers.On(Master::PLAYER_ACTION_RESULT, [](const PlayerActionResult& result, const SystemAddress&) { PlayerActions::HandleResult(result); }); + handlers.On(Master::SHUTDOWN_RESPONSE, OnWorldShutDown); + return handlers; + }(); + return handlers; + } + + void HandleMasterPacket(Packet* packet) { + MasterHandlers().Dispatch(packet, ServiceType::MASTER); + } } int main(int argc, char** argv) { @@ -622,9 +540,7 @@ int main(int argc, char** argv) { // Only once the master link is up; sent earlier, the request is dropped and auth/chat look offline if (g_Server->GetIsConnectedToMaster() && std::chrono::steady_clock::now() >= g_NextServerListRequest) { - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_SERVER_LIST); - g_Server->SendToMaster(bitStream); + MasterPackets::SendToMaster(MasterPackets::RequestServerList()); g_NextServerListRequest = std::chrono::steady_clock::now() + SERVER_LIST_INTERVAL; } diff --git a/dDashboardServer/routes/AccountModeration.cpp b/dDashboardServer/routes/AccountModeration.cpp index b89acac09..d7aa2405f 100644 --- a/dDashboardServer/routes/AccountModeration.cpp +++ b/dDashboardServer/routes/AccountModeration.cpp @@ -4,7 +4,7 @@ #include "RouteUtils.h" #include "PlayerActions.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "WSRoutes.h" #include "Database.h" #include "HTTPContext.h" diff --git a/dDashboardServer/routes/Announcements.cpp b/dDashboardServer/routes/Announcements.cpp index 03cf2ccc6..9f0c61ed1 100644 --- a/dDashboardServer/routes/Announcements.cpp +++ b/dDashboardServer/routes/Announcements.cpp @@ -2,7 +2,7 @@ #include "AnnouncementSchedule.h" #include "LiveWorld.h" #include "DashboardRoutes.h" -#include "DashboardMessages.h" +#include "master/DashboardMessages.h" #include #include diff --git a/dDashboardServer/routes/CharacterTools.cpp b/dDashboardServer/routes/CharacterTools.cpp index 39cad05e9..85f352537 100644 --- a/dDashboardServer/routes/CharacterTools.cpp +++ b/dDashboardServer/routes/CharacterTools.cpp @@ -8,7 +8,7 @@ #include "Scheduler.h" #include "Background.h" #include "PlayerActions.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "WSRoutes.h" #include "ClientAssets.h" #include "DashboardRoutes.h" diff --git a/dDashboardServer/routes/ChatRoutes.cpp b/dDashboardServer/routes/ChatRoutes.cpp index 7e8b844fe..4743afe6c 100644 --- a/dDashboardServer/routes/ChatRoutes.cpp +++ b/dDashboardServer/routes/ChatRoutes.cpp @@ -6,7 +6,7 @@ #include "RouteUtils.h" #include "DashboardRoutes.h" #include "PlayerActions.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "WSRoutes.h" #include "Permissions.h" #include "Database.h" diff --git a/dDashboardServer/routes/EventsCalendar.cpp b/dDashboardServer/routes/EventsCalendar.cpp index cb4784697..9072fa169 100644 --- a/dDashboardServer/routes/EventsCalendar.cpp +++ b/dDashboardServer/routes/EventsCalendar.cpp @@ -10,7 +10,7 @@ #include "LiveWorld.h" #include "LiveOpsRules.h" #include "PlayerActions.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "ServerState.h" #include "GameLabels.h" diff --git a/dDashboardServer/routes/Inspector.cpp b/dDashboardServer/routes/Inspector.cpp index 4075116c3..3a2937648 100644 --- a/dDashboardServer/routes/Inspector.cpp +++ b/dDashboardServer/routes/Inspector.cpp @@ -1,4 +1,5 @@ #include "Inspector.h" +#include "MasterPackets.h" #include #include @@ -11,7 +12,7 @@ #include "Game.h" #include "InspectorFormat.h" #include "Logger.h" -#include "MessageCapture.h" +#include "master/MessageCapture.h" #include "MessageType/Master.h" #include "Permissions.h" #include "RouteUtils.h" @@ -85,10 +86,7 @@ namespace { void SendControl(const MessageCaptureControl& control) { if (!Connected()) return; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::MESSAGE_CAPTURE_CONTROL); - control.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(control); } // (Re)start in whichever world has the player, for the time that is left diff --git a/dDashboardServer/routes/InspectorFormat.h b/dDashboardServer/routes/InspectorFormat.h index 86f4d9af0..487eeb8d6 100644 --- a/dDashboardServer/routes/InspectorFormat.h +++ b/dDashboardServer/routes/InspectorFormat.h @@ -11,7 +11,7 @@ #include "json.hpp" #include "magic_enum.hpp" #include "IMessageCaptures.h" -#include "MessageCapture.h" +#include "master/MessageCapture.h" #include "MessageType/Game.h" /** diff --git a/dDashboardServer/routes/InstanceLoad.cpp b/dDashboardServer/routes/InstanceLoad.cpp index cfd1b6f48..a3491e1e4 100644 --- a/dDashboardServer/routes/InstanceLoad.cpp +++ b/dDashboardServer/routes/InstanceLoad.cpp @@ -1,4 +1,5 @@ #include "InstanceLoad.h" +#include "MasterPackets.h" #include "InstanceLimits.h" #include "DashboardRoutes.h" #include "ServerState.h" @@ -62,9 +63,7 @@ namespace { // Master reloads its settings and zone limits on CONFIG_RELOAD (and passes it on to every server) bool TellMaster() { if (!Game::server || !Game::server->GetIsConnectedToMaster()) return false; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::CONFIG_RELOAD); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(MasterPackets::ConfigReload()); return true; } diff --git a/dDashboardServer/routes/LiveWorld.cpp b/dDashboardServer/routes/LiveWorld.cpp index 222738330..865bfac3f 100644 --- a/dDashboardServer/routes/LiveWorld.cpp +++ b/dDashboardServer/routes/LiveWorld.cpp @@ -1,4 +1,5 @@ #include "LiveWorld.h" +#include "MasterPackets.h" #include "WorldView.h" #include "Permissions.h" #include "DashboardRoutes.h" @@ -11,7 +12,7 @@ #include "Alerts.h" #include "BitStreamUtils.h" #include "ServerState.h" -#include "DashboardMessages.h" +#include "master/DashboardMessages.h" #include "Database.h" #include "Game.h" #include "Logger.h" @@ -73,11 +74,11 @@ namespace { void SendAnnouncement(const std::string& title, const std::string& message, const std::vector& zones = {}) { if (!Game::server || !Game::server->GetIsConnectedToMaster()) return; - Announcement announcement{ title, message, zones }; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::ANNOUNCE); - announcement.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + Announcement announcement; + announcement.title = title; + announcement.message = message; + announcement.zones = zones; + MasterPackets::SendToMaster(announcement); } std::string Duration(int64_t seconds) { @@ -130,9 +131,7 @@ namespace { g_Restart.reset(); SaveRestart(); if (Game::server && Game::server->GetIsConnectedToMaster()) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::DASHBOARD_SHUTDOWN); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(MasterPackets::DashboardShutdown()); } return; } @@ -235,11 +234,10 @@ namespace LiveWorld { } if (!running) return JsonError(reply, eHTTPStatusCode::NOT_FOUND, "That world instance isn't running"); if (!Game::server || !Game::server->GetIsConnectedToMaster()) return JsonError(reply, eHTTPStatusCode::SERVICE_UNAVAILABLE, "Not connected to the master server"); - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::INSTANCE_SHUTDOWN); - bitStream.Write(zone); - bitStream.Write(instance); - Game::server->SendToMaster(bitStream); + MasterPackets::InstanceShutdown request; + request.zoneID = zone; + request.instanceID = instance; + MasterPackets::SendToMaster(request); Audit(context, "shutdown_instance", "Zone " + std::to_string(zone) + " instance " + std::to_string(instance) + " (" + std::to_string(players) + " player(s))"); JsonSuccess(reply, { {"message", "Shutting it down"} }); }); diff --git a/dDashboardServer/routes/MissionTools.cpp b/dDashboardServer/routes/MissionTools.cpp index 223db8407..a5cc21c88 100644 --- a/dDashboardServer/routes/MissionTools.cpp +++ b/dDashboardServer/routes/MissionTools.cpp @@ -6,7 +6,7 @@ #include "RouteUtils.h" #include "CharacterTools.h" #include "PlayerActions.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "ClientAssets.h" #include "GameLabels.h" #include "WSRoutes.h" diff --git a/dDashboardServer/routes/ModerationTools.cpp b/dDashboardServer/routes/ModerationTools.cpp index 7761d9ef5..dabe253a9 100644 --- a/dDashboardServer/routes/ModerationTools.cpp +++ b/dDashboardServer/routes/ModerationTools.cpp @@ -10,7 +10,7 @@ #include "ClientAssets.h" #include "GameLabels.h" #include "PlayerActions.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "Strikes.h" #include "WSRoutes.h" #include "Database.h" diff --git a/dDashboardServer/routes/PlayerActions.cpp b/dDashboardServer/routes/PlayerActions.cpp index 08d858c61..10fc13378 100644 --- a/dDashboardServer/routes/PlayerActions.cpp +++ b/dDashboardServer/routes/PlayerActions.cpp @@ -1,4 +1,5 @@ #include "PlayerActions.h" +#include "MasterPackets.h" #include #include @@ -90,10 +91,7 @@ uint32_t PlayerActions::Request(PlayerActionRequest request, uint32_t ownerAccou return request.requestId; } - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_ACTION); - request.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(request); return request.requestId; } diff --git a/dDashboardServer/routes/PlayerActions.h b/dDashboardServer/routes/PlayerActions.h index 5ec73d302..ab8a1ad36 100644 --- a/dDashboardServer/routes/PlayerActions.h +++ b/dDashboardServer/routes/PlayerActions.h @@ -8,7 +8,7 @@ #include #include "json.hpp" -#include "PlayerAction.h" +#include "master/PlayerAction.h" /** * Sends player actions (kick, refresh, rescue) through master to every world server and reports the outcome. diff --git a/dDashboardServer/routes/SettingsRoutes.cpp b/dDashboardServer/routes/SettingsRoutes.cpp index 20c321222..806cff0d8 100644 --- a/dDashboardServer/routes/SettingsRoutes.cpp +++ b/dDashboardServer/routes/SettingsRoutes.cpp @@ -1,4 +1,5 @@ #include "SettingsRoutes.h" +#include "MasterPackets.h" #include "Permissions.h" #include "SettingsCatalog.h" #include "SettingsHistory.h" @@ -160,9 +161,7 @@ namespace { void ReloadEverywhere() { Game::config->ReloadConfig(); if (!Game::server || !Game::server->GetIsConnectedToMaster()) return; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::CONFIG_RELOAD); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(MasterPackets::ConfigReload()); } bool ValidName(const std::string& name) { diff --git a/dDashboardServer/routes/VanityRoutes.cpp b/dDashboardServer/routes/VanityRoutes.cpp index 46bf93f61..facb3d3c0 100644 --- a/dDashboardServer/routes/VanityRoutes.cpp +++ b/dDashboardServer/routes/VanityRoutes.cpp @@ -9,7 +9,7 @@ #include "RouteUtils.h" #include "VanityJson.h" #include "PlayerActions.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "DashboardRoutes.h" #include "ClientAssets.h" #include "BinaryPathFinder.h" diff --git a/dDashboardServer/routes/WSRoutes.cpp b/dDashboardServer/routes/WSRoutes.cpp index 6f2df4745..c55c57a0f 100644 --- a/dDashboardServer/routes/WSRoutes.cpp +++ b/dDashboardServer/routes/WSRoutes.cpp @@ -7,7 +7,7 @@ #include "Game.h" #include "Database.h" #include "Logger.h" -#include "DataChanged.h" +#include "master/DataChanged.h" #include "Alerts.h" #include "LiveWorld.h" #include "GeneralUtils.h" diff --git a/dDashboardServer/routes/WorldView.cpp b/dDashboardServer/routes/WorldView.cpp index d9a4de6db..ea01e1e11 100644 --- a/dDashboardServer/routes/WorldView.cpp +++ b/dDashboardServer/routes/WorldView.cpp @@ -21,7 +21,7 @@ #include "RouteUtils.h" #include "CDClientDatabase.h" -#include "DashboardMessages.h" +#include "master/DashboardMessages.h" #include "Database.h" #include "Game.h" #include "Logger.h" diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.cpp b/dGame/dComponents/RocketLaunchpadControlComponent.cpp index ec6305b0b..a64cce767 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.cpp +++ b/dGame/dComponents/RocketLaunchpadControlComponent.cpp @@ -1,4 +1,5 @@ #include "RocketLaunchpadControlComponent.h" +#include "MasterPackets.h" #include @@ -136,10 +137,9 @@ LWOCLONEID RocketLaunchpadControlComponent::GetSelectedCloneId(LWOOBJID player) } void RocketLaunchpadControlComponent::TellMasterToPrepZone(int zoneID) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PREP_ZONE); - bitStream.Write(zoneID); - Game::server->SendToMaster(bitStream); + MasterPackets::PrepZone request; + request.zoneID = zoneID; + MasterPackets::SendToMaster(request); } diff --git a/dGame/dGameMessages/GameMessageDecoder.cpp b/dGame/dGameMessages/GameMessageDecoder.cpp index bb8b889fa..95fbc1437 100644 --- a/dGame/dGameMessages/GameMessageDecoder.cpp +++ b/dGame/dGameMessages/GameMessageDecoder.cpp @@ -12,7 +12,7 @@ #include "EchoSyncSkill.h" #include "RequestServerProjectileImpact.h" #include "DoClientProjectileImpact.h" -#include "MessageCapture.h" +#include "master/MessageCapture.h" namespace { using json = nlohmann::json; diff --git a/dGame/dUtilities/DashboardActions.cpp b/dGame/dUtilities/DashboardActions.cpp index b698c3dc0..3dbb05969 100644 --- a/dGame/dUtilities/DashboardActions.cpp +++ b/dGame/dUtilities/DashboardActions.cpp @@ -1,6 +1,6 @@ #include "DashboardActions.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "Game.h" #include "Logger.h" #include "Database.h" diff --git a/dGame/dUtilities/DashboardNotify.cpp b/dGame/dUtilities/DashboardNotify.cpp index 216623606..408e17f68 100644 --- a/dGame/dUtilities/DashboardNotify.cpp +++ b/dGame/dUtilities/DashboardNotify.cpp @@ -1,15 +1,16 @@ #include "DashboardNotify.h" +#include "MasterPackets.h" #include #include #include "BitStreamUtils.h" -#include "DataChanged.h" +#include "master/DataChanged.h" #include "Game.h" #include "dServer.h" #include "MessageType/Master.h" #include "ServiceType.h" -#include "DashboardMessages.h" +#include "master/DashboardMessages.h" #include "PlayerManager.h" #include "Entity.h" #include "Character.h" @@ -44,10 +45,7 @@ namespace DashboardNotify { for (const auto& [table, id] : g_Pending) message.entries.push_back({ table, id }); g_Pending.clear(); - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::DATA_CHANGED); - message.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(message); } } @@ -75,10 +73,7 @@ namespace DashboardNotify { message.players.push_back({ character->GetID(), position.x, position.y, position.z }); } - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_POSITIONS); - message.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(message); } std::string ChatLine(const std::string& title, const std::string& message) { diff --git a/dGame/dUtilities/MessageInspector.cpp b/dGame/dUtilities/MessageInspector.cpp index 01b1104ff..625d7446e 100644 --- a/dGame/dUtilities/MessageInspector.cpp +++ b/dGame/dUtilities/MessageInspector.cpp @@ -1,4 +1,5 @@ #include "MessageInspector.h" +#include "MasterPackets.h" #include #include @@ -11,7 +12,7 @@ #include "Game.h" #include "GameMessageDecoder.h" #include "Logger.h" -#include "MessageCapture.h" +#include "master/MessageCapture.h" #include "MessageType/Client.h" #include "MessageType/Master.h" #include "PlayerManager.h" @@ -64,10 +65,7 @@ namespace { data.dropped = dropped; data.entries = std::move(entries); - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::MESSAGE_CAPTURE_DATA); - data.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(data); } // Send what is waiting: a couple of batches per call (the rest goes next time), or everything when `all` diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 10e47107d..8d325953b 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -1,4 +1,5 @@ #include "DEVGMCommands.h" +#include "MasterPackets.h" #include @@ -527,9 +528,7 @@ namespace DEVGMCommands { void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args) { //Tell the master server that we're going to be shutting down whole "universe": - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SHUTDOWN_UNIVERSE); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(MasterPackets::ShutdownUniverse()); ChatPackets::SendSystemMessage(sysAddr, u"Sent universe shutdown notification to master."); //Tell chat to send an announcement to all servers diff --git a/dGame/dUtilities/WorldMigration.cpp b/dGame/dUtilities/WorldMigration.cpp index 88c84db66..0bd19b828 100644 --- a/dGame/dUtilities/WorldMigration.cpp +++ b/dGame/dUtilities/WorldMigration.cpp @@ -1,4 +1,5 @@ #include "WorldMigration.h" +#include "MasterPackets.h" #include #include @@ -74,10 +75,7 @@ namespace { status.failed = failed; status.remaining = remaining; status.message = message; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::MIGRATE_STATUS); - status.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(status); } void SendStatus(eState state, uint16_t remaining, const std::string& message) { @@ -147,10 +145,7 @@ namespace { g_Leaving[sysAddr] = { playerId, 0.0f }; { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::MIGRATE_PLAYER_STATE); - carried.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(carried); } // Seamless: take every object we sent away first. The client deletes them but keeps its own player object when @@ -360,10 +355,7 @@ void WorldMigration::RequestMigration(Entity* requester, eKind kind, uint32_t ta } LOG("Asking master to %s this instance (migration %u, requested by %s)", KindName(kind), request.requestId, request.requestedBy.c_str()); - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::INSTANCE_MIGRATE); - request.Serialize(bitStream); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(request); } namespace { diff --git a/dGame/dUtilities/WorldMigration.h b/dGame/dUtilities/WorldMigration.h index 857a5e61a..dbecc8328 100644 --- a/dGame/dUtilities/WorldMigration.h +++ b/dGame/dUtilities/WorldMigration.h @@ -3,7 +3,7 @@ #include -#include "InstanceMigration.h" +#include "master/InstanceMigration.h" #include "RakNetTypes.h" #include "dCommonVars.h" diff --git a/dMasterServer/InstanceManager.cpp b/dMasterServer/InstanceManager.cpp index de544e181..65bfbf944 100644 --- a/dMasterServer/InstanceManager.cpp +++ b/dMasterServer/InstanceManager.cpp @@ -21,6 +21,22 @@ using std::make_unique; namespace { const InstancePtr g_Empty{ nullptr }; + + // Where the player of request goes: instance + void SendZoneTransferResponse(const PendingInstanceRequest& request, const Instance& instance) { + const auto& zoneId = instance.GetZoneID(); + MasterPackets::RequestZoneTransferResponse response; + response.requestID = request.id; + response.mythranShift = request.mythranShift; + response.zoneID = zoneId.GetMapID(); + response.zoneInstance = zoneId.GetInstanceID(); + response.zoneClone = zoneId.GetCloneID(); + response.serverPort = static_cast(instance.GetPort()); + response.serverIP = LUString(instance.GetIP(), 255); + response.stamps = request.stamps; + response.stamps.Add(eStamps::PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, zoneId.GetInstanceID()); + MasterPackets::SendTo(request.sysAddr, response); + } } InstanceManager::InstanceManager(const std::string& externalIP) : mExternalIP{ externalIP } { @@ -156,20 +172,7 @@ void InstanceManager::ReadyInstance(const InstancePtr& instance) { LOG("Responding to pending request %llu -> %i (%i)", request, zoneId.GetMapID(), zoneId.GetCloneID()); - auto stamps = request.stamps; - stamps.Add(eStamps::PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, zoneId.GetInstanceID()); - MasterPackets::SendZoneTransferResponse( - Game::server, - request.sysAddr, - request.id, - request.mythranShift, - zoneId.GetMapID(), - zoneId.GetInstanceID(), - zoneId.GetCloneID(), - instance->GetIP(), - instance->GetPort(), - stamps - ); + SendZoneTransferResponse(request, *instance); } pending.clear(); @@ -178,13 +181,9 @@ void InstanceManager::ReadyInstance(const InstancePtr& instance) { void InstanceManager::RequestAffirmation(const InstancePtr& instance, const PendingInstanceRequest& request) { instance->GetPendingAffirmations().push_back(request); - CBITSTREAM; - - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::AFFIRM_TRANSFER_REQUEST); - - bitStream.Write(request.id); - - Game::server->Send(bitStream, instance->GetSysAddr(), false); + MasterPackets::AffirmTransferRequest affirm; + affirm.requestID = request.id; + MasterPackets::SendTo(instance->GetSysAddr(), affirm); LOG("Sent affirmation request %llu to %i/%i", request.id, static_cast(instance->GetZoneID().GetMapID()), @@ -200,22 +199,7 @@ void InstanceManager::AffirmTransfer(const InstancePtr& instance, const uint64_t if (request.id != transferID) continue; - const auto& zoneId = instance->GetZoneID(); - - auto stamps = request.stamps; - stamps.Add(eStamps::PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, zoneId.GetInstanceID()); - MasterPackets::SendZoneTransferResponse( - Game::server, - request.sysAddr, - request.id, - request.mythranShift, - zoneId.GetMapID(), - zoneId.GetInstanceID(), - zoneId.GetCloneID(), - instance->GetIP(), - instance->GetPort(), - stamps - ); + SendZoneTransferResponse(request, *instance); pending.erase(pending.begin() + i); @@ -240,7 +224,7 @@ void InstanceManager::RedirectPendingRequests(const InstancePtr& instance) { } } -const InstancePtr& InstanceManager::GetInstanceBySysAddr(SystemAddress& sysAddr) { +const InstancePtr& InstanceManager::GetInstanceBySysAddr(const SystemAddress& sysAddr) { for (const auto& instance : m_Instances) { if (instance && instance->GetSysAddr() == sysAddr) { return instance; @@ -418,11 +402,7 @@ bool Instance::GetShutdownComplete() const { } void Instance::Shutdown() { - CBITSTREAM; - - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SHUTDOWN); - - Game::server->Send(bitStream, this->m_SysAddr, false); + MasterPackets::SendTo(this->m_SysAddr, MasterPackets::Shutdown()); LOG("Triggered world shutdown for zone/clone/instance %i/%i/%i", GetMapID(), GetCloneID(), GetInstanceID()); } diff --git a/dMasterServer/InstanceManager.h b/dMasterServer/InstanceManager.h index 43783e568..203338f3a 100644 --- a/dMasterServer/InstanceManager.h +++ b/dMasterServer/InstanceManager.h @@ -146,7 +146,7 @@ public: void RedirectPendingRequests(const InstancePtr& instance); - const InstancePtr& GetInstanceBySysAddr(SystemAddress& sysAddr); + const InstancePtr& GetInstanceBySysAddr(const SystemAddress& sysAddr); const InstancePtr& FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId = 0); const InstancePtr& FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID); diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 69625f3b9..feb469a65 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -1,5 +1,5 @@ -#include "PlayerAction.h" -#include "DashboardMessages.h" +#include "master/PlayerAction.h" +#include "master/DashboardMessages.h" #include #include #include @@ -47,6 +47,10 @@ #include "CDZoneTableTable.h" #include "eGameMasterLevel.h" #include "StringifiedEnum.h" +#include "PacketDispatcher.h" +#include "master/DataChanged.h" +#include "master/MessageCapture.h" +#include "master/InstanceMigration.h" #ifdef DARKFLAME_PLATFORM_UNIX @@ -101,10 +105,7 @@ namespace { result.affected = it->second.affected; result.timedOut = timedOut; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_ACTION_RESULT); - result.Serialize(bitStream); - Game::server->Send(bitStream, dashboardServerMasterPeerSysAddr, false); + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, result); } g_PendingPlayerActions.erase(it); } @@ -423,11 +424,8 @@ int main(int argc, char** argv) { // Instance migration progress goes to every world, where the GM who asked for it hears about it MigrationCoordinator::SetReporter([](const MigrationStatus& status) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::MIGRATE_STATUS); - status.Serialize(bitStream); for (const auto& instance : Game::im->GetInstances()) { - if (instance && instance->GetIsReady() && !instance->GetShutdownComplete()) Game::server->Send(bitStream, instance->GetSysAddr(), false); + if (instance && instance->GetIsReady() && !instance->GetShutdownComplete()) MasterPackets::SendTo(instance->GetSysAddr(), status); } }); Game::im->LoadZoneLimits(); @@ -559,6 +557,421 @@ int main(int argc, char** argv) { return ShutdownSequence(EXIT_SUCCESS); } +namespace { + using namespace MasterPackets; + + void OnRequestZoneTransfer(const RequestZoneTransfer& request, const SystemAddress& sysAddr) { + LOG("Received zone transfer req"); + const uint64_t requestID = request.requestID; + const uint8_t mythranShift = request.mythranShift; + const uint32_t zoneID = request.zoneID; + const uint32_t zoneClone = request.cloneID; + // The login stamps travelling with the request (see Stamps.h); master adds its steps + Stamps stamps = request.stamps; + if (!stamps.empty()) stamps.Add(eStamps::PASSPORT_AUTH_WORLD_PACKET_RECEIVED, zoneID); + if (shutdownSequenceStarted) { + LOG("Shutdown sequence has been started. Not creating a new zone."); + return; + } + const auto& in = Game::im->GetInstance(zoneID, false, zoneClone); + + for (const auto& instance : Game::im->GetInstances()) { + LOG("Instance: %i/%i/%i -> %i %s", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in, instance->GetSysAddr().ToString()); + } + + if (in && !in->GetIsReady()) //Instance not ready, make a pending request + { + if (!stamps.empty()) stamps.Add(eStamps::PASSPORT_AUTH_IM_LOGIN_QUEUED, in->GetInstanceID()); + in->GetPendingRequests().push_back({ requestID, static_cast(mythranShift), sysAddr, stamps }); + LOG("Server not ready, adding pending request %llu %i %i", requestID, zoneID, zoneClone); + return; + } + + //Instance is ready, transfer + LOG("Responding to transfer request %llu for zone %i %i", requestID, zoneID, zoneClone); + Game::im->RequestAffirmation(in, { requestID, static_cast(mythranShift), sysAddr, stamps }); + } + + //This is here because otherwise we'd have to include IM in + //non-master servers. This packet allows us to add World + //servers back if master crashed + void OnServerInfo(const ServerInfo& info, const SystemAddress& sysAddr) { + const uint32_t theirPort = info.port; + const uint32_t theirZoneID = info.zoneID; + const uint32_t theirInstanceID = info.instanceID; + const ServiceType theirServerType = info.serverType; + const LUString& theirIP = info.ip; + + switch (theirServerType) { + case ServiceType::WORLD: + if (!Game::im->IsPortInUse(theirPort)) { + auto in = std::make_unique(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12); + in->SetSysAddr(sysAddr); + Game::im->AddInstance(in); + } else { + const auto& instance = Game::im->FindInstanceWithPrivate(theirZoneID, static_cast(theirInstanceID)); + if (instance) { + instance->SetSysAddr(sysAddr); + } + } + break; + case ServiceType::CHAT: + chatServerMasterPeerSysAddr = sysAddr; + break; + case ServiceType::AUTH: + authServerMasterPeerSysAddr = sysAddr; + break; + case ServiceType::DASHBOARD: + dashboardServerMasterPeerSysAddr = sysAddr; + break; + default: + break; + } + + if (theirServerType != ServiceType::DASHBOARD && dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, info); + } + + LOG("Received %s server info, instance: %i port: %i", StringifiedEnum::ToString(theirServerType).data(), theirInstanceID, theirPort); + } + + void OnSetSessionKey(const SetSessionKey& request, const SystemAddress& sysAddr) { + const uint32_t sessionKey = request.sessionKey; + const LUString& username = request.username; + + for (auto it : activeSessions) { + if (it.second == username.string) { + activeSessions.erase(it.first); + + NewSessionAlert alert; + alert.sessionKey = sessionKey; + alert.username = username; + alert.Broadcast(); + + break; + } + } + + activeSessions.insert(std::make_pair(sessionKey, username.string)); + LOG("Got sessionKey %i for user %s", sessionKey, username.string.c_str()); + } + + void OnRequestSessionKey(const RequestSessionKey& request, const SystemAddress& sysAddr) { + const LUWString& username = request.username; + LOG("Requesting session key for %s", username.GetAsString().c_str()); + for (auto key : activeSessions) { + if (key.second == username.GetAsString()) { + SessionKeyResponse response; + response.sessionKey = key.first; + response.username = username; + MasterPackets::SendTo(sysAddr, response); + break; + } + } + } + + void OnPlayerAdded(const PlayerAdded& added, const SystemAddress& sysAddr) { + const auto& instance = + Game::im->FindInstanceWithPrivate(added.zoneID, added.instanceID); + if (instance) { + instance->AddPlayer(Player()); + } else { + LOG("Instance missing? What?"); + } + + if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, added); + } + } + + void OnPlayerRemoved(const PlayerRemoved& removed, const SystemAddress& sysAddr) { + const auto& instance = + Game::im->FindInstance(removed.zoneID, removed.instanceID); + if (instance) { + instance->RemovePlayer(Player()); + } + + if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, removed); + } + } + + void OnCreatePrivateZone(const CreatePrivateZone& request, const SystemAddress& sysAddr) { + // Passwords were cut to 50 characters when read + const auto& newInst = Game::im->CreatePrivateInstance(request.zoneID, request.cloneID, request.password.c_str()); + LOG("Creating private zone %i/%i/%i", newInst->GetMapID(), newInst->GetCloneID(), newInst->GetInstanceID()); + } + + void OnRequestPrivateZone(const RequestPrivateZone& request, const SystemAddress& sysAddr) { + const uint64_t requestID = request.requestID; + const uint8_t mythranShift = request.mythranShift; + + const auto& instance = Game::im->FindPrivateInstance(request.password.c_str()); + + LOG("Join private zone: %llu %d %p", requestID, mythranShift, instance.get()); + + if (instance == nullptr) { + return; + } + + const auto& zone = instance->GetZoneID(); + + RequestZoneTransferResponse response; + response.requestID = requestID; + response.mythranShift = static_cast(mythranShift); + response.zoneID = zone.GetMapID(); + response.zoneInstance = instance->GetInstanceID(); + response.zoneClone = zone.GetCloneID(); + response.serverPort = static_cast(instance->GetPort()); + response.serverIP = LUString(instance->GetIP(), 255); + MasterPackets::SendTo(sysAddr, response); + } + + void OnWorldReady(const WorldReady& ready, const SystemAddress& sysAddr) { + const LWOMAPID zoneID = ready.zoneID; + const LWOINSTANCEID instanceID = ready.instanceID; + + LOG("Got world ready %i %i", zoneID, instanceID); + + const auto& instance = Game::im->FindInstanceWithPrivate(zoneID, instanceID); + + if (instance == nullptr) { + LOG("Failed to find zone to ready"); + return; + } + + LOG("Ready zone %i", zoneID); + Game::im->ReadyInstance(instance); + + if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { + WorldReadyInfo info; + info.zoneID = zoneID; + info.instanceID = instanceID; + info.cloneID = instance->GetCloneID(); + info.ip = LUString(instance->GetIP()); + info.port = instance->GetPort(); + info.isPrivate = instance->GetIsPrivate() ? 1 : 0; + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, info); + } + } + + void OnPrepZone(const PrepZone& request, const SystemAddress& sysAddr) { + const int32_t zoneID = request.zoneID; + if (shutdownSequenceStarted) { + LOG("Shutdown sequence has been started. Not prepping a new zone."); + } else { + LOG("Prepping zone %i", zoneID); + Game::im->GetInstance(zoneID, false, 0); + } + } + + void OnAffirmTransferResponse(const AffirmTransferResponse& response, const SystemAddress& sysAddr) { + const uint64_t requestID = response.requestID; + + LOG("Got affirmation of transfer %llu", requestID); + + const auto& instance = Game::im->GetInstanceBySysAddr(sysAddr); + + if (instance == nullptr) + return; + + Game::im->AffirmTransfer(instance, requestID); + LOG("Affirmation complete %llu", requestID); + } + + void OnShutdownResponse(const ShutdownResponse& response, const SystemAddress& sysAddr) { + const auto& instance = Game::im->GetInstanceBySysAddr(sysAddr); + LOG("Got shutdown response from %s", sysAddr.ToString()); + if (instance == nullptr) { + return; + } + + LOG("Got shutdown response from zone %i clone %i instance %i port %i", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance->GetPort()); + instance->SetIsShuttingDown(true); + } + + void OnShutdownUniverse(const ShutdownUniverse& request, const SystemAddress& sysAddr) { + LOG("Received shutdown universe command, shutting down in 10 minutes."); + Game::universeShutdownRequested = true; + } + + void OnInstanceMigrate(const InstanceMigrationRequest& request, const SystemAddress& sysAddr) { + // Only servers connected to master can send this (a world, for a GM's /replaceinstance or /mergeinstance) + if (shutdownSequenceStarted) { + LOG("Shutdown sequence has been started. Not starting instance migration %u.", request.requestId); + return; + } + MigrationCoordinator::Start(request); + } + + void OnPlayerAction(const PlayerActionRequest& request, const SystemAddress& sysAddr) { + // Only the dashboard may ask worlds to act on players + if (sysAddr != dashboardServerMasterPeerSysAddr) { + LOG("Ignoring player action from a server that is not the dashboard"); + return; + } + + PendingPlayerAction pending; + pending.action = request.action; + pending.deadline = std::chrono::steady_clock::now() + PLAYER_ACTION_TIMEOUT; + + for (const auto& instance : Game::im->GetInstances()) { + if (!instance || !instance->GetIsReady() || instance->GetIsShuttingDown()) continue; + MasterPackets::SendTo(instance->GetSysAddr(), request); + pending.waitingOn.insert(instance->GetSysAddr()); + } + // The chat server checks web chat with the same filter: it reloads too, but doesn't answer + if (request.action == ePlayerAction::RELOAD_CHAT_FILTER && chatServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { + MasterPackets::SendTo(chatServerMasterPeerSysAddr, request); + } + + LOG("Dashboard player action %i (request %u) sent to %zu world(s)", static_cast(request.action), request.requestId, pending.waitingOn.size()); + const bool noWorlds = pending.waitingOn.empty(); + g_PendingPlayerActions[request.requestId] = std::move(pending); + if (noWorlds) FinishPlayerAction(request.requestId, false); + } + + void OnPlayerActionResult(const PlayerActionResult& result, const SystemAddress& sysAddr) { + const auto it = g_PendingPlayerActions.find(result.requestId); + if (it == g_PendingPlayerActions.end()) return; + it->second.affected += result.affected; + it->second.waitingOn.erase(sysAddr); + if (it->second.waitingOn.empty()) FinishPlayerAction(result.requestId, false); + } + + // World -> dashboard messages master passes on (only from worlds, only when a dashboard is connected) + template + void ForwardWorldToDashboard(const Msg& msg, const SystemAddress& sysAddr) { + if (dashboardServerMasterPeerSysAddr == UNASSIGNED_SYSTEM_ADDRESS || !Game::im->GetInstanceBySysAddr(sysAddr)) return; + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, msg); + } + + void OnAnnounce(const Announcement& announcement, const SystemAddress& sysAddr) { + if (sysAddr != dashboardServerMasterPeerSysAddr) { + LOG("Ignoring announcement from a server that is not the dashboard"); + return; + } + uint32_t worlds = 0; + for (const auto& instance : Game::im->GetInstances()) { + if (!instance || !instance->GetIsReady() || instance->GetIsShuttingDown() || !announcement.ShownIn(instance->GetMapID())) continue; + MasterPackets::SendTo(instance->GetSysAddr(), announcement); + worlds++; + } + LOG("Dashboard announcement sent to %u world(s)", worlds); + } + + void OnConfigReload(const ConfigReload& reload, const SystemAddress& sysAddr) { + if (sysAddr != dashboardServerMasterPeerSysAddr) { + LOG("Ignoring config reload from a server that is not the dashboard"); + return; + } + LOG("Reloading settings (changed on the dashboard)"); + Game::config->ReloadConfig(); + Game::im->LoadZoneLimits(); + // Everyone else: auth, chat and every world + for (const auto& peer : { authServerMasterPeerSysAddr, chatServerMasterPeerSysAddr }) { + if (peer != UNASSIGNED_SYSTEM_ADDRESS) MasterPackets::SendTo(peer, reload); + } + for (const auto& instance : Game::im->GetInstances()) { + if (instance && instance->GetIsReady()) MasterPackets::SendTo(instance->GetSysAddr(), reload); + } + } + + void OnInstanceShutdown(const InstanceShutdown& request, const SystemAddress& sysAddr) { + if (sysAddr != dashboardServerMasterPeerSysAddr) { + LOG("Ignoring instance shutdown from a server that is not the dashboard"); + return; + } + const uint32_t zoneId = request.zoneID, instanceId = request.instanceID; + const auto& instance = Game::im->FindInstanceWithPrivate(static_cast(zoneId), static_cast(instanceId)); + if (!instance) { + LOG("Dashboard asked to shut down zone %u instance %u, which isn't running", zoneId, instanceId); + return; + } + LOG("Shutting down zone %u instance %u (from the dashboard)", zoneId, instanceId); + instance->Shutdown(); + } + + void OnDashboardShutdown(const DashboardShutdown& request, const SystemAddress& sysAddr) { + if (sysAddr != dashboardServerMasterPeerSysAddr) { + LOG("Ignoring shutdown request from a server that is not the dashboard"); + return; + } + LOG("Shutdown requested from the dashboard (scheduled restart)"); + Game::lastSignal = -1; + } + + void OnMessageCaptureControl(const MessageCaptureControl& control, const SystemAddress& sysAddr) { + // Only the dashboard starts message captures; every world gets it, and the one with the player acts on it + if (sysAddr != dashboardServerMasterPeerSysAddr) { + LOG("Ignoring a message capture request from a server that is not the dashboard"); + return; + } + for (const auto& instance : Game::im->GetInstances()) { + if (instance && instance->GetIsReady() && !instance->GetIsShuttingDown()) MasterPackets::SendTo(instance->GetSysAddr(), control); + } + } + + void OnRequestServerList(const RequestServerList& request, const SystemAddress& sysAddr) { + LOG("Dashboard requested server list"); + + ServerListResponse response; + response.authOnline = authServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS ? 1 : 0; + response.chatOnline = chatServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS ? 1 : 0; + + for (const auto& inst : Game::im->GetInstances()) { + if (!inst || !inst->GetIsReady() || inst->GetIsShuttingDown()) continue; + auto& entry = response.instances.emplace_back(); + entry.mapID = inst->GetMapID(); + entry.instanceID = inst->GetInstanceID(); + entry.cloneID = inst->GetCloneID(); + entry.players = static_cast(inst->GetCurrentClientCount()); + entry.ip = LUString(inst->GetIP()); + entry.port = inst->GetPort(); + entry.isPrivate = inst->GetIsPrivate() ? 1 : 0; + } + + MasterPackets::SendTo(sysAddr, response); + } + + const PacketDispatcher& MasterHandlers() { + static const auto handlers = [] { + PacketDispatcher handlers; + using MessageType::Master; + handlers.On(Master::REQUEST_ZONE_TRANSFER, OnRequestZoneTransfer); + handlers.On(Master::SERVER_INFO, OnServerInfo); + handlers.On(Master::SET_SESSION_KEY, OnSetSessionKey); + handlers.On(Master::REQUEST_SESSION_KEY, OnRequestSessionKey); + handlers.On(Master::PLAYER_ADDED, OnPlayerAdded); + handlers.On(Master::PLAYER_REMOVED, OnPlayerRemoved); + handlers.On(Master::CREATE_PRIVATE_ZONE, OnCreatePrivateZone); + handlers.On(Master::REQUEST_PRIVATE_ZONE, OnRequestPrivateZone); + handlers.On(Master::WORLD_READY, OnWorldReady); + handlers.On(Master::PREP_ZONE, OnPrepZone); + handlers.On(Master::AFFIRM_TRANSFER_RESPONSE, OnAffirmTransferResponse); + handlers.On(Master::SHUTDOWN_RESPONSE, OnShutdownResponse); + handlers.On(Master::SHUTDOWN_UNIVERSE, OnShutdownUniverse); + handlers.On(Master::INSTANCE_MIGRATE, OnInstanceMigrate); + handlers.On(Master::MIGRATE_STATUS, [](const MigrationStatus& status, const SystemAddress& sysAddr) { MigrationCoordinator::HandleStatus(sysAddr, status); }); + handlers.On(Master::MIGRATE_PLAYER_STATE, [](const CarriedPlayerState& state, const SystemAddress& sysAddr) { MigrationCoordinator::HandleCarriedState(sysAddr, state); }); + handlers.On(Master::PLAYER_ACTION, OnPlayerAction); + handlers.On(Master::PLAYER_ACTION_RESULT, OnPlayerActionResult); + handlers.On(Master::PLAYER_POSITIONS, ForwardWorldToDashboard); + handlers.On(Master::ANNOUNCE, OnAnnounce); + handlers.On(Master::CONFIG_RELOAD, OnConfigReload); + handlers.On(Master::INSTANCE_SHUTDOWN, OnInstanceShutdown); + handlers.On(Master::DASHBOARD_SHUTDOWN, OnDashboardShutdown); + // Only world servers report game writes; pass them on unchanged + handlers.On(Master::DATA_CHANGED, ForwardWorldToDashboard); + handlers.On(Master::MESSAGE_CAPTURE_CONTROL, OnMessageCaptureControl); + handlers.On(Master::MESSAGE_CAPTURE_DATA, ForwardWorldToDashboard); + handlers.On(Master::REQUEST_SERVER_LIST, OnRequestServerList); + return handlers; + }(); + return handlers; + } +} + void HandlePacket(Packet* packet) { if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { @@ -572,11 +985,10 @@ void HandlePacket(Packet* packet) { if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS && packet->systemAddress != dashboardServerMasterPeerSysAddr) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SHUTDOWN_RESPONSE); - bitStream.Write(instance->GetMapID()); - bitStream.Write(instance->GetInstanceID()); - Game::server->Send(bitStream, dashboardServerMasterPeerSysAddr, false); + MasterPackets::WorldShutDown shutDown; + shutDown.zoneID = instance->GetMapID(); + shutDown.instanceID = instance->GetInstanceID(); + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, shutDown); } MigrationCoordinator::OnInstanceGone(*instance); @@ -587,14 +999,10 @@ void HandlePacket(Packet* packet) { chatServerMasterPeerSysAddr = UNASSIGNED_SYSTEM_ADDRESS; if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SERVER_INFO); - bitStream.Write(0); - bitStream.Write(0); - bitStream.Write(0); - bitStream.Write(ServiceType::CHAT); - bitStream.Write(LUString("offline")); - Game::server->Send(bitStream, dashboardServerMasterPeerSysAddr, false); + MasterPackets::ServerInfo offline; + offline.serverType = ServiceType::CHAT; + offline.ip = LUString("offline"); + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, offline); } StartChatServer(); @@ -604,14 +1012,10 @@ void HandlePacket(Packet* packet) { authServerMasterPeerSysAddr = UNASSIGNED_SYSTEM_ADDRESS; if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SERVER_INFO); - bitStream.Write(0); - bitStream.Write(0); - bitStream.Write(0); - bitStream.Write(ServiceType::AUTH); - bitStream.Write(LUString("offline")); - Game::server->Send(bitStream, dashboardServerMasterPeerSysAddr, false); + MasterPackets::ServerInfo offline; + offline.serverType = ServiceType::AUTH; + offline.ip = LUString("offline"); + MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, offline); } StartAuthServer(); @@ -625,566 +1029,8 @@ void HandlePacket(Packet* packet) { if (packet->length < 4) return; - if (static_cast(packet->data[1]) == ServiceType::MASTER) { - switch (static_cast(packet->data[3])) { - - case MessageType::Master::REQUEST_ZONE_TRANSFER: { - LOG("Received zone transfer req"); - CINSTREAM_SKIP_HEADER; - uint64_t requestID = 0; - uint8_t mythranShift = false; - uint32_t zoneID = 0; - uint32_t zoneClone = 0; - - inStream.Read(requestID); - inStream.Read(mythranShift); - inStream.Read(zoneID); - inStream.Read(zoneClone); - // The login stamps travelling with the request (see Stamps.h); master adds its steps - Stamps stamps; - if (!stamps.Deserialize(inStream)) stamps = {}; - if (!stamps.empty()) stamps.Add(eStamps::PASSPORT_AUTH_WORLD_PACKET_RECEIVED, zoneID); - if (shutdownSequenceStarted) { - LOG("Shutdown sequence has been started. Not creating a new zone."); - break; - } - const auto& in = Game::im->GetInstance(zoneID, false, zoneClone); - - for (const auto& instance : Game::im->GetInstances()) { - LOG("Instance: %i/%i/%i -> %i %s", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in, instance->GetSysAddr().ToString()); - } - - if (in && !in->GetIsReady()) //Instance not ready, make a pending request - { - if (!stamps.empty()) stamps.Add(eStamps::PASSPORT_AUTH_IM_LOGIN_QUEUED, in->GetInstanceID()); - in->GetPendingRequests().push_back({ requestID, static_cast(mythranShift), packet->systemAddress, stamps }); - LOG("Server not ready, adding pending request %llu %i %i", requestID, zoneID, zoneClone); - break; - } - - //Instance is ready, transfer - LOG("Responding to transfer request %llu for zone %i %i", requestID, zoneID, zoneClone); - Game::im->RequestAffirmation(in, { requestID, static_cast(mythranShift), packet->systemAddress, stamps }); - break; - } - - case MessageType::Master::SERVER_INFO: { - //MasterPackets::HandleServerInfo(packet); - - //This is here because otherwise we'd have to include IM in - //non-master servers. This packet allows us to add World - //servers back if master crashed - CINSTREAM_SKIP_HEADER; - - uint32_t theirPort = 0; - uint32_t theirZoneID = 0; - uint32_t theirInstanceID = 0; - ServiceType theirServerType; - LUString theirIP; - - inStream.Read(theirPort); - inStream.Read(theirZoneID); - inStream.Read(theirInstanceID); - inStream.Read(theirServerType); - inStream.Read(theirIP); - - switch (theirServerType) { - case ServiceType::WORLD: - if (!Game::im->IsPortInUse(theirPort)) { - auto in = std::make_unique(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12); - in->SetSysAddr(packet->systemAddress); - Game::im->AddInstance(in); - } else { - const auto& instance = Game::im->FindInstanceWithPrivate(theirZoneID, static_cast(theirInstanceID)); - if (instance) { - instance->SetSysAddr(packet->systemAddress); - } - } - break; - case ServiceType::CHAT: - chatServerMasterPeerSysAddr = packet->systemAddress; - break; - case ServiceType::AUTH: - authServerMasterPeerSysAddr = packet->systemAddress; - break; - case ServiceType::DASHBOARD: - dashboardServerMasterPeerSysAddr = packet->systemAddress; - break; - default: - break; - } - - if (theirServerType != ServiceType::DASHBOARD && dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SERVER_INFO); - bitStream.Write(theirPort); - bitStream.Write(theirZoneID); - bitStream.Write(theirInstanceID); - bitStream.Write(theirServerType); - bitStream.Write(theirIP); - Game::server->Send(bitStream, dashboardServerMasterPeerSysAddr, false); - } - - LOG("Received %s server info, instance: %i port: %i", StringifiedEnum::ToString(theirServerType).data(), theirInstanceID, theirPort); - - break; - } - - case MessageType::Master::SET_SESSION_KEY: { - CINSTREAM_SKIP_HEADER; - uint32_t sessionKey = 0; - inStream.Read(sessionKey); - LUString username; - inStream.Read(username); - - for (auto it : activeSessions) { - if (it.second == username.string) { - activeSessions.erase(it.first); - - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::NEW_SESSION_ALERT); - bitStream.Write(sessionKey); - bitStream.Write(username); - SEND_PACKET_BROADCAST; - - break; - } - } - - activeSessions.insert(std::make_pair(sessionKey, username.string)); - LOG("Got sessionKey %i for user %s", sessionKey, username.string.c_str()); - break; - } - - case MessageType::Master::REQUEST_SESSION_KEY: { - CINSTREAM_SKIP_HEADER; - LUWString username; - inStream.Read(username); - LOG("Requesting session key for %s", username.GetAsString().c_str()); - for (auto key : activeSessions) { - if (key.second == username.GetAsString()) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SESSION_KEY_RESPONSE); - bitStream.Write(key.first); - bitStream.Write(username); - Game::server->Send(bitStream, packet->systemAddress, false); - break; - } - } - break; - } - - case MessageType::Master::PLAYER_ADDED: { - CINSTREAM_SKIP_HEADER; - - LWOMAPID theirZoneID = 0; - LWOINSTANCEID theirInstanceID = 0; - - inStream.Read(theirZoneID); - inStream.Read(theirInstanceID); - - const auto& instance = - Game::im->FindInstanceWithPrivate(theirZoneID, theirInstanceID); - if (instance) { - instance->AddPlayer(Player()); - } else { - LOG("Instance missing? What?"); - } - - if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_ADDED); - bitStream.Write(theirZoneID); - bitStream.Write(theirInstanceID); - Game::server->Send(bitStream, dashboardServerMasterPeerSysAddr, false); - } - break; - } - - case MessageType::Master::PLAYER_REMOVED: { - CINSTREAM_SKIP_HEADER; - - LWOMAPID theirZoneID = 0; - LWOINSTANCEID theirInstanceID = 0; - - inStream.Read(theirZoneID); - inStream.Read(theirInstanceID); - - const auto& instance = - Game::im->FindInstance(theirZoneID, theirInstanceID); - if (instance) { - instance->RemovePlayer(Player()); - } - - if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_REMOVED); - bitStream.Write(theirZoneID); - bitStream.Write(theirInstanceID); - Game::server->Send(bitStream, dashboardServerMasterPeerSysAddr, false); - } - break; - } - - case MessageType::Master::CREATE_PRIVATE_ZONE: { - CINSTREAM_SKIP_HEADER; - - uint32_t mapId; - LWOCLONEID cloneId; - std::string password; - - inStream.Read(mapId); - inStream.Read(cloneId); - - uint32_t len; - inStream.Read(len); - len = std::min(len, 50); // cap the master password at 50 characters - - for (uint32_t i = 0; len > i; i++) { - char character; - inStream.Read(character); - password += character; - } - const auto& newInst = Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str()); - LOG("Creating private zone %i/%i/%i", newInst->GetMapID(), newInst->GetCloneID(), newInst->GetInstanceID()); - - break; - } - - case MessageType::Master::REQUEST_PRIVATE_ZONE: { - CINSTREAM_SKIP_HEADER; - - uint64_t requestID = 0; - uint8_t mythranShift = false; - - std::string password; - - inStream.Read(requestID); - inStream.Read(mythranShift); - - uint32_t len; - inStream.Read(len); - len = std::min(len, 50); - - for (uint32_t i = 0; i < len; i++) { - char character; inStream.Read(character); - password += character; - } - - const auto& instance = Game::im->FindPrivateInstance(password.c_str()); - - LOG("Join private zone: %llu %d %p", requestID, mythranShift, instance.get()); - - if (instance == nullptr) { - return; - } - - const auto& zone = instance->GetZoneID(); - - MasterPackets::SendZoneTransferResponse(Game::server, packet->systemAddress, requestID, static_cast(mythranShift), zone.GetMapID(), instance->GetInstanceID(), zone.GetCloneID(), instance->GetIP(), instance->GetPort()); - - break; - } - - case MessageType::Master::WORLD_READY: { - CINSTREAM_SKIP_HEADER; - - LWOMAPID zoneID; - LWOINSTANCEID instanceID; - - inStream.Read(zoneID); - inStream.Read(instanceID); - - LOG("Got world ready %i %i", zoneID, instanceID); - - const auto& instance = Game::im->FindInstanceWithPrivate(zoneID, instanceID); - - if (instance == nullptr) { - LOG("Failed to find zone to ready"); - return; - } - - LOG("Ready zone %i", zoneID); - Game::im->ReadyInstance(instance); - - if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::WORLD_READY); - bitStream.Write(zoneID); - bitStream.Write(instanceID); - bitStream.Write(instance->GetCloneID()); - bitStream.Write(LUString(instance->GetIP())); - bitStream.Write(instance->GetPort()); - bitStream.Write(instance->GetIsPrivate() ? 1 : 0); - Game::server->Send(bitStream, dashboardServerMasterPeerSysAddr, false); - } - break; - } - - case MessageType::Master::PREP_ZONE: { - CINSTREAM_SKIP_HEADER; - - int32_t zoneID; - inStream.Read(zoneID); - if (shutdownSequenceStarted) { - LOG("Shutdown sequence has been started. Not prepping a new zone."); - break; - } else { - LOG("Prepping zone %i", zoneID); - Game::im->GetInstance(zoneID, false, 0); - } - break; - } - - case MessageType::Master::AFFIRM_TRANSFER_RESPONSE: { - CINSTREAM_SKIP_HEADER; - - uint64_t requestID; - - inStream.Read(requestID); - - LOG("Got affirmation of transfer %llu", requestID); - - const auto& instance = Game::im->GetInstanceBySysAddr(packet->systemAddress); - - if (instance == nullptr) - return; - - Game::im->AffirmTransfer(instance, requestID); - LOG("Affirmation complete %llu", requestID); - break; - } - - case MessageType::Master::SHUTDOWN_RESPONSE: { - CINSTREAM_SKIP_HEADER; - - const auto& instance = Game::im->GetInstanceBySysAddr(packet->systemAddress); - LOG("Got shutdown response from %s", packet->systemAddress.ToString()); - if (instance == nullptr) { - return; - } - - LOG("Got shutdown response from zone %i clone %i instance %i port %i", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance->GetPort()); - instance->SetIsShuttingDown(true); - break; - } - - case MessageType::Master::SHUTDOWN_UNIVERSE: { - LOG("Received shutdown universe command, shutting down in 10 minutes."); - Game::universeShutdownRequested = true; - break; - } - - case MessageType::Master::INSTANCE_MIGRATE: { - // Only servers connected to master can send this (a world, for a GM's /replaceinstance or /mergeinstance) - CINSTREAM_SKIP_HEADER; - InstanceMigrationRequest request; - if (!request.Deserialize(inStream)) break; - if (shutdownSequenceStarted) { - LOG("Shutdown sequence has been started. Not starting instance migration %u.", request.requestId); - break; - } - MigrationCoordinator::Start(request); - break; - } - - case MessageType::Master::MIGRATE_STATUS: { - CINSTREAM_SKIP_HEADER; - MigrationStatus status; - if (status.Deserialize(inStream)) MigrationCoordinator::HandleStatus(packet->systemAddress, status); - break; - } - - case MessageType::Master::MIGRATE_PLAYER_STATE: { - CINSTREAM_SKIP_HEADER; - CarriedPlayerState state; - if (state.Deserialize(inStream)) MigrationCoordinator::HandleCarriedState(packet->systemAddress, state, packet->data, packet->length); - break; - } - - case MessageType::Master::PLAYER_ACTION: { - // Only the dashboard may ask worlds to act on players - if (packet->systemAddress != dashboardServerMasterPeerSysAddr) { - LOG("Ignoring player action from a server that is not the dashboard"); - break; - } - - CINSTREAM_SKIP_HEADER; - PlayerActionRequest request; - if (!request.Deserialize(inStream)) break; - - PendingPlayerAction pending; - pending.action = request.action; - pending.deadline = std::chrono::steady_clock::now() + PLAYER_ACTION_TIMEOUT; - - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_ACTION); - request.Serialize(bitStream); - for (const auto& instance : Game::im->GetInstances()) { - if (!instance || !instance->GetIsReady() || instance->GetIsShuttingDown()) continue; - Game::server->Send(bitStream, instance->GetSysAddr(), false); - pending.waitingOn.insert(instance->GetSysAddr()); - } - // The chat server checks web chat with the same filter: it reloads too, but doesn't answer - if (request.action == ePlayerAction::RELOAD_CHAT_FILTER && chatServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) { - Game::server->Send(bitStream, chatServerMasterPeerSysAddr, false); - } - - LOG("Dashboard player action %i (request %u) sent to %zu world(s)", static_cast(request.action), request.requestId, pending.waitingOn.size()); - const bool noWorlds = pending.waitingOn.empty(); - g_PendingPlayerActions[request.requestId] = std::move(pending); - if (noWorlds) FinishPlayerAction(request.requestId, false); - break; - } - - case MessageType::Master::PLAYER_ACTION_RESULT: { - CINSTREAM_SKIP_HEADER; - PlayerActionResult result; - if (!result.Deserialize(inStream)) break; - - const auto it = g_PendingPlayerActions.find(result.requestId); - if (it == g_PendingPlayerActions.end()) break; - it->second.affected += result.affected; - it->second.waitingOn.erase(packet->systemAddress); - if (it->second.waitingOn.empty()) FinishPlayerAction(result.requestId, false); - break; - } - - case MessageType::Master::PLAYER_POSITIONS: { - if (dashboardServerMasterPeerSysAddr == UNASSIGNED_SYSTEM_ADDRESS || !Game::im->GetInstanceBySysAddr(packet->systemAddress)) break; - RakNet::BitStream forward(packet->data, packet->length, false); - Game::server->Send(forward, dashboardServerMasterPeerSysAddr, false); - break; - } - - case MessageType::Master::ANNOUNCE: { - if (packet->systemAddress != dashboardServerMasterPeerSysAddr) { - LOG("Ignoring announcement from a server that is not the dashboard"); - break; - } - CINSTREAM_SKIP_HEADER; - Announcement announcement; - if (!announcement.Deserialize(inStream)) break; - RakNet::BitStream forward(packet->data, packet->length, false); - uint32_t worlds = 0; - for (const auto& instance : Game::im->GetInstances()) { - if (!instance || !instance->GetIsReady() || instance->GetIsShuttingDown() || !announcement.ShownIn(instance->GetMapID())) continue; - Game::server->Send(forward, instance->GetSysAddr(), false); - worlds++; - } - LOG("Dashboard announcement sent to %u world(s)", worlds); - break; - } - - case MessageType::Master::CONFIG_RELOAD: { - if (packet->systemAddress != dashboardServerMasterPeerSysAddr) { - LOG("Ignoring config reload from a server that is not the dashboard"); - break; - } - LOG("Reloading settings (changed on the dashboard)"); - Game::config->ReloadConfig(); - Game::im->LoadZoneLimits(); - // Everyone else: auth, chat and every world - RakNet::BitStream forward(packet->data, packet->length, false); - for (const auto& peer : { authServerMasterPeerSysAddr, chatServerMasterPeerSysAddr }) { - if (peer != UNASSIGNED_SYSTEM_ADDRESS) Game::server->Send(forward, peer, false); - } - for (const auto& instance : Game::im->GetInstances()) { - if (instance && instance->GetIsReady()) Game::server->Send(forward, instance->GetSysAddr(), false); - } - break; - } - - case MessageType::Master::INSTANCE_SHUTDOWN: { - if (packet->systemAddress != dashboardServerMasterPeerSysAddr) { - LOG("Ignoring instance shutdown from a server that is not the dashboard"); - break; - } - CINSTREAM_SKIP_HEADER; - uint32_t zoneId = 0, instanceId = 0; - if (!inStream.Read(zoneId) || !inStream.Read(instanceId)) break; - const auto& instance = Game::im->FindInstanceWithPrivate(static_cast(zoneId), static_cast(instanceId)); - if (!instance) { - LOG("Dashboard asked to shut down zone %u instance %u, which isn't running", zoneId, instanceId); - break; - } - LOG("Shutting down zone %u instance %u (from the dashboard)", zoneId, instanceId); - instance->Shutdown(); - break; - } - - case MessageType::Master::DASHBOARD_SHUTDOWN: { - if (packet->systemAddress != dashboardServerMasterPeerSysAddr) { - LOG("Ignoring shutdown request from a server that is not the dashboard"); - break; - } - LOG("Shutdown requested from the dashboard (scheduled restart)"); - Game::lastSignal = -1; - break; - } - - case MessageType::Master::DATA_CHANGED: { - // Only world servers report game writes; pass them on unchanged - if (dashboardServerMasterPeerSysAddr == UNASSIGNED_SYSTEM_ADDRESS || !Game::im->GetInstanceBySysAddr(packet->systemAddress)) break; - RakNet::BitStream forward(packet->data, packet->length, false); - Game::server->Send(forward, dashboardServerMasterPeerSysAddr, false); - break; - } - - case MessageType::Master::MESSAGE_CAPTURE_CONTROL: { - // Only the dashboard starts message captures; every world gets it, and the one with the player acts on it - if (packet->systemAddress != dashboardServerMasterPeerSysAddr) { - LOG("Ignoring a message capture request from a server that is not the dashboard"); - break; - } - RakNet::BitStream forward(packet->data, packet->length, false); - for (const auto& instance : Game::im->GetInstances()) { - if (instance && instance->GetIsReady() && !instance->GetIsShuttingDown()) Game::server->Send(forward, instance->GetSysAddr(), false); - } - break; - } - - case MessageType::Master::MESSAGE_CAPTURE_DATA: { - if (dashboardServerMasterPeerSysAddr == UNASSIGNED_SYSTEM_ADDRESS || !Game::im->GetInstanceBySysAddr(packet->systemAddress)) break; - RakNet::BitStream forward(packet->data, packet->length, false); - Game::server->Send(forward, dashboardServerMasterPeerSysAddr, false); - break; - } - - case MessageType::Master::REQUEST_SERVER_LIST: { - LOG("Dashboard requested server list"); - - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SERVER_LIST_RESPONSE); - - bitStream.Write(authServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS ? 1 : 0); - bitStream.Write(chatServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS ? 1 : 0); - - const auto& instances = Game::im->GetInstances(); - uint32_t instanceCount = 0; - for (const auto& inst : instances) { - if (inst && inst->GetIsReady() && !inst->GetIsShuttingDown()) instanceCount++; - } - bitStream.Write(instanceCount); - - for (const auto& inst : instances) { - if (!inst || !inst->GetIsReady() || inst->GetIsShuttingDown()) continue; - bitStream.Write(inst->GetMapID()); - bitStream.Write(inst->GetInstanceID()); - bitStream.Write(inst->GetCloneID()); - bitStream.Write(inst->GetCurrentClientCount()); - bitStream.Write(LUString(inst->GetIP())); - bitStream.Write(inst->GetPort()); - bitStream.Write(inst->GetIsPrivate() ? 1 : 0); - } - - Game::server->Send(bitStream, packet->systemAddress, false); - break; - } - - default: - LOG("Unknown master packet ID from server: %i", packet->data[3]); - } + if (!MasterHandlers().Dispatch(packet, ServiceType::MASTER) && packet->data[0] == ID_USER_PACKET_ENUM && static_cast(packet->data[1]) == ServiceType::MASTER) { + LOG("Unknown master packet ID from server: %i", packet->data[3]); } } @@ -1205,9 +1051,7 @@ int ShutdownSequence(int32_t signal) { Game::lastSignal = -1; { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SHUTDOWN); - Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true); + MasterPackets::Shutdown().Broadcast(); LOG("Triggered master shutdown"); } diff --git a/dMasterServer/MigrationCoordinator.cpp b/dMasterServer/MigrationCoordinator.cpp index f7e5ca8c1..f82e2ae49 100644 --- a/dMasterServer/MigrationCoordinator.cpp +++ b/dMasterServer/MigrationCoordinator.cpp @@ -8,6 +8,7 @@ #include "CDClientManager.h" #include "Game.h" #include "InstanceManager.h" +#include "MasterPackets.h" #include "Logger.h" #include "MessageType/Master.h" #include "ServiceType.h" @@ -114,10 +115,7 @@ namespace { order.targetZone = migration.zone; order.targetInstance = migration.target; order.targetPort = 0; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::MIGRATE_PLAYERS); - order.Serialize(bitStream); - Game::server->Send(bitStream, source->GetSysAddr(), false); + MasterPackets::SendTo(source->GetSysAddr(), order); } // Ends a migration. The source goes back to taking players unless it is being shut down. @@ -153,10 +151,7 @@ namespace { // Without a loading screen the "dimensional shift" notice would be the only sign; leave it out then order.seamless = migration.seamless; order.mythranShift = !migration.seamless; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::MIGRATE_PLAYERS); - order.Serialize(bitStream); - Game::server->Send(bitStream, source.GetSysAddr(), false); + MasterPackets::SendTo(source.GetSysAddr(), order); migration.state = migration.warnSeconds > 0 ? eState::WARNING : eState::MOVING; migration.deadline = Clock::now() + std::chrono::seconds(migration.warnSeconds) + MOVE_TIMEOUT; @@ -257,15 +252,14 @@ void MigrationCoordinator::HandleStatus(const SystemAddress& from, const Migrati Report(migration, status.state, status.message, status.remaining); } -void MigrationCoordinator::HandleCarriedState(const SystemAddress& from, const CarriedPlayerState& state, const unsigned char* data, uint32_t length) { +void MigrationCoordinator::HandleCarriedState(const SystemAddress& from, const CarriedPlayerState& state) { for (const auto& [id, migration] : g_Active) { if (migration.zone != state.targetZone || migration.target != state.targetInstance) continue; const auto& source = FindInstance(migration.zone, migration.source); if (!source || source->GetSysAddr() != from) continue; const auto& target = FindInstance(migration.zone, migration.target); if (!target) return; - RakNet::BitStream forward(const_cast(data), length, false); - Game::server->Send(forward, target->GetSysAddr(), false); + MasterPackets::SendTo(target->GetSysAddr(), state); return; } } diff --git a/dMasterServer/MigrationCoordinator.h b/dMasterServer/MigrationCoordinator.h index a2bb0f893..ab5f244a1 100644 --- a/dMasterServer/MigrationCoordinator.h +++ b/dMasterServer/MigrationCoordinator.h @@ -3,7 +3,7 @@ #include -#include "InstanceMigration.h" +#include "master/InstanceMigration.h" #include "RakNetTypes.h" class Instance; @@ -25,7 +25,7 @@ namespace MigrationCoordinator { void HandleStatus(const SystemAddress& from, const MigrationStatus& status); // A source world sent state to carry over for one player: passed on to the target world as is - void HandleCarriedState(const SystemAddress& from, const CarriedPlayerState& state, const unsigned char* data, uint32_t length); + void HandleCarriedState(const SystemAddress& from, const CarriedPlayerState& state); // A world server went away; migrations it was part of end void OnInstanceGone(const Instance& instance); diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index f2c8c3e7d..6bde1b3e5 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -1,4 +1,5 @@ #include "AuthPackets.h" +#include "MasterPackets.h" #include #include "BitStreamUtils.h" @@ -294,12 +295,11 @@ void AuthPackets::SendLoginResponse(dServer* server, const SystemAddress& sysAdd //Inform the master server that we've created a session for this user, before the client can reach the world server: if (responseCode == eLoginResponse::SUCCESS) { stamps.Add(eStamps::PASSPORT_AUTH_IM_COMMUNICATION_START); - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SET_SESSION_KEY); - bitStream.Write(sessionKey); - bitStream.Write(LUString(username)); + MasterPackets::SetSessionKey setSessionKey; + setSessionKey.sessionKey = sessionKey; + setSessionKey.username = LUString(username); stamps.Add(eStamps::PASSPORT_AUTH_IM_LOGIN_START); - server->SendToMaster(bitStream); + MasterPackets::SendToMaster(setSessionKey, server); stamps.Add(eStamps::PASSPORT_AUTH_IM_COMMUNICATION_END, 1); LOG("Set session key for user %s", username.c_str()); diff --git a/dNet/MasterPackets.cpp b/dNet/MasterPackets.cpp index 543ca4ee4..d3935ae77 100644 --- a/dNet/MasterPackets.cpp +++ b/dNet/MasterPackets.cpp @@ -2,109 +2,274 @@ #include "BitStream.h" #include "dCommonVars.h" #include "dServer.h" +#include "Game.h" #include "ServiceType.h" #include "MessageType/Master.h" #include "BitStreamUtils.h" +#include #include -void MasterPackets::SendZoneTransferRequest(dServer* server, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t cloneID, const Stamps& stamps) { - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER); - - bitStream.Write(requestID); - bitStream.Write(mythranShift); - bitStream.Write(zoneID); - bitStream.Write(cloneID); - stamps.Serialize(bitStream); - - server->SendToMaster(bitStream); +namespace { + // u32 length and 1 byte per character. A longer string is cut to maxLength when read (the rest is skipped). + bool ReadCappedString(RakNet::BitStream& bitStream, std::string& value, const uint32_t maxLength) { + uint32_t length{}; + VALIDATE_READ(bitStream.Read(length)); + value.resize(std::min(length, maxLength)); + return value.empty() || bitStream.ReadBits(reinterpret_cast(value.data()), BYTES_TO_BITS(value.size()), true); + } } -void MasterPackets::SendZoneCreatePrivate(dServer* server, uint32_t zoneID, uint32_t cloneID, const std::string& password) { +void MasterPackets::SendToMaster(const LUBitStream& msg, dServer* server) { RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::CREATE_PRIVATE_ZONE); + msg.WritePacket(bitStream); + (server ? server : Game::server)->SendToMaster(bitStream); +} - bitStream.Write(zoneID); - bitStream.Write(cloneID); +void MasterPackets::SendTo(const SystemAddress& sysAddr, const LUBitStream& msg) { + RakNet::BitStream bitStream; + msg.WritePacket(bitStream); + Game::server->Send(bitStream, sysAddr, false); +} - bitStream.Write(password.size()); - for (auto character : password) { - bitStream.Write(character); +namespace MasterPackets { + void RequestZoneTransfer::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(requestID); + bitStream.Write(mythranShift); + bitStream.Write(zoneID); + bitStream.Write(cloneID); + stamps.Serialize(bitStream); } - server->SendToMaster(bitStream); -} - -void MasterPackets::SendZoneRequestPrivate(dServer* server, uint64_t requestID, bool mythranShift, const std::string& password) { - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_PRIVATE_ZONE); - - bitStream.Write(requestID); - bitStream.Write(mythranShift); - - bitStream.Write(password.size()); - for (auto character : password) { - bitStream.Write(character); + bool RequestZoneTransfer::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(requestID)); + VALIDATE_READ(bitStream.Read(mythranShift)); + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(cloneID)); + if (!stamps.Deserialize(bitStream)) stamps = {}; + return true; } - server->SendToMaster(bitStream); -} - -void MasterPackets::SendWorldReady(dServer* server, LWOMAPID zoneId, LWOINSTANCEID instanceId) { - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::WORLD_READY); - - bitStream.Write(zoneId); - bitStream.Write(instanceId); - - server->SendToMaster(bitStream); -} - -void MasterPackets::SendZoneTransferResponse(dServer* server, const SystemAddress& sysAddr, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, const std::string& serverIP, uint32_t serverPort, const Stamps& stamps) { - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER_RESPONSE); - - bitStream.Write(requestID); - bitStream.Write(mythranShift); - bitStream.Write(zoneID); - bitStream.Write(zoneInstance); - bitStream.Write(zoneClone); - bitStream.Write(serverPort); - bitStream.Write(LUString(serverIP, 255)); - stamps.Serialize(bitStream); - - server->Send(bitStream, sysAddr, false); -} - -void MasterPackets::HandleServerInfo(Packet* packet) { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header{}; - inStream.Read(header); - - uint32_t theirPort = 0; - uint32_t theirZoneID = 0; - uint32_t theirInstanceID = 0; - LUString theirIP; - - inStream.Read(theirPort); - inStream.Read(theirZoneID); - inStream.Read(theirInstanceID); - inStream.Read(theirIP); - - //TODO: Actually mark this server as an available server in the manager -} - -void MasterPackets::SendServerInfo(dServer* server, Packet* packet) { - LOG("SendServerInfo called for server type %i", static_cast(server->GetServerType())); - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SERVER_INFO); - - bitStream.Write(server->GetPort()); - bitStream.Write(server->GetZoneID()); - bitStream.Write(server->GetInstanceID()); - bitStream.Write(server->GetServerType()); - bitStream.Write(LUString(server->GetIP())); - - server->SendToMaster(bitStream); + void RequestZoneTransferResponse::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(requestID); + bitStream.Write(mythranShift); + bitStream.Write(zoneID); + bitStream.Write(zoneInstance); + bitStream.Write(zoneClone); + bitStream.Write(serverPort); + bitStream.Write(serverIP); + stamps.Serialize(bitStream); + } + + bool RequestZoneTransferResponse::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(requestID)); + VALIDATE_READ(bitStream.Read(mythranShift)); + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(zoneInstance)); + VALIDATE_READ(bitStream.Read(zoneClone)); + VALIDATE_READ(bitStream.Read(serverPort)); + VALIDATE_READ(bitStream.Read(serverIP)); + if (!stamps.Deserialize(bitStream)) stamps = {}; + return true; + } + + void ServerInfo::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(port); + bitStream.Write(zoneID); + bitStream.Write(instanceID); + bitStream.Write(serverType); + bitStream.Write(ip); + } + + bool ServerInfo::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(port)); + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(instanceID)); + VALIDATE_READ(bitStream.Read(serverType)); + VALIDATE_READ(bitStream.Read(ip)); + return true; + } + + void RequestSessionKey::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(username); + } + + bool RequestSessionKey::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(username)); + return true; + } + + void SetSessionKey::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(sessionKey); + bitStream.Write(username); + } + + bool SetSessionKey::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(sessionKey)); + VALIDATE_READ(bitStream.Read(username)); + return true; + } + + void SessionKeyResponse::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(sessionKey); + bitStream.Write(username); + } + + bool SessionKeyResponse::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(sessionKey)); + VALIDATE_READ(bitStream.Read(username)); + return true; + } + + void NewSessionAlert::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(sessionKey); + bitStream.Write(username); + } + + bool NewSessionAlert::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(sessionKey)); + VALIDATE_READ(bitStream.Read(username)); + return true; + } + + void PlayerCountChange::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(zoneID); + bitStream.Write(instanceID); + } + + bool PlayerCountChange::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(instanceID)); + return true; + } + + void CreatePrivateZone::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(zoneID); + bitStream.Write(cloneID); + BitStreamUtils::WriteLengthPrefixed(bitStream, password); + } + + bool CreatePrivateZone::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(cloneID)); + VALIDATE_READ(ReadCappedString(bitStream, password, MAX_PASSWORD_LENGTH)); + return true; + } + + void RequestPrivateZone::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(requestID); + bitStream.Write(mythranShift); + BitStreamUtils::WriteLengthPrefixed(bitStream, password); + } + + bool RequestPrivateZone::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(requestID)); + VALIDATE_READ(bitStream.Read(mythranShift)); + VALIDATE_READ(ReadCappedString(bitStream, password, MAX_PASSWORD_LENGTH)); + return true; + } + + void WorldReady::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(zoneID); + bitStream.Write(instanceID); + } + + bool WorldReady::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(instanceID)); + return true; + } + + void WorldReadyInfo::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(zoneID); + bitStream.Write(instanceID); + bitStream.Write(cloneID); + bitStream.Write(ip); + bitStream.Write(port); + bitStream.Write(isPrivate); + } + + bool WorldReadyInfo::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(instanceID)); + VALIDATE_READ(bitStream.Read(cloneID)); + VALIDATE_READ(bitStream.Read(ip)); + VALIDATE_READ(bitStream.Read(port)); + VALIDATE_READ(bitStream.Read(isPrivate)); + return true; + } + + void PrepZone::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(zoneID); + } + + bool PrepZone::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(zoneID)); + return true; + } + + void WorldShutDown::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(zoneID); + bitStream.Write(instanceID); + } + + bool WorldShutDown::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(instanceID)); + return true; + } + + void AffirmTransfer::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(requestID); + } + + bool AffirmTransfer::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(requestID)); + return true; + } + + void ServerListResponse::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(authOnline); + bitStream.Write(chatOnline); + bitStream.Write(instances.size()); + for (const auto& instance : instances) { + bitStream.Write(instance.mapID); + bitStream.Write(instance.instanceID); + bitStream.Write(instance.cloneID); + bitStream.Write(instance.players); + bitStream.Write(instance.ip); + bitStream.Write(instance.port); + bitStream.Write(instance.isPrivate); + } + } + + bool ServerListResponse::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(authOnline)); + VALIDATE_READ(bitStream.Read(chatOnline)); + uint32_t count{}; + VALIDATE_READ(bitStream.Read(count)); + if (count > MAX_INSTANCES) return false; + instances.resize(count); + for (auto& instance : instances) { + VALIDATE_READ(bitStream.Read(instance.mapID)); + VALIDATE_READ(bitStream.Read(instance.instanceID)); + VALIDATE_READ(bitStream.Read(instance.cloneID)); + VALIDATE_READ(bitStream.Read(instance.players)); + VALIDATE_READ(bitStream.Read(instance.ip)); + VALIDATE_READ(bitStream.Read(instance.port)); + VALIDATE_READ(bitStream.Read(instance.isPrivate)); + } + return true; + } + + void InstanceShutdown::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(zoneID); + bitStream.Write(instanceID); + } + + bool InstanceShutdown::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(zoneID)); + VALIDATE_READ(bitStream.Read(instanceID)); + return true; + } } diff --git a/dNet/MasterPackets.h b/dNet/MasterPackets.h index c86d7e241..c11177c86 100644 --- a/dNet/MasterPackets.h +++ b/dNet/MasterPackets.h @@ -3,24 +3,290 @@ #include #include +#include #include "RakNetTypes.h" #include "dCommonVars.h" +#include "BitStreamUtils.h" +#include "MessageType/Master.h" +#include "ServiceType.h" #include "Stamps.h" +#include "master/DashboardMessages.h" +#include "master/DataChanged.h" +#include "master/InstanceMigration.h" +#include "master/MessageCapture.h" +#include "master/PlayerAction.h" + class dServer; +/** + * Packets between master and the other servers (MessageType::Master). None of them reach a client. + * + * The dashboard's messages and instance migration are MASTER packets too; they live in per-topic headers under + * dNet/master/ (PlayerAction.h, DataChanged.h, DashboardMessages.h, MessageCapture.h, InstanceMigration.h), which + * this header includes, so including MasterPackets.h gives every MASTER struct. + */ namespace MasterPackets { - // stamps: the login stamps travelling with the request (empty unless auth asks for a world during a login) - void SendZoneTransferRequest(dServer* server, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t cloneID, const Stamps& stamps = {}); - void SendZoneTransferResponse(dServer* server, const SystemAddress& sysAddr, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, const std::string& serverIP, uint32_t serverPort, const Stamps& stamps = {}); + // Any server -> master, over server's connection to master (Game::server's when null) + void SendToMaster(const LUBitStream& msg, dServer* server = nullptr); - void HandleServerInfo(Packet* packet); - void SendServerInfo(dServer* server, Packet* packet); + // Master -> one server. Never broadcasts: nothing is sent to UNASSIGNED_SYSTEM_ADDRESS (a server that hasn't + // connected yet), unlike LUBitStream::Send. + void SendTo(const SystemAddress& sysAddr, const LUBitStream& msg); - void SendZoneCreatePrivate(dServer* server, uint32_t zoneID, uint32_t cloneID, const std::string& password); + // World -> master: a player wants to go to zoneID (clone cloneID) + struct RequestZoneTransfer : public LUBitStream { + uint64_t requestID{}; + uint8_t mythranShift{}; + uint32_t zoneID{}; + uint32_t cloneID{}; + // The login stamps travelling with the request (see Stamps.h); empty unless auth asks for a world during a + // login. Read leniently: a request without them reads as empty. + Stamps stamps{}; - void SendZoneRequestPrivate(dServer* server, uint64_t requestID, bool mythranShift, const std::string& password); + RequestZoneTransfer() : LUBitStream(ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; - void SendWorldReady(dServer* server, LWOMAPID zoneId, LWOINSTANCEID instanceId); + // Master -> world: where to send the player of requestID + struct RequestZoneTransferResponse : public LUBitStream { + uint64_t requestID{}; + uint8_t mythranShift{}; + uint32_t zoneID{}; + uint32_t zoneInstance{}; + uint32_t zoneClone{}; + uint16_t serverPort{}; + LUString serverIP{ 255u }; + // The request's login stamps with master's own added (see Stamps.h). Read leniently like the request's. + Stamps stamps{}; + + RequestZoneTransferResponse() : LUBitStream(ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER_RESPONSE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Any server -> master, once connected; master -> dashboard for every other server (ip "offline" when one is gone) + struct ServerInfo : public LUBitStream { + uint32_t port{}; + uint32_t zoneID{}; + uint32_t instanceID{}; + ServiceType serverType{}; + LUString ip{}; + + ServerInfo() : LUBitStream(ServiceType::MASTER, MessageType::Master::SERVER_INFO) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // World -> master: the session key of the account logging in + struct RequestSessionKey : public LUBitStream { + LUWString username{}; + + RequestSessionKey() : LUBitStream(ServiceType::MASTER, MessageType::Master::REQUEST_SESSION_KEY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Auth -> master: an account logged in with this session key + struct SetSessionKey : public LUBitStream { + uint32_t sessionKey{}; + LUString username{}; + + SetSessionKey() : LUBitStream(ServiceType::MASTER, MessageType::Master::SET_SESSION_KEY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Master -> world: answer to RequestSessionKey + struct SessionKeyResponse : public LUBitStream { + uint32_t sessionKey{}; + LUWString username{}; + + SessionKeyResponse() : LUBitStream(ServiceType::MASTER, MessageType::Master::SESSION_KEY_RESPONSE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Master -> every server: the account logged in again with a new session key + struct NewSessionAlert : public LUBitStream { + uint32_t sessionKey{}; + LUString username{}; + + NewSessionAlert() : LUBitStream(ServiceType::MASTER, MessageType::Master::NEW_SESSION_ALERT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // World -> master -> dashboard: a player came into (PLAYER_ADDED) or left (PLAYER_REMOVED) a world + struct PlayerCountChange : public LUBitStream { + LWOMAPID zoneID{}; + LWOINSTANCEID instanceID{}; + + PlayerCountChange(MessageType::Master id) : LUBitStream(ServiceType::MASTER, id) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + struct PlayerAdded : public PlayerCountChange { + PlayerAdded() : PlayerCountChange(MessageType::Master::PLAYER_ADDED) {} + }; + + struct PlayerRemoved : public PlayerCountChange { + PlayerRemoved() : PlayerCountChange(MessageType::Master::PLAYER_REMOVED) {} + }; + + // World -> master: start a private instance of zoneID that needs `password` to join + struct CreatePrivateZone : public LUBitStream { + // Longer passwords are cut to this when read + static constexpr uint32_t MAX_PASSWORD_LENGTH = 50; + + uint32_t zoneID{}; + uint32_t cloneID{}; + std::string password; // u32 length, then 1 byte per character + + CreatePrivateZone() : LUBitStream(ServiceType::MASTER, MessageType::Master::CREATE_PRIVATE_ZONE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // World -> master: send the player of requestID to the private instance with this password + struct RequestPrivateZone : public LUBitStream { + // Longer passwords are cut to this when read + static constexpr uint32_t MAX_PASSWORD_LENGTH = 50; + + uint64_t requestID{}; + uint8_t mythranShift{}; + std::string password; // u32 length, then 1 byte per character + + RequestPrivateZone() : LUBitStream(ServiceType::MASTER, MessageType::Master::REQUEST_PRIVATE_ZONE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // World -> master: the world finished loading and takes players + struct WorldReady : public LUBitStream { + LWOMAPID zoneID{}; + LWOINSTANCEID instanceID{}; + + WorldReady() : LUBitStream(ServiceType::MASTER, MessageType::Master::WORLD_READY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Master -> dashboard (WORLD_READY): a world is ready, and where it is + struct WorldReadyInfo : public LUBitStream { + LWOMAPID zoneID{}; + LWOINSTANCEID instanceID{}; + LWOCLONEID cloneID{}; + LUString ip{}; + uint32_t port{}; + uint8_t isPrivate{}; + + WorldReadyInfo() : LUBitStream(ServiceType::MASTER, MessageType::Master::WORLD_READY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // World -> master: start an instance of zoneID now, players are about to ask for it + struct PrepZone : public LUBitStream { + int32_t zoneID{}; + + PrepZone() : LUBitStream(ServiceType::MASTER, MessageType::Master::PREP_ZONE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Master -> any server: shut down + struct Shutdown : public LUBitStream { + Shutdown() : LUBitStream(ServiceType::MASTER, MessageType::Master::SHUTDOWN) {} + }; + + // World -> master: shutting down + struct ShutdownResponse : public LUBitStream { + ShutdownResponse() : LUBitStream(ServiceType::MASTER, MessageType::Master::SHUTDOWN_RESPONSE) {} + }; + + // Master -> dashboard (SHUTDOWN_RESPONSE): a world is gone + struct WorldShutDown : public LUBitStream { + LWOMAPID zoneID{}; + LWOINSTANCEID instanceID{}; + + WorldShutDown() : LUBitStream(ServiceType::MASTER, MessageType::Master::SHUTDOWN_RESPONSE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // World -> master: a GM asked to shut every server down in 10 minutes + struct ShutdownUniverse : public LUBitStream { + ShutdownUniverse() : LUBitStream(ServiceType::MASTER, MessageType::Master::SHUTDOWN_UNIVERSE) {} + }; + + // Master -> world: can you take the player of requestID? (AFFIRM_TRANSFER_REQUEST); world -> master: yes + // (AFFIRM_TRANSFER_RESPONSE) + struct AffirmTransfer : public LUBitStream { + uint64_t requestID{}; + + AffirmTransfer(MessageType::Master id) : LUBitStream(ServiceType::MASTER, id) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + struct AffirmTransferRequest : public AffirmTransfer { + AffirmTransferRequest() : AffirmTransfer(MessageType::Master::AFFIRM_TRANSFER_REQUEST) {} + }; + + struct AffirmTransferResponse : public AffirmTransfer { + AffirmTransferResponse() : AffirmTransfer(MessageType::Master::AFFIRM_TRANSFER_RESPONSE) {} + }; + + // Dashboard -> master: which servers are running (answered with ServerListResponse) + struct RequestServerList : public LUBitStream { + RequestServerList() : LUBitStream(ServiceType::MASTER, MessageType::Master::REQUEST_SERVER_LIST) {} + }; + + // Master -> dashboard + struct ServerListResponse : public LUBitStream { + // More than this many worlds is a broken packet + static constexpr uint32_t MAX_INSTANCES = 10000; + + struct Instance { + LWOMAPID mapID{}; + LWOINSTANCEID instanceID{}; + LWOCLONEID cloneID{}; + uint32_t players{}; + LUString ip{}; + uint32_t port{}; + uint8_t isPrivate{}; + }; + + uint8_t authOnline{}; + uint8_t chatOnline{}; + std::vector instances; // u32 count + + ServerListResponse() : LUBitStream(ServiceType::MASTER, MessageType::Master::SERVER_LIST_RESPONSE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Dashboard -> master: shut the whole server down now (scheduled restarts) + struct DashboardShutdown : public LUBitStream { + DashboardShutdown() : LUBitStream(ServiceType::MASTER, MessageType::Master::DASHBOARD_SHUTDOWN) {} + }; + + // Dashboard -> master -> every server: settings changed on the dashboard, reload the config + struct ConfigReload : public LUBitStream { + ConfigReload() : LUBitStream(ServiceType::MASTER, MessageType::Master::CONFIG_RELOAD) {} + }; + + // Dashboard -> master: shut down one world instance + struct InstanceShutdown : public LUBitStream { + uint32_t zoneID{}; + uint32_t instanceID{}; + + InstanceShutdown() : LUBitStream(ServiceType::MASTER, MessageType::Master::INSTANCE_SHUTDOWN) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; } #endif // MASTERPACKETS_H diff --git a/dNet/ZoneInstanceManager.cpp b/dNet/ZoneInstanceManager.cpp index f37b45e78..aca3e2356 100644 --- a/dNet/ZoneInstanceManager.cpp +++ b/dNet/ZoneInstanceManager.cpp @@ -17,40 +17,30 @@ void ZoneInstanceManager::RequestZoneTransfer(dServer* server, uint32_t zoneID, const auto nextID = ++currentRequestID; requests[nextID] = callback; - MasterPackets::SendZoneTransferRequest(server, nextID, mythranShift, zoneID, zoneClone, stamps); + MasterPackets::RequestZoneTransfer request; + request.requestID = nextID; + request.mythranShift = mythranShift; + request.zoneID = zoneID; + request.cloneID = zoneClone; + request.stamps = stamps; + MasterPackets::SendToMaster(request, server); } //! Handles a zone transfer response -void ZoneInstanceManager::HandleRequestZoneTransferResponse(Packet* packet) { - CINSTREAM_SKIP_HEADER; - uint64_t requestID; - inStream.Read(requestID); - bool mythranShift; - uint8_t tmp; - inStream.Read(tmp); - mythranShift = tmp > 0; - uint32_t zoneID; - inStream.Read(zoneID); - uint32_t zoneInstance; - inStream.Read(zoneInstance); - uint32_t zoneClone; - inStream.Read(zoneClone); - uint16_t serverPort; - inStream.Read(serverPort); - LUString serverIP(255); - inStream.Read(serverIP); - Stamps stamps; - if (!stamps.Deserialize(inStream)) stamps = {}; - - const auto entry = requests.find(requestID); +void ZoneInstanceManager::HandleRequestZoneTransferResponse(const MasterPackets::RequestZoneTransferResponse& response) { + const auto entry = requests.find(response.requestID); if (entry != requests.end()) { - entry->second(mythranShift, zoneID, zoneInstance, zoneClone, serverIP.string, serverPort, stamps); + entry->second(response.mythranShift > 0, response.zoneID, response.zoneInstance, response.zoneClone, response.serverIP.string, response.serverPort, response.stamps); requests.erase(entry); } } void ZoneInstanceManager::CreatePrivateZone(dServer* server, uint32_t zoneID, uint32_t zoneClone, const std::string& password) { - MasterPackets::SendZoneCreatePrivate(server, zoneID, zoneClone, password); + MasterPackets::CreatePrivateZone request; + request.zoneID = zoneID; + request.cloneID = zoneClone; + request.password = password; + MasterPackets::SendToMaster(request, server); } void ZoneInstanceManager::RequestPrivateZone( @@ -63,5 +53,9 @@ void ZoneInstanceManager::RequestPrivateZone( callback(mythranShift, zoneID, zoneInstance, zoneClone, serverIP, serverPort); }; - MasterPackets::SendZoneRequestPrivate(server, nextID, mythranShift, password); + MasterPackets::RequestPrivateZone request; + request.requestID = nextID; + request.mythranShift = mythranShift; + request.password = password; + MasterPackets::SendToMaster(request, server); } diff --git a/dNet/ZoneInstanceManager.h b/dNet/ZoneInstanceManager.h index 50fd7c5e0..a12b47435 100644 --- a/dNet/ZoneInstanceManager.h +++ b/dNet/ZoneInstanceManager.h @@ -11,6 +11,10 @@ class dServer; +namespace MasterPackets { + struct RequestZoneTransferResponse; +} + /*! \file ZoneInstanceManager.hpp \brief A class for handling zone transfers and zone-related functions @@ -56,7 +60,7 @@ public: \param requestID The request ID \param packet The packet */ - void HandleRequestZoneTransferResponse(Packet* packet); + void HandleRequestZoneTransferResponse(const MasterPackets::RequestZoneTransferResponse& response); void CreatePrivateZone(dServer* server, uint32_t zoneID, uint32_t zoneClone, const std::string& password); diff --git a/dNet/dServer.cpp b/dNet/dServer.cpp index ca7c8a144..ad81bf710 100644 --- a/dNet/dServer.cpp +++ b/dNet/dServer.cpp @@ -148,14 +148,26 @@ Packet* dServer::ReceiveFromMaster() { LOG("Established connection to master: ServiceType (%s), Zone (%i), Instance (%i)", StringifiedEnum::ToString(this->GetServerType()).data(), this->GetZoneID(), this->GetInstanceID()); mMasterConnectionActive = true; mMasterSystemAddress = packet->systemAddress; - MasterPackets::SendServerInfo(this, packet); + LOG("SendServerInfo called for server type %i", static_cast(GetServerType())); + MasterPackets::ServerInfo info; + info.port = GetPort(); + info.zoneID = GetZoneID(); + info.instanceID = GetInstanceID(); + info.serverType = GetServerType(); + info.ip = LUString(GetIP()); + MasterPackets::SendToMaster(info, this); break; } case ID_USER_PACKET_ENUM: { - if (static_cast(packet->data[1]) == ServiceType::MASTER) { - switch (static_cast(packet->data[3])) { + RakNet::BitStream inStream(packet->data, packet->length, false); + LUBitStream header; + if (header.ReadHeader(inStream) && header.connectionType == ServiceType::MASTER) { + // What every server does with these; the rest goes back to the server's own handlers + switch (static_cast(header.internalPacketID)) { case MessageType::Master::REQUEST_ZONE_TRANSFER_RESPONSE: { - ZoneInstanceManager::Instance()->HandleRequestZoneTransferResponse(packet); + MasterPackets::RequestZoneTransferResponse response; + if (response.Deserialize(inStream)) ZoneInstanceManager::Instance()->HandleRequestZoneTransferResponse(response); + else LOG("Dropped a zone transfer response from master that failed to read"); break; } case MessageType::Master::SHUTDOWN: diff --git a/dNet/DashboardMessages.h b/dNet/master/DashboardMessages.h similarity index 84% rename from dNet/DashboardMessages.h rename to dNet/master/DashboardMessages.h index ef994b450..c77c26525 100644 --- a/dNet/DashboardMessages.h +++ b/dNet/master/DashboardMessages.h @@ -7,6 +7,8 @@ #include #include "BitStream.h" +#include "BitStreamUtils.h" +#include "MessageType/Master.h" #include "dCommonVars.h" namespace DashboardMessages { @@ -29,7 +31,9 @@ namespace DashboardMessages { * PLAYER_POSITIONS (world -> master -> dashboard): where each player in a world instance is, sent every * second while anyone is online and once more when the world empties. */ -struct PlayerPositions { +struct PlayerPositions : public LUBitStream { + PlayerPositions() : LUBitStream(ServiceType::MASTER, MessageType::Master::PLAYER_POSITIONS) {} + struct Player { LWOOBJID characterId{}; float x{}, y{}, z{}; @@ -42,7 +46,7 @@ struct PlayerPositions { uint32_t cloneId{}; std::vector players; - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { stream.Write(zoneId); stream.Write(instanceId); stream.Write(cloneId); @@ -56,7 +60,7 @@ struct PlayerPositions { } } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { uint16_t count{}; if (!stream.Read(zoneId) || !stream.Read(instanceId) || !stream.Read(cloneId) || !stream.Read(count) || count > MAX_PLAYERS) return false; players.resize(count); @@ -71,7 +75,9 @@ struct PlayerPositions { * ANNOUNCE (dashboard -> master -> worlds): a message shown to every player online, or only to those in the listed * zones (master only forwards it to worlds of those zones). */ -struct Announcement { +struct Announcement : public LUBitStream { + Announcement() : LUBitStream(ServiceType::MASTER, MessageType::Master::ANNOUNCE) {} + static constexpr uint16_t MAX_TITLE = 100; static constexpr uint16_t MAX_MESSAGE = 1000; static constexpr uint16_t MAX_ZONES = 200; @@ -80,7 +86,7 @@ struct Announcement { std::string message; std::vector zones; // empty: every world - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { DashboardMessages::WriteText(stream, title, MAX_TITLE); DashboardMessages::WriteText(stream, message, MAX_MESSAGE); const auto count = static_cast(std::min(zones.size(), MAX_ZONES)); @@ -88,7 +94,7 @@ struct Announcement { for (uint16_t i = 0; i < count; i++) stream.Write(zones[i]); } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { if (!DashboardMessages::ReadText(stream, title, MAX_TITLE) || !DashboardMessages::ReadText(stream, message, MAX_MESSAGE)) return false; uint16_t count{}; if (!stream.Read(count) || count > MAX_ZONES) return false; diff --git a/dNet/DataChanged.h b/dNet/master/DataChanged.h similarity index 83% rename from dNet/DataChanged.h rename to dNet/master/DataChanged.h index 8258dfd83..a11fafd22 100644 --- a/dNet/DataChanged.h +++ b/dNet/master/DataChanged.h @@ -7,13 +7,17 @@ #include #include "BitStream.h" +#include "BitStreamUtils.h" +#include "MessageType/Master.h" #include "dCommonVars.h" /** * DATA_CHANGED payload: the tables and rows a game server just wrote. The dashboard turns each entry into a * table_changed event so open pages refresh without waiting for its periodic database check. */ -struct DataChanged { +struct DataChanged : public LUBitStream { + DataChanged() : LUBitStream(ServiceType::MASTER, MessageType::Master::DATA_CHANGED) {} + struct Entry { std::string table; // dashboard table name, e.g. "characters", "mail", "economy" LWOOBJID id{}; // row the change is about, 0 for the table as a whole @@ -24,7 +28,7 @@ struct DataChanged { std::vector entries; - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { const auto count = static_cast(std::min(entries.size(), MAX_ENTRIES)); stream.Write(count); for (size_t i = 0; i < count; i++) { @@ -36,7 +40,7 @@ struct DataChanged { } } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { uint16_t count{}; if (!stream.Read(count) || count > MAX_ENTRIES) return false; entries.clear(); diff --git a/dNet/InstanceMigration.h b/dNet/master/InstanceMigration.h similarity index 93% rename from dNet/InstanceMigration.h rename to dNet/master/InstanceMigration.h index d858f1312..f7dc3b2a5 100644 --- a/dNet/InstanceMigration.h +++ b/dNet/master/InstanceMigration.h @@ -9,6 +9,8 @@ #include #include "BitStream.h" +#include "BitStreamUtils.h" +#include "MessageType/Master.h" #include "dCommonVars.h" /** @@ -227,7 +229,9 @@ namespace InstanceMigration { * INSTANCE_MIGRATE (a world for a GM command, or anything else connected to master): move everyone in one instance. targetInstance 0 picks a target for * merges (the best fit); replaces always start a fresh instance. */ -struct InstanceMigrationRequest { +struct InstanceMigrationRequest : public LUBitStream { + InstanceMigrationRequest() : LUBitStream(ServiceType::MASTER, MessageType::Master::INSTANCE_MIGRATE) {} + static constexpr uint16_t MAX_BY = 64; static constexpr uint16_t MAX_WARN_SECONDS = 300; @@ -242,7 +246,7 @@ struct InstanceMigrationRequest { LWOOBJID requesterId{}; // the character who asked, told how it goes; 0 for none std::string requestedBy; - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { stream.Write(requestId); stream.Write(static_cast(kind)); stream.Write(zoneId); @@ -255,7 +259,7 @@ struct InstanceMigrationRequest { InstanceMigration::WriteText(stream, requestedBy, MAX_BY); } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { uint8_t kindValue{}, shutdown{}, seamlessValue{}; if (!stream.Read(requestId) || !stream.Read(kindValue) || !stream.Read(zoneId) || !stream.Read(sourceInstance) || !stream.Read(targetInstance) || !stream.Read(warnSeconds) || !stream.Read(shutdown) || !stream.Read(seamlessValue) || @@ -272,7 +276,9 @@ struct InstanceMigrationRequest { * MIGRATE_PLAYERS (master -> source world): send everyone to this instance. The target is already running and has * seats held for them. */ -struct MigratePlayersOrder { +struct MigratePlayersOrder : public LUBitStream { + MigratePlayersOrder() : LUBitStream(ServiceType::MASTER, MessageType::Master::MIGRATE_PLAYERS) {} + static constexpr uint16_t MAX_IP = 255; uint32_t migrationId{}; @@ -292,7 +298,7 @@ struct MigratePlayersOrder { */ bool seamless{}; - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { stream.Write(migrationId); stream.Write(targetZone); stream.Write(targetInstance); @@ -305,7 +311,7 @@ struct MigratePlayersOrder { stream.Write(seamless ? 1 : 0); } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { uint8_t shift{}, seamlessValue{}; if (!stream.Read(migrationId) || !stream.Read(targetZone) || !stream.Read(targetInstance) || !stream.Read(targetClone) || !InstanceMigration::ReadText(stream, targetIp, MAX_IP) || !stream.Read(targetPort) || !stream.Read(warnSeconds) || @@ -321,7 +327,9 @@ struct MigratePlayersOrder { * MIGRATE_STATUS (source world -> master, master -> every world): progress of one migration. Master passes it on * with who asked for it, and sends its own (STARTING_TARGET, FAILED) the same way. */ -struct MigrationStatus { +struct MigrationStatus : public LUBitStream { + MigrationStatus() : LUBitStream(ServiceType::MASTER, MessageType::Master::MIGRATE_STATUS) {} + static constexpr uint16_t MAX_MESSAGE = 300; uint32_t migrationId{}; @@ -338,7 +346,7 @@ struct MigrationStatus { bool Finished() const { return state == InstanceMigration::eState::DONE || state == InstanceMigration::eState::FAILED; } - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { stream.Write(migrationId); stream.Write(static_cast(state)); stream.Write(static_cast(kind)); @@ -352,7 +360,7 @@ struct MigrationStatus { InstanceMigration::WriteText(stream, message, MAX_MESSAGE); } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { uint8_t stateValue{}, kindValue{}; if (!stream.Read(migrationId) || !stream.Read(stateValue) || !stream.Read(kindValue) || !stream.Read(zoneId) || !stream.Read(sourceInstance) || !stream.Read(targetInstance) || !stream.Read(moved) || !stream.Read(remaining) || @@ -369,14 +377,16 @@ struct MigrationStatus { * character doesn't keep, put back when they finish loading in the target. Everything else (position, health, * imagination, armor, buffs, inventory, missions) is in the character XML, saved just before the transfer. */ -struct CarriedPlayerState { +struct CarriedPlayerState : public LUBitStream { + CarriedPlayerState() : LUBitStream(ServiceType::MASTER, MessageType::Master::MIGRATE_PLAYER_STATE) {} + uint32_t targetZone{}; uint32_t targetInstance{}; LWOOBJID characterId{}; LWOOBJID petItemId{}; // the pet that was out (its item), summoned again; 0 for none bool seamless{}; // the client kept its scene: skip LOAD_STATIC_ZONE and load the player at once - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { stream.Write(targetZone); stream.Write(targetInstance); stream.Write(characterId); @@ -384,7 +394,7 @@ struct CarriedPlayerState { stream.Write(seamless ? 1 : 0); } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { uint8_t seamlessValue{}; if (!stream.Read(targetZone) || !stream.Read(targetInstance) || !stream.Read(characterId) || !stream.Read(petItemId) || !stream.Read(seamlessValue)) return false; seamless = seamlessValue != 0; diff --git a/dNet/MessageCapture.h b/dNet/master/MessageCapture.h similarity index 93% rename from dNet/MessageCapture.h rename to dNet/master/MessageCapture.h index 90d53abe7..d1c7e6920 100644 --- a/dNet/MessageCapture.h +++ b/dNet/master/MessageCapture.h @@ -10,6 +10,8 @@ #include #include "BitStream.h" +#include "BitStreamUtils.h" +#include "MessageType/Master.h" #include "dCommonVars.h" /** @@ -93,7 +95,9 @@ namespace MessageCapture { } // MESSAGE_CAPTURE_CONTROL payload -struct MessageCaptureControl { +struct MessageCaptureControl : public LUBitStream { + MessageCaptureControl() : LUBitStream(ServiceType::MASTER, MessageType::Master::MESSAGE_CAPTURE_CONTROL) {} + uint32_t captureId{}; eMessageCaptureControl action{}; LWOOBJID characterId{}; @@ -103,7 +107,7 @@ struct MessageCaptureControl { std::vector only; // START: capture only these message IDs (empty: all) std::vector skip; // START: never capture these message IDs - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { stream.Write(captureId); stream.Write(action); stream.Write(characterId); @@ -114,7 +118,7 @@ struct MessageCaptureControl { MessageCapture::WriteIds(stream, skip); } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { uint8_t server{}, client{}; if (!stream.Read(captureId) || !stream.Read(action) || !stream.Read(characterId) || !stream.Read(seconds) || !stream.Read(server) || !stream.Read(client)) return false; @@ -166,7 +170,9 @@ struct MessageCaptureEntry { }; // MESSAGE_CAPTURE_DATA payload -struct MessageCaptureData { +struct MessageCaptureData : public LUBitStream { + MessageCaptureData() : LUBitStream(ServiceType::MASTER, MessageType::Master::MESSAGE_CAPTURE_DATA) {} + static constexpr uint16_t MAX_ENTRIES = 500; uint32_t captureId{}; @@ -179,7 +185,7 @@ struct MessageCaptureData { std::vector entries; uint32_t cloneId{}; // the world's clone (a property's owner), 0 elsewhere - void Serialize(RakNet::BitStream& stream) const { + void Serialize(RakNet::BitStream& stream) const override { stream.Write(captureId); stream.Write(status); stream.Write(characterId); @@ -193,7 +199,7 @@ struct MessageCaptureData { stream.Write(cloneId); } - bool Deserialize(RakNet::BitStream& stream) { + bool Deserialize(RakNet::BitStream& stream) override { uint16_t count{}; if (!stream.Read(captureId) || !stream.Read(status) || !stream.Read(characterId) || !stream.Read(zoneId) || !stream.Read(instanceId) || !stream.Read(reason) || !stream.Read(dropped) || !stream.Read(count) || count > MAX_ENTRIES) return false; diff --git a/dNet/PlayerAction.h b/dNet/master/PlayerAction.h similarity index 89% rename from dNet/PlayerAction.h rename to dNet/master/PlayerAction.h index 573736b07..a554f9fdc 100644 --- a/dNet/PlayerAction.h +++ b/dNet/master/PlayerAction.h @@ -6,6 +6,8 @@ #include #include "BitStream.h" +#include "BitStreamUtils.h" +#include "MessageType/Master.h" #include "dCommonVars.h" /** @@ -35,7 +37,9 @@ enum class ePlayerAction : uint8_t { RELOAD_LIVE_OPS, // load the running live events and open challenges again (every world, answering 1 each; see LiveEvents.h) }; -struct PlayerActionRequest { +struct PlayerActionRequest : public LUBitStream { + PlayerActionRequest() : LUBitStream(ServiceType::MASTER, MessageType::Master::PLAYER_ACTION) {} + uint32_t requestId{}; ePlayerAction action{}; uint32_t accountId{}; @@ -61,7 +65,7 @@ struct PlayerActionRequest { return length; } - void Serialize(RakNet::BitStream& bitStream) const { + void Serialize(RakNet::BitStream& bitStream) const override { bitStream.Write(requestId); bitStream.Write(action); bitStream.Write(accountId); @@ -79,7 +83,7 @@ struct PlayerActionRequest { bitStream.Write(instanceId); } - bool Deserialize(RakNet::BitStream& bitStream) { + bool Deserialize(RakNet::BitStream& bitStream) override { uint8_t approvedByte{}; uint16_t length{}; if (!(bitStream.Read(requestId) && bitStream.Read(action) && bitStream.Read(accountId) && bitStream.Read(characterId) && @@ -96,7 +100,9 @@ struct PlayerActionRequest { } }; -struct PlayerActionResult { +struct PlayerActionResult : public LUBitStream { + PlayerActionResult() : LUBitStream(ServiceType::MASTER, MessageType::Master::PLAYER_ACTION_RESULT) {} + uint32_t requestId{}; ePlayerAction action{}; // Number of sessions/characters the action applied to. Zero means the player was not online. @@ -104,14 +110,14 @@ struct PlayerActionResult { // Set by master when not every world answered before the timeout bool timedOut{}; - void Serialize(RakNet::BitStream& bitStream) const { + void Serialize(RakNet::BitStream& bitStream) const override { bitStream.Write(requestId); bitStream.Write(action); bitStream.Write(affected); bitStream.Write(timedOut); } - bool Deserialize(RakNet::BitStream& bitStream) { + bool Deserialize(RakNet::BitStream& bitStream) override { uint8_t timedOutByte{}; const bool ok = bitStream.Read(requestId) && bitStream.Read(action) && bitStream.Read(affected) && bitStream.Read(timedOutByte); timedOut = timedOutByte != 0; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index cb010c1d0..f03e2dec4 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -2,9 +2,9 @@ #include "ConfigSync.h" #include "EconomyLedger.h" #include "DashboardNotify.h" -#include "DashboardMessages.h" -#include "PlayerAction.h" -#include "MessageCapture.h" +#include "master/DashboardMessages.h" +#include "master/PlayerAction.h" +#include "master/MessageCapture.h" #include "MessageInspector.h" #include "LiveEvents.h" #include @@ -62,6 +62,7 @@ #include "WorldRoutePacket.h" #include "ChatServerLink.h" #include "PacketDispatcher.h" +#include "MasterPackets.h" #include "GameMessageHandler.h" #include "GameMessages.h" #include "Mail.h" @@ -69,7 +70,6 @@ #include "SkillComponent.h" #include "DestroyableComponent.h" #include "Game.h" -#include "MasterPackets.h" #include "PropertyManagementComponent.h" #include "AssetManager.h" #include "LevelProgressionComponent.h" @@ -576,7 +576,10 @@ int main(int argc, char** argv) { if (!ready && Game::server->GetIsConnectedToMaster()) { LOG("Finished loading world with zone (%i), ready up!", Game::server->GetZoneID()); - MasterPackets::SendWorldReady(Game::server, Game::server->GetZoneID(), Game::server->GetInstanceID()); + MasterPackets::WorldReady worldReady; + worldReady.zoneID = static_cast(Game::server->GetZoneID()); + worldReady.instanceID = static_cast(Game::server->GetInstanceID()); + MasterPackets::SendToMaster(worldReady); ready = true; } @@ -682,18 +685,11 @@ void HandlePacketChat(Packet* packet) { } } -void HandleMasterPacket(Packet* packet) { - if (packet->length < 2) return; - if (static_cast(packet->data[1]) != ServiceType::MASTER || packet->length < 4) return; - switch (static_cast(packet->data[3])) { - - case MessageType::Master::SESSION_KEY_RESPONSE: { +namespace { + void OnSessionKeyResponse(const MasterPackets::SessionKeyResponse& response, const SystemAddress& masterAddr) { //Read our session key and to which user it belongs: - CINSTREAM_SKIP_HEADER; - uint32_t sessionKey = 0; - inStream.Read(sessionKey); - LUWString username; - inStream.Read(username); + const uint32_t sessionKey = response.sessionKey; + const LUWString& username = response.username; //Find them: auto it = g_PendingUsers.find(username.GetAsString()); @@ -711,7 +707,7 @@ void HandleMasterPacket(Packet* packet) { } else { LOG("User %s authenticated with correct key.", username.GetAsString().c_str()); - UserManager::Instance()->DeleteUser(packet->systemAddress); + UserManager::Instance()->DeleteUser(masterAddr); //Create our user and send them in: UserManager::Instance()->CreateUser(it->second.sysAddr, username.GetAsString(), userHash); @@ -754,11 +750,10 @@ void HandleMasterPacket(Packet* packet) { //Notify master: { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_ADDED); - bitStream.Write(Game::server->GetZoneID()); - bitStream.Write(g_InstanceID); - Game::server->SendToMaster(bitStream); + MasterPackets::PlayerAdded added; + added.zoneID = static_cast(Game::server->GetZoneID()); + added.instanceID = static_cast(g_InstanceID); + MasterPackets::SendToMaster(added); } if (seamlessCharacter != LWOOBJID_EMPTY) { @@ -766,89 +761,20 @@ void HandleMasterPacket(Packet* packet) { WorldMigration::OnSeamlessArrival(Game::entityManager->GetEntity(seamlessCharacter)); } } - - break; } - case MessageType::Master::AFFIRM_TRANSFER_REQUEST: { - CINSTREAM_SKIP_HEADER; - uint64_t requestID; - inStream.Read(requestID); + + void OnAffirmTransferRequest(const MasterPackets::AffirmTransferRequest& request, const SystemAddress& sysAddr) { + const uint64_t requestID = request.requestID; LOG("Got affirmation request of transfer %llu", requestID); - CBITSTREAM; - - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::AFFIRM_TRANSFER_RESPONSE); - bitStream.Write(requestID); - Game::server->SendToMaster(bitStream); - - break; + MasterPackets::AffirmTransferResponse response; + response.requestID = requestID; + MasterPackets::SendToMaster(response); } - case MessageType::Master::SHUTDOWN: { - Game::lastSignal = -1; - LOG("Got shutdown request from master, zone (%i), instance (%i)", Game::server->GetZoneID(), Game::server->GetInstanceID()); - break; - } - - case MessageType::Master::MIGRATE_PLAYERS: { - CINSTREAM_SKIP_HEADER; - MigratePlayersOrder order; - if (order.Deserialize(inStream)) WorldMigration::HandleOrder(order); - break; - } - - case MessageType::Master::MIGRATE_PLAYER_STATE: { - CINSTREAM_SKIP_HEADER; - CarriedPlayerState state; - if (state.Deserialize(inStream)) WorldMigration::StoreCarriedState(state); - break; - } - - case MessageType::Master::MIGRATE_STATUS: { - CINSTREAM_SKIP_HEADER; - MigrationStatus status; - if (status.Deserialize(inStream)) WorldMigration::HandleStatus(status); - break; - } - - case MessageType::Master::PLAYER_ACTION: { - CINSTREAM_SKIP_HEADER; - PlayerActionRequest request; - if (!request.Deserialize(inStream)) break; - - PlayerActionResult result; - result.requestId = request.requestId; - result.action = request.action; - result.affected = DashboardActions::Apply(request); - - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_ACTION_RESULT); - result.Serialize(bitStream); - Game::server->SendToMaster(bitStream); - break; - } - - case MessageType::Master::MESSAGE_CAPTURE_CONTROL: { - CINSTREAM_SKIP_HEADER; - MessageCaptureControl control; - if (control.Deserialize(inStream)) MessageInspector::Control(control); - break; - } - - case MessageType::Master::ANNOUNCE: { - CINSTREAM_SKIP_HEADER; - Announcement announcement; - if (announcement.Deserialize(inStream)) DashboardNotify::Announce(announcement.title, announcement.message); - break; - } - - case MessageType::Master::NEW_SESSION_ALERT: { - CINSTREAM_SKIP_HEADER; - uint32_t sessionKey{}; - inStream.Read(sessionKey); - - LUString username; - inStream.Read(username); + void OnNewSessionAlert(const MasterPackets::NewSessionAlert& alert, const SystemAddress& sysAddr) { + const uint32_t sessionKey = alert.sessionKey; + const LUString& username = alert.username; LOG("Got new session alert for user %s", username.string.c_str()); //Find them: User* user = UserManager::Instance()->GetUser(username.string.c_str()); @@ -863,9 +789,43 @@ void HandleMasterPacket(Packet* packet) { Game::server->Disconnect(user->GetSystemAddress(), eServerDisconnectIdentifiers::INVALID_SESSION_KEY); return; } - break; } - default: + + void OnPlayerAction(const PlayerActionRequest& request, const SystemAddress& sysAddr) { + PlayerActionResult result; + result.requestId = request.requestId; + result.action = request.action; + result.affected = DashboardActions::Apply(request); + MasterPackets::SendToMaster(result); + } + + // Packets from master that dServer hands back to us + const PacketDispatcher& MasterHandlers() { + static const auto handlers = [] { + PacketDispatcher handlers; + using MessageType::Master; + handlers.On(Master::SESSION_KEY_RESPONSE, OnSessionKeyResponse); + handlers.On(Master::AFFIRM_TRANSFER_REQUEST, OnAffirmTransferRequest); + handlers.On(Master::SHUTDOWN, [](const MasterPackets::Shutdown&, const SystemAddress&) { + Game::lastSignal = -1; + LOG("Got shutdown request from master, zone (%i), instance (%i)", Game::server->GetZoneID(), Game::server->GetInstanceID()); + }); + handlers.On(Master::MIGRATE_PLAYERS, [](const MigratePlayersOrder& order, const SystemAddress&) { WorldMigration::HandleOrder(order); }); + handlers.On(Master::MIGRATE_PLAYER_STATE, [](const CarriedPlayerState& state, const SystemAddress&) { WorldMigration::StoreCarriedState(state); }); + handlers.On(Master::MIGRATE_STATUS, [](const MigrationStatus& status, const SystemAddress&) { WorldMigration::HandleStatus(status); }); + handlers.On(Master::PLAYER_ACTION, OnPlayerAction); + handlers.On(Master::MESSAGE_CAPTURE_CONTROL, [](const MessageCaptureControl& control, const SystemAddress&) { MessageInspector::Control(control); }); + handlers.On(Master::ANNOUNCE, [](const Announcement& announcement, const SystemAddress&) { DashboardNotify::Announce(announcement.title, announcement.message); }); + handlers.On(Master::NEW_SESSION_ALERT, OnNewSessionAlert); + return handlers; + }(); + return handlers; + } +} + +void HandleMasterPacket(Packet* packet) { + if (packet->length < 4) return; + if (!MasterHandlers().Dispatch(packet, ServiceType::MASTER)) { LOG("Unknown packet ID from master %i", int(packet->data[3])); } } @@ -1151,11 +1111,10 @@ void CleanupDisconnectedUser(const SystemAddress& sysAddr) { PropertyManagementComponent::Instance()->Save(); } - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PLAYER_REMOVED); - bitStream.Write(Game::server->GetZoneID()); - bitStream.Write(g_InstanceID); - Game::server->SendToMaster(bitStream); + MasterPackets::PlayerRemoved removed; + removed.zoneID = static_cast(Game::server->GetZoneID()); + removed.instanceID = static_cast(g_InstanceID); + MasterPackets::SendToMaster(removed); } // The world server's handlers for what clients send (ServiceType::WORLD). WorldPackets has the structs; each one is @@ -1202,10 +1161,9 @@ namespace { } //Request the session info from Master: - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_SESSION_KEY); - bitStream.Write(username); - Game::server->SendToMaster(bitStream); + MasterPackets::RequestSessionKey request; + request.username = username; + MasterPackets::SendToMaster(request); //Insert info into our pending list TempSessionInfo info; @@ -1742,7 +1700,5 @@ void FinalizeShutdown() { } void SendShutdownMessageToMaster() { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SHUTDOWN_RESPONSE); - Game::server->SendToMaster(bitStream); + MasterPackets::SendToMaster(MasterPackets::ShutdownResponse()); } diff --git a/docs/PacketArchitecture.md b/docs/PacketArchitecture.md index b8443cce6..e1312ff98 100644 --- a/docs/PacketArchitecture.md +++ b/docs/PacketArchitecture.md @@ -180,6 +180,9 @@ Every non-game-message packet is an `LUBitStream` subclass. The base owns the fu doesn't link `dGame`) is in `ClientPackets`; chat-service packets the client receives are in `ChatPackets::Client`. - Chat -> client packets are wrapped in `ChatPackets::WorldRoutePacket` (`dNet/WorldRoutePacket.h`: target object ID + the inner packet of any service); the world passes the inner bytes on unchanged. +- MASTER structs: `MasterPackets.h` for the core ones (session keys, zone transfer, private zones, player counts, + world ready, shutdown, server list); topic groups (dashboard player actions, data changes, positions and + announcements, message capture, instance migration) in `dNet/master/.h`, all included by `MasterPackets.h`. ```cpp // dNet/ChatPackets.h diff --git a/tests/dCommonTests/InstanceMigrationTests.cpp b/tests/dCommonTests/InstanceMigrationTests.cpp index f5fe7b956..3d9b23a7d 100644 --- a/tests/dCommonTests/InstanceMigrationTests.cpp +++ b/tests/dCommonTests/InstanceMigrationTests.cpp @@ -1,5 +1,5 @@ #include -#include "InstanceMigration.h" +#include "master/InstanceMigration.h" using namespace InstanceMigration; diff --git a/tests/dGameTests/dNetTests/CMakeLists.txt b/tests/dGameTests/dNetTests/CMakeLists.txt index 25e09445f..9bc202c14 100644 --- a/tests/dGameTests/dNetTests/CMakeLists.txt +++ b/tests/dGameTests/dNetTests/CMakeLists.txt @@ -1,6 +1,7 @@ SET(DNET_TESTS "ChatPacketsTests.cpp" "CommonAuthPacketsTests.cpp" + "MasterPacketsTests.cpp" "WorldPacketsTests.cpp") # Get the folder name and prepend it to the files above diff --git a/tests/dGameTests/dNetTests/CommonAuthPacketsTests.cpp b/tests/dGameTests/dNetTests/CommonAuthPacketsTests.cpp index 3bdac94b1..3f34fe279 100644 --- a/tests/dGameTests/dNetTests/CommonAuthPacketsTests.cpp +++ b/tests/dGameTests/dNetTests/CommonAuthPacketsTests.cpp @@ -433,9 +433,21 @@ TEST_F(CommonAuthPacketsTests, StampsRoundTrip) { } TEST_F(CommonAuthPacketsTests, ZoneTransferResponseCarriesStamps) { + const auto sendResponse = [](const Stamps& stamps) { + MasterPackets::RequestZoneTransferResponse response; + response.requestID = 99; + response.mythranShift = true; + response.zoneID = 1000; + response.zoneInstance = 7; + response.zoneClone = 0; + response.serverIP = LUString("127.0.0.1", 255); + response.serverPort = 2001; + response.stamps = stamps; + MasterPackets::SendTo(TestAddress(), response); + }; Stamps stamps; stamps.list = { Stamp(eStamps::PASSPORT_AUTH_START, 0, 10), Stamp(eStamps::PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, 7, 11) }; - const auto sent = Capture([&] { MasterPackets::SendZoneTransferResponse(Game::server, TestAddress(), 99, true, 1000, 7, 0, "127.0.0.1", 2001, stamps); }); + const auto sent = Capture([&] { sendResponse(stamps); }); ASSERT_EQ(sent.size(), 1); RakNet::BitStream bitStream(const_cast(sent[0].bytes.data()), sent[0].bytes.size(), true); @@ -469,7 +481,7 @@ TEST_F(CommonAuthPacketsTests, ZoneTransferResponseCarriesStamps) { EXPECT_EQ(copy.list[1].timestamp, 11); // Without a login the list is empty: just its size field - const auto plain = Capture([&] { MasterPackets::SendZoneTransferResponse(Game::server, TestAddress(), 99, true, 1000, 7, 0, "127.0.0.1", 2001); }); + const auto plain = Capture([&] { sendResponse({}); }); ASSERT_EQ(plain.size(), 1); EXPECT_EQ(plain[0].bytes.size() + 32, sent[0].bytes.size()); const std::vector tail(plain[0].bytes.end() - 4, plain[0].bytes.end()); diff --git a/tests/dGameTests/dNetTests/Legacy/MasterPacketsLegacy.h b/tests/dGameTests/dNetTests/Legacy/MasterPacketsLegacy.h new file mode 100644 index 000000000..062d7db4e --- /dev/null +++ b/tests/dGameTests/dNetTests/Legacy/MasterPacketsLegacy.h @@ -0,0 +1,392 @@ +#ifndef MASTERPACKETSLEGACY_H +#define MASTERPACKETSLEGACY_H + +// FROZEN ORACLE - DO NOT EDIT. +// Verbatim copies of the hand written master packet code that dNet/MasterPackets.h replaced (dNet/MasterPackets.cpp, +// dNet/ZoneInstanceManager.cpp, dMasterServer/{MasterServer,InstanceManager}.cpp, dWorldServer/WorldServer.cpp, +// dDashboardServer/DashboardServer.cpp and the other senders at 9a392167, with the login stamps). Only the namespace changed, except: +// - senders write into `bitStream` instead of sending it (SendToMaster and the master's peers aren't mocked); +// - values the old code took from a server or an instance are passed in; +// - old readers return what they read in a struct instead of acting on it. +// These messages never reach a client, but master, worlds, auth, chat and the dashboard can run different builds +// during an update, so their bytes are pinned too. + +#include "BitStreamUtils.h" +#include "dCommonVars.h" +#include "MessageType/Master.h" +#include "ServiceType.h" +#include "Stamps.h" + +#include +#include +#include +#include + +namespace LegacyMaster { + // dNet/MasterPackets.cpp + inline void SendZoneTransferRequest(RakNet::BitStream& bitStream, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t cloneID, const Stamps& stamps = {}) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER); + + bitStream.Write(requestID); + bitStream.Write(mythranShift); + bitStream.Write(zoneID); + bitStream.Write(cloneID); + stamps.Serialize(bitStream); + } + + inline void SendZoneCreatePrivate(RakNet::BitStream& bitStream, uint32_t zoneID, uint32_t cloneID, const std::string& password) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::CREATE_PRIVATE_ZONE); + + bitStream.Write(zoneID); + bitStream.Write(cloneID); + + bitStream.Write(password.size()); + for (auto character : password) { + bitStream.Write(character); + } + } + + inline void SendZoneRequestPrivate(RakNet::BitStream& bitStream, uint64_t requestID, bool mythranShift, const std::string& password) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_PRIVATE_ZONE); + + bitStream.Write(requestID); + bitStream.Write(mythranShift); + + bitStream.Write(password.size()); + for (auto character : password) { + bitStream.Write(character); + } + } + + inline void SendWorldReady(RakNet::BitStream& bitStream, LWOMAPID zoneId, LWOINSTANCEID instanceId) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::WORLD_READY); + + bitStream.Write(zoneId); + bitStream.Write(instanceId); + } + + inline void SendZoneTransferResponse(RakNet::BitStream& bitStream, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, const std::string& serverIP, uint32_t serverPort, const Stamps& stamps = {}) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER_RESPONSE); + + bitStream.Write(requestID); + bitStream.Write(mythranShift); + bitStream.Write(zoneID); + bitStream.Write(zoneInstance); + bitStream.Write(zoneClone); + bitStream.Write(serverPort); + bitStream.Write(LUString(serverIP, 255)); + stamps.Serialize(bitStream); + } + + // SendServerInfo, with dServer's getters (int port, unsigned int zone, int instance) passed in + inline void SendServerInfo(RakNet::BitStream& bitStream, int port, unsigned int zoneID, int instanceID, ServiceType serverType, const std::string& ip) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SERVER_INFO); + + bitStream.Write(port); + bitStream.Write(zoneID); + bitStream.Write(instanceID); + bitStream.Write(serverType); + bitStream.Write(LUString(ip)); + } + + // dMasterServer/MasterServer.cpp: a server went offline (to the dashboard) + inline void WriteServerOffline(RakNet::BitStream& bitStream, ServiceType type) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SERVER_INFO); + bitStream.Write(0); + bitStream.Write(0); + bitStream.Write(0); + bitStream.Write(type); + bitStream.Write(LUString("offline")); + } + + // dMasterServer/MasterServer.cpp: a world disconnected (to the dashboard) + inline void WriteWorldShutDown(RakNet::BitStream& bitStream, LWOMAPID mapID, LWOINSTANCEID instanceID) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SHUTDOWN_RESPONSE); + bitStream.Write(mapID); + bitStream.Write(instanceID); + } + + // dMasterServer/MasterServer.cpp: WORLD_READY (to the dashboard) + inline void WriteWorldReadyInfo(RakNet::BitStream& bitStream, LWOMAPID zoneID, LWOINSTANCEID instanceID, LWOCLONEID cloneID, const std::string& ip, uint32_t port, bool isPrivate) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::WORLD_READY); + bitStream.Write(zoneID); + bitStream.Write(instanceID); + bitStream.Write(cloneID); + bitStream.Write(LUString(ip)); + bitStream.Write(port); + bitStream.Write(isPrivate ? 1 : 0); + } + + // dMasterServer/MasterServer.cpp SET_SESSION_KEY, and dNet/AuthPackets.cpp + inline void WriteSessionKey(RakNet::BitStream& bitStream, MessageType::Master type, uint32_t sessionKey, const LUString& username) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, type); + bitStream.Write(sessionKey); + bitStream.Write(username); + } + + // dMasterServer/MasterServer.cpp REQUEST_SESSION_KEY + inline void WriteSessionKeyResponse(RakNet::BitStream& bitStream, uint32_t sessionKey, const LUWString& username) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SESSION_KEY_RESPONSE); + bitStream.Write(sessionKey); + bitStream.Write(username); + } + + // dWorldServer/WorldServer.cpp + inline void WriteRequestSessionKey(RakNet::BitStream& bitStream, const LUWString& username) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_SESSION_KEY); + bitStream.Write(username); + } + + // dWorldServer/WorldServer.cpp and dMasterServer/MasterServer.cpp (PLAYER_ADDED and PLAYER_REMOVED) + inline void WritePlayerCount(RakNet::BitStream& bitStream, MessageType::Master type, LWOMAPID zoneID, LWOINSTANCEID instanceID) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, type); + bitStream.Write(zoneID); + bitStream.Write(instanceID); + } + + // dMasterServer/InstanceManager.cpp and dWorldServer/WorldServer.cpp (AFFIRM_TRANSFER_REQUEST and _RESPONSE) + inline void WriteAffirmTransfer(RakNet::BitStream& bitStream, MessageType::Master type, uint64_t requestID) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, type); + + bitStream.Write(requestID); + } + + // Messages with nothing after the header: SHUTDOWN, SHUTDOWN_RESPONSE (world), SHUTDOWN_UNIVERSE, + // REQUEST_SERVER_LIST, CONFIG_RELOAD, DASHBOARD_SHUTDOWN + inline void WriteEmpty(RakNet::BitStream& bitStream, MessageType::Master type) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, type); + } + + // dGame/dComponents/RocketLaunchpadControlComponent.cpp + inline void TellMasterToPrepZone(RakNet::BitStream& bitStream, int zoneID) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::PREP_ZONE); + bitStream.Write(zoneID); + } + + // dDashboardServer/routes/LiveWorld.cpp + inline void WriteInstanceShutdown(RakNet::BitStream& bitStream, uint32_t zone, uint32_t instance) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::INSTANCE_SHUTDOWN); + bitStream.Write(zone); + bitStream.Write(instance); + } + + struct ServerListInstance { + LWOMAPID mapID{}; + LWOINSTANCEID instanceID{}; + LWOCLONEID cloneID{}; + uint32_t players{}; + std::string ip; + uint32_t port{}; + bool isPrivate{}; + }; + + // dMasterServer/MasterServer.cpp REQUEST_SERVER_LIST, with the ready instances passed in + inline void WriteServerList(RakNet::BitStream& bitStream, bool authOnline, bool chatOnline, const std::vector& instances) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SERVER_LIST_RESPONSE); + + bitStream.Write(authOnline ? 1 : 0); + bitStream.Write(chatOnline ? 1 : 0); + + uint32_t instanceCount = instances.size(); + bitStream.Write(instanceCount); + + for (const auto& inst : instances) { + bitStream.Write(inst.mapID); + bitStream.Write(inst.instanceID); + bitStream.Write(inst.cloneID); + bitStream.Write(inst.players); + bitStream.Write(LUString(inst.ip)); + bitStream.Write(inst.port); + bitStream.Write(inst.isPrivate ? 1 : 0); + } + } + + // Readers. The stream is past the packet header. + + struct TransferRequestRead { + uint64_t requestID = 0; + uint8_t mythranShift = false; + uint32_t zoneID = 0; + uint32_t zoneClone = 0; + Stamps stamps; + }; + + // dMasterServer/MasterServer.cpp REQUEST_ZONE_TRANSFER + inline TransferRequestRead ReadZoneTransferRequest(RakNet::BitStream& inStream) { + uint64_t requestID = 0; + uint8_t mythranShift = false; + uint32_t zoneID = 0; + uint32_t zoneClone = 0; + + inStream.Read(requestID); + inStream.Read(mythranShift); + inStream.Read(zoneID); + inStream.Read(zoneClone); + // The login stamps travelling with the request (see Stamps.h) + Stamps stamps; + if (!stamps.Deserialize(inStream)) stamps = {}; + return { requestID, mythranShift, zoneID, zoneClone, stamps }; + } + + struct TransferResponseRead { + uint64_t requestID{}; + bool mythranShift{}; + uint32_t zoneID{}; + uint32_t zoneInstance{}; + uint32_t zoneClone{}; + uint16_t serverPort{}; + std::string serverIP; + Stamps stamps; + }; + + // dNet/ZoneInstanceManager.cpp HandleRequestZoneTransferResponse + inline TransferResponseRead ReadZoneTransferResponse(RakNet::BitStream& inStream) { + uint64_t requestID; + inStream.Read(requestID); + bool mythranShift; + uint8_t tmp; + inStream.Read(tmp); + mythranShift = tmp > 0; + uint32_t zoneID; + inStream.Read(zoneID); + uint32_t zoneInstance; + inStream.Read(zoneInstance); + uint32_t zoneClone; + inStream.Read(zoneClone); + uint16_t serverPort; + inStream.Read(serverPort); + LUString serverIP(255); + inStream.Read(serverIP); + Stamps stamps; + if (!stamps.Deserialize(inStream)) stamps = {}; + return { requestID, mythranShift, zoneID, zoneInstance, zoneClone, serverPort, serverIP.string, stamps }; + } + + struct ServerInfoRead { + uint32_t theirPort = 0; + uint32_t theirZoneID = 0; + uint32_t theirInstanceID = 0; + ServiceType theirServerType; + std::string theirIP; + }; + + // dMasterServer/MasterServer.cpp and dDashboardServer/DashboardServer.cpp SERVER_INFO + inline ServerInfoRead ReadServerInfo(RakNet::BitStream& inStream) { + uint32_t theirPort = 0; + uint32_t theirZoneID = 0; + uint32_t theirInstanceID = 0; + ServiceType theirServerType; + LUString theirIP; + + inStream.Read(theirPort); + inStream.Read(theirZoneID); + inStream.Read(theirInstanceID); + inStream.Read(theirServerType); + inStream.Read(theirIP); + return { theirPort, theirZoneID, theirInstanceID, theirServerType, theirIP.string }; + } + + // dMasterServer/MasterServer.cpp CREATE_PRIVATE_ZONE + inline std::pair, std::string> ReadCreatePrivateZone(RakNet::BitStream& inStream) { + uint32_t mapId; + LWOCLONEID cloneId; + std::string password; + + inStream.Read(mapId); + inStream.Read(cloneId); + + uint32_t len; + inStream.Read(len); + len = std::min(len, 50); // cap the master password at 50 characters + + for (uint32_t i = 0; len > i; i++) { + char character; + inStream.Read(character); + password += character; + } + return { { mapId, cloneId }, password }; + } + + // dMasterServer/MasterServer.cpp REQUEST_PRIVATE_ZONE + inline std::pair, std::string> ReadRequestPrivateZone(RakNet::BitStream& inStream) { + uint64_t requestID = 0; + uint8_t mythranShift = false; + + std::string password; + + inStream.Read(requestID); + inStream.Read(mythranShift); + + uint32_t len; + inStream.Read(len); + len = std::min(len, 50); + + for (uint32_t i = 0; i < len; i++) { + char character; inStream.Read(character); + password += character; + } + return { { requestID, mythranShift }, password }; + } + + struct ServerListRead { + uint8_t authOnline = 0; + uint8_t chatOnline = 0; + std::vector instances; + }; + + // dDashboardServer/DashboardServer.cpp SERVER_LIST_RESPONSE + inline ServerListRead ReadServerList(RakNet::BitStream& inStream) { + ServerListRead out; + uint8_t authOnline = 0; + uint8_t chatOnline = 0; + uint32_t instanceCount = 0; + + inStream.Read(authOnline); + inStream.Read(chatOnline); + inStream.Read(instanceCount); + out.authOnline = authOnline; + out.chatOnline = chatOnline; + + for (uint32_t i = 0; i < instanceCount; i++) { + ServerListInstance info; + LUString ip; + // Same types as MasterServer writes them (map and instance IDs are 16 bits) + LWOMAPID mapID = 0; + LWOINSTANCEID instanceID = 0; + inStream.Read(mapID); + inStream.Read(instanceID); + info.mapID = mapID; + info.instanceID = instanceID; + inStream.Read(info.cloneID); + inStream.Read(info.players); + inStream.Read(ip); + info.ip = ip.string; + inStream.Read(info.port); + uint8_t isPrivate = 0; + inStream.Read(isPrivate); + info.isPrivate = isPrivate != 0; + out.instances.push_back(info); + } + return out; + } + + // dDashboardServer/DashboardServer.cpp WORLD_READY + inline ServerListInstance ReadWorldReadyInfo(RakNet::BitStream& inStream) { + LWOMAPID zoneID; + LWOINSTANCEID instanceID; + LWOCLONEID cloneID; + LUString ip; + uint32_t port; + uint8_t isPrivate; + + inStream.Read(zoneID); + inStream.Read(instanceID); + inStream.Read(cloneID); + inStream.Read(ip); + inStream.Read(port); + inStream.Read(isPrivate); + return { zoneID, instanceID, cloneID, 0, ip.string, port, isPrivate != 0 }; + } +} + +#endif // MASTERPACKETSLEGACY_H diff --git a/tests/dGameTests/dNetTests/MasterPacketsTests.cpp b/tests/dGameTests/dNetTests/MasterPacketsTests.cpp new file mode 100644 index 000000000..7a585b591 --- /dev/null +++ b/tests/dGameTests/dNetTests/MasterPacketsTests.cpp @@ -0,0 +1,472 @@ +#include "MasterPackets.h" +#include "master/DashboardMessages.h" +#include "master/DataChanged.h" +#include "master/InstanceMigration.h" +#include "master/MessageCapture.h" +#include "master/PlayerAction.h" +#include "PacketDispatcher.h" +#include "PacketTestUtils.h" +#include "Game.h" +#include "Logger.h" +#include "dGameMessagesTests/GameMessageTestUtils.h" +#include "Legacy/MasterPacketsLegacy.h" + +#include +#include +#include + +#include + +using namespace PacketTestUtils; +using GameMessageTestUtils::RoundTrip; +using GameMessageTestUtils::ExpectTruncatedFails; + +namespace { + const std::array g_Strings = { "", "localhost", "a password that is longer than fifty characters, which master cuts", "10.0.0.255" }; + const std::array g_RequestIds = { 0, 1, 0x0102030405060708ULL }; + const std::array g_Numbers = { 0, 1100, 0xFFFFFFFF }; + + PacketBytes StructPacket(const LUBitStream& msg) { + RakNet::BitStream bitStream; + msg.WritePacket(bitStream); + return FromBitStream(bitStream); + } + + PacketBytes Written(const std::function& write) { + RakNet::BitStream bitStream; + write(bitStream); + return FromBitStream(bitStream); + } + + // A stream positioned after the header of msg's packet + void LoadPayload(RakNet::BitStream& bitStream, const LUBitStream& msg) { + msg.WritePacket(bitStream); + bitStream.IgnoreBytes(8); + } + + // The old way the dashboard and migration structs were sent: the header, then Serialize + template + void ExpectHeaderThenSerialize(const T& msg, MessageType::Master id) { + RakNet::BitStream old; + BitStreamUtils::WriteHeader(old, ServiceType::MASTER, id); + msg.Serialize(old); + EXPECT_PACKET_EQ(FromBitStream(old), StructPacket(msg)); + RoundTrip(msg); + } +} + +TEST(MasterPacketsTests, ZoneTransferMatchesLegacy) { + Stamps someStamps; + someStamps.list.emplace_back(eStamps::PASSPORT_AUTH_START, 0, 1700000000); + someStamps.list.emplace_back(eStamps::PASSPORT_AUTH_WORLD_PACKET_RECEIVED, 1100, 1700000001); + for (const auto& stamps : { Stamps{}, someStamps }) + for (const auto requestID : g_RequestIds) { + for (const bool mythranShift : { false, true }) { + for (const auto zone : g_Numbers) { + MasterPackets::RequestZoneTransfer request; + request.requestID = requestID; + request.mythranShift = mythranShift; + request.zoneID = zone; + request.cloneID = zone ^ 0x5555; + request.stamps = stamps; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendZoneTransferRequest(b, requestID, mythranShift, zone, zone ^ 0x5555, stamps); }), StructPacket(request)); + + RakNet::BitStream stream; LoadPayload(stream, request); + const auto legacy = LegacyMaster::ReadZoneTransferRequest(stream); + const auto copy = RoundTrip(request); + EXPECT_EQ(legacy.requestID, copy.requestID); + EXPECT_EQ(legacy.mythranShift, copy.mythranShift); + EXPECT_EQ(legacy.zoneID, copy.zoneID); + EXPECT_EQ(legacy.zoneClone, copy.cloneID); + EXPECT_EQ(legacy.stamps.size(), copy.stamps.size()); + + for (const auto& ip : g_Strings) { + for (const uint32_t port : { 0u, 2001u, 70000u }) { + MasterPackets::RequestZoneTransferResponse response; + response.requestID = requestID; + response.mythranShift = mythranShift; + response.zoneID = zone; + response.zoneInstance = zone / 2; + response.zoneClone = zone / 3; + response.serverPort = static_cast(port); + response.serverIP = LUString(ip, 255); + response.stamps = stamps; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendZoneTransferResponse(b, requestID, mythranShift, zone, zone / 2, zone / 3, ip, port, stamps); }), StructPacket(response)); + + RakNet::BitStream responseStream; LoadPayload(responseStream, response); + const auto legacyResponse = LegacyMaster::ReadZoneTransferResponse(responseStream); + const auto responseCopy = RoundTrip(response); + EXPECT_EQ(legacyResponse.requestID, responseCopy.requestID); + EXPECT_EQ(legacyResponse.mythranShift, responseCopy.mythranShift > 0); + EXPECT_EQ(legacyResponse.zoneInstance, responseCopy.zoneInstance); + EXPECT_EQ(legacyResponse.serverPort, responseCopy.serverPort); + EXPECT_EQ(legacyResponse.serverIP, responseCopy.serverIP.string); + EXPECT_EQ(legacyResponse.stamps.size(), responseCopy.stamps.size()); + } + } + } + } + } +} + +// Senders without the login stamps (older servers) still read, with no stamps; a cut off fixed part does not +TEST(MasterPacketsTests, ZoneTransferStampsAreOptional) { + RakNet::BitStream noStamps; + noStamps.Write(5); + noStamps.Write(1); + noStamps.Write(1100); + noStamps.Write(0); + MasterPackets::RequestZoneTransfer request; + ASSERT_TRUE(request.Deserialize(noStamps)); + EXPECT_EQ(request.requestID, 5u); + EXPECT_TRUE(request.stamps.empty()); + + MasterPackets::RequestZoneTransfer full; + full.requestID = 5; + RakNet::BitStream all; + full.Serialize(all); + for (uint32_t bits = 0; bits < 8 * (8 + 1 + 4 + 4); bits++) { + RakNet::BitStream prefix; + prefix.WriteBits(all.GetData(), bits, false); + MasterPackets::RequestZoneTransfer copy; + EXPECT_FALSE(copy.Deserialize(prefix)) << bits; + } +} + +TEST(MasterPacketsTests, ServerInfoMatchesLegacy) { + for (const auto type : { ServiceType::AUTH, ServiceType::CHAT, ServiceType::WORLD, ServiceType::DASHBOARD }) { + for (const auto& ip : g_Strings) { + if (ip.size() > 33) continue; // LUString(33) + for (const int instance : { 0, 7, -1 }) { + MasterPackets::ServerInfo info; + info.port = 2001; + info.zoneID = 1100; + info.instanceID = static_cast(instance); + info.serverType = type; + info.ip = LUString(ip); + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendServerInfo(b, 2001, 1100u, instance, type, ip); }), StructPacket(info)); + + RakNet::BitStream stream; LoadPayload(stream, info); + const auto legacy = LegacyMaster::ReadServerInfo(stream); + const auto copy = RoundTrip(info); + EXPECT_EQ(legacy.theirPort, copy.port); + EXPECT_EQ(legacy.theirInstanceID, copy.instanceID); + EXPECT_EQ(legacy.theirServerType, copy.serverType); + EXPECT_EQ(legacy.theirIP, copy.ip.string); + ExpectTruncatedFails(info); + } + } + MasterPackets::ServerInfo offline; + offline.serverType = type; + offline.ip = LUString("offline"); + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteServerOffline(b, type); }), StructPacket(offline)); + } +} + +TEST(MasterPacketsTests, SessionKeysMatchLegacy) { + for (const uint32_t key : { 0u, 12345u, 0xFFFFFFFFu }) { + for (const auto& name : g_Strings) { + if (name.size() > 33) continue; + MasterPackets::SetSessionKey set; + set.sessionKey = key; + set.username = LUString(name); + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteSessionKey(b, MessageType::Master::SET_SESSION_KEY, key, LUString(name)); }), StructPacket(set)); + RoundTrip(set); + ExpectTruncatedFails(set); + + MasterPackets::NewSessionAlert alert; + alert.sessionKey = key; + alert.username = LUString(name); + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteSessionKey(b, MessageType::Master::NEW_SESSION_ALERT, key, LUString(name)); }), StructPacket(alert)); + RoundTrip(alert); + + MasterPackets::SessionKeyResponse response; + response.sessionKey = key; + response.username = LUWString(name); + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteSessionKeyResponse(b, key, LUWString(name)); }), StructPacket(response)); + RoundTrip(response); + ExpectTruncatedFails(response); + + MasterPackets::RequestSessionKey request; + request.username = LUWString(name); + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteRequestSessionKey(b, LUWString(name)); }), StructPacket(request)); + RoundTrip(request); + ExpectTruncatedFails(request); + } + } +} + +TEST(MasterPacketsTests, PrivateZonesMatchLegacy) { + for (const auto& password : g_Strings) { + MasterPackets::CreatePrivateZone create; + create.zoneID = 1150; + create.cloneID = 0xAABBCCDD; + create.password = password; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendZoneCreatePrivate(b, 1150, 0xAABBCCDD, password); }), StructPacket(create)); + + RakNet::BitStream stream; LoadPayload(stream, create); + const auto legacy = LegacyMaster::ReadCreatePrivateZone(stream); + RakNet::BitStream stream2; LoadPayload(stream2, create); + MasterPackets::CreatePrivateZone read; + ASSERT_TRUE(read.Deserialize(stream2)); + EXPECT_EQ(legacy.first.first, read.zoneID); + EXPECT_EQ(legacy.first.second, read.cloneID); + EXPECT_EQ(legacy.second, read.password); // both cut at 50 characters + if (password.size() <= 50) RoundTrip(create); + + for (const bool mythranShift : { false, true }) { + MasterPackets::RequestPrivateZone request; + request.requestID = 0x0102030405060708ULL; + request.mythranShift = mythranShift; + request.password = password; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendZoneRequestPrivate(b, 0x0102030405060708ULL, mythranShift, password); }), StructPacket(request)); + + RakNet::BitStream requestStream; LoadPayload(requestStream, request); + const auto legacyRequest = LegacyMaster::ReadRequestPrivateZone(requestStream); + RakNet::BitStream requestStream2; LoadPayload(requestStream2, request); + MasterPackets::RequestPrivateZone readRequest; + ASSERT_TRUE(readRequest.Deserialize(requestStream2)); + EXPECT_EQ(legacyRequest.first.first, readRequest.requestID); + EXPECT_EQ(legacyRequest.first.second, readRequest.mythranShift); + EXPECT_EQ(legacyRequest.second, readRequest.password); + if (password.size() <= 50) RoundTrip(request); + } + } +} + +TEST(MasterPacketsTests, WorldStateMatchesLegacy) { + for (const LWOMAPID zone : { static_cast(0), static_cast(1100), static_cast(0xFFFF) }) { + for (const LWOINSTANCEID instance : { static_cast(0), static_cast(3), static_cast(0xFFFF) }) { + MasterPackets::WorldReady ready; + ready.zoneID = zone; + ready.instanceID = instance; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendWorldReady(b, zone, instance); }), StructPacket(ready)); + RoundTrip(ready); + ExpectTruncatedFails(ready); + + MasterPackets::PlayerAdded added; + added.zoneID = zone; + added.instanceID = instance; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WritePlayerCount(b, MessageType::Master::PLAYER_ADDED, zone, instance); }), StructPacket(added)); + RoundTrip(added); + ExpectTruncatedFails(added); + + MasterPackets::PlayerRemoved removed; + removed.zoneID = zone; + removed.instanceID = instance; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WritePlayerCount(b, MessageType::Master::PLAYER_REMOVED, zone, instance); }), StructPacket(removed)); + RoundTrip(removed); + + MasterPackets::WorldShutDown shutDown; + shutDown.zoneID = zone; + shutDown.instanceID = instance; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteWorldShutDown(b, zone, instance); }), StructPacket(shutDown)); + RoundTrip(shutDown); + + for (const bool isPrivate : { false, true }) { + MasterPackets::WorldReadyInfo info; + info.zoneID = zone; + info.instanceID = instance; + info.cloneID = 0xDEADBEEF; + info.ip = LUString("127.0.0.1"); + info.port = 3000; + info.isPrivate = isPrivate ? 1 : 0; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteWorldReadyInfo(b, zone, instance, 0xDEADBEEF, "127.0.0.1", 3000, isPrivate); }), StructPacket(info)); + RakNet::BitStream stream; LoadPayload(stream, info); + const auto legacy = LegacyMaster::ReadWorldReadyInfo(stream); + const auto copy = RoundTrip(info); + EXPECT_EQ(legacy.cloneID, copy.cloneID); + EXPECT_EQ(legacy.ip, copy.ip.string); + EXPECT_EQ(legacy.isPrivate, copy.isPrivate != 0); + ExpectTruncatedFails(info); + } + } + } + + for (const int zone : { 0, 1100, -1 }) { + MasterPackets::PrepZone prep; + prep.zoneID = zone; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::TellMasterToPrepZone(b, zone); }), StructPacket(prep)); + RoundTrip(prep); + ExpectTruncatedFails(prep); + } + + for (const auto requestID : g_RequestIds) { + MasterPackets::AffirmTransferRequest request; + request.requestID = requestID; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteAffirmTransfer(b, MessageType::Master::AFFIRM_TRANSFER_REQUEST, requestID); }), StructPacket(request)); + RoundTrip(request); + ExpectTruncatedFails(request); + MasterPackets::AffirmTransferResponse response; + response.requestID = requestID; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteAffirmTransfer(b, MessageType::Master::AFFIRM_TRANSFER_RESPONSE, requestID); }), StructPacket(response)); + RoundTrip(response); + } + + for (const auto zone : g_Numbers) { + MasterPackets::InstanceShutdown shutdown; + shutdown.zoneID = zone; + shutdown.instanceID = zone / 7; + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteInstanceShutdown(b, zone, zone / 7); }), StructPacket(shutdown)); + RoundTrip(shutdown); + ExpectTruncatedFails(shutdown); + } +} + +TEST(MasterPacketsTests, EmptyMessagesMatchLegacy) { + EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::SHUTDOWN); }), StructPacket(MasterPackets::Shutdown())); + EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::SHUTDOWN_RESPONSE); }), StructPacket(MasterPackets::ShutdownResponse())); + EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::SHUTDOWN_UNIVERSE); }), StructPacket(MasterPackets::ShutdownUniverse())); + EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::REQUEST_SERVER_LIST); }), StructPacket(MasterPackets::RequestServerList())); + EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::CONFIG_RELOAD); }), StructPacket(MasterPackets::ConfigReload())); + EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::DASHBOARD_SHUTDOWN); }), StructPacket(MasterPackets::DashboardShutdown())); +} + +TEST(MasterPacketsTests, Golden) { + MasterPackets::PlayerAdded added; + added.zoneID = 1100; + added.instanceID = 2; + // 53 | MASTER (6) | PLAYER_ADDED (7) | pad | u16 1100 | u16 2 + EXPECT_PACKET_EQ(FromHex("53 06 00 07 00 00 00 00 4c 04 02 00"), StructPacket(added)); + + MasterPackets::AffirmTransferRequest affirm; + affirm.requestID = 0x0102030405060708ULL; + // AFFIRM_TRANSFER_REQUEST (17) | u64 + EXPECT_PACKET_EQ(FromHex("53 06 00 11 00 00 00 00 08 07 06 05 04 03 02 01"), StructPacket(affirm)); +} + +TEST(MasterPacketsTests, ServerListMatchesLegacy) { + for (size_t count = 0; count <= 3; count++) { + std::vector instances; + MasterPackets::ServerListResponse response; + response.authOnline = 1; + response.chatOnline = count % 2; + for (size_t i = 0; i < count; i++) { + LegacyMaster::ServerListInstance instance{ static_cast(1000 + i), static_cast(i), static_cast(i * 7), static_cast(i * 3), "10.0.0." + std::to_string(i), static_cast(3000 + i), i == 2 }; + instances.push_back(instance); + auto& entry = response.instances.emplace_back(); + entry.mapID = instance.mapID; + entry.instanceID = instance.instanceID; + entry.cloneID = instance.cloneID; + entry.players = instance.players; + entry.ip = LUString(instance.ip); + entry.port = instance.port; + entry.isPrivate = instance.isPrivate ? 1 : 0; + } + EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteServerList(b, true, count % 2, instances); }), StructPacket(response)); + + RakNet::BitStream stream; LoadPayload(stream, response); + const auto legacy = LegacyMaster::ReadServerList(stream); + const auto copy = RoundTrip(response); + ASSERT_EQ(legacy.instances.size(), copy.instances.size()); + for (size_t i = 0; i < count; i++) { + EXPECT_EQ(legacy.instances[i].mapID, copy.instances[i].mapID); + EXPECT_EQ(legacy.instances[i].players, copy.instances[i].players); + EXPECT_EQ(legacy.instances[i].ip, copy.instances[i].ip.string); + EXPECT_EQ(legacy.instances[i].isPrivate, copy.instances[i].isPrivate != 0); + } + ExpectTruncatedFails(response); + } +} + +// The dashboard and migration structs kept their payloads; they now carry their own header +TEST(MasterPacketsTests, DashboardAndMigrationStructsKeepTheirBytes) { + PlayerActionRequest action; + action.requestId = 7; + action.action = ePlayerAction::CHAT_MESSAGE; + action.text = "hello"; + action.name = "[Discord] Bob"; + ExpectHeaderThenSerialize(action, MessageType::Master::PLAYER_ACTION); + + PlayerActionResult result; + result.requestId = 7; + result.affected = 3; + result.timedOut = true; + ExpectHeaderThenSerialize(result, MessageType::Master::PLAYER_ACTION_RESULT); + + DataChanged changed; + changed.entries.push_back({ "characters", 42 }); + ExpectHeaderThenSerialize(changed, MessageType::Master::DATA_CHANGED); + + PlayerPositions positions; + positions.zoneId = 1100; + positions.players.push_back({ 42, 1.0f, 2.0f, 3.0f }); + ExpectHeaderThenSerialize(positions, MessageType::Master::PLAYER_POSITIONS); + + Announcement announcement; + announcement.title = "Title"; + announcement.message = "Message"; + announcement.zones = { 1100, 1200 }; + ExpectHeaderThenSerialize(announcement, MessageType::Master::ANNOUNCE); + + MessageCaptureControl control; + control.captureId = 3; + control.seconds = 60; + control.only = { 1, 2 }; + ExpectHeaderThenSerialize(control, MessageType::Master::MESSAGE_CAPTURE_CONTROL); + + MessageCaptureData data; + data.captureId = 3; + data.entries.resize(1); + data.entries[0].payload = "abc"; + ExpectHeaderThenSerialize(data, MessageType::Master::MESSAGE_CAPTURE_DATA); + + InstanceMigrationRequest migrate; + migrate.requestId = 9; + migrate.requestedBy = "GM"; + ExpectHeaderThenSerialize(migrate, MessageType::Master::INSTANCE_MIGRATE); + + MigratePlayersOrder order; + order.targetIp = "127.0.0.1"; + ExpectHeaderThenSerialize(order, MessageType::Master::MIGRATE_PLAYERS); + + MigrationStatus status; + status.message = "Moving players"; + ExpectHeaderThenSerialize(status, MessageType::Master::MIGRATE_STATUS); + + CarriedPlayerState state; + state.characterId = 42; + ExpectHeaderThenSerialize(state, MessageType::Master::MIGRATE_PLAYER_STATE); +} + +TEST(MasterPacketsTests, DispatcherRoutesAndDropsBadPackets) { + // Dropped packets are logged + auto* const previousLogger = Game::logger; + Game::logger = new Logger("./testing.log", true, true); + PacketDispatcher handlers; + int calls = 0; + MasterPackets::PlayerAdded seen; + handlers.On(MessageType::Master::PLAYER_ADDED, [&](const MasterPackets::PlayerAdded& msg, const SystemAddress&) { calls++; seen = msg; }); + + MasterPackets::PlayerAdded added; + added.zoneID = 1100; + added.instanceID = 4; + RakNet::BitStream good; + added.WritePacket(good); + Packet packet{}; + packet.data = good.GetData(); + packet.length = good.GetNumberOfBytesUsed(); + EXPECT_TRUE(handlers.Dispatch(&packet, ServiceType::MASTER)); + EXPECT_EQ(calls, 1); + EXPECT_EQ(seen.zoneID, 1100); + EXPECT_EQ(seen.instanceID, 4); + + // Wrong service: not ours + EXPECT_FALSE(handlers.Dispatch(&packet, ServiceType::CHAT)); + + // Truncated: handled (dropped), the handler isn't called + packet.length -= 1; + EXPECT_TRUE(handlers.Dispatch(&packet, ServiceType::MASTER)); + EXPECT_EQ(calls, 1); + + // No handler for this ID + MasterPackets::PlayerRemoved removed; + RakNet::BitStream other; + removed.WritePacket(other); + packet.data = other.GetData(); + packet.length = other.GetNumberOfBytesUsed(); + EXPECT_FALSE(handlers.Dispatch(&packet, ServiceType::MASTER)); + + delete Game::logger; + Game::logger = previousLogger; +} diff --git a/tests/dWebTests/AlertsAndTwoFactorTests.cpp b/tests/dWebTests/AlertsAndTwoFactorTests.cpp index c4fabbbfc..a2d336806 100644 --- a/tests/dWebTests/AlertsAndTwoFactorTests.cpp +++ b/tests/dWebTests/AlertsAndTwoFactorTests.cpp @@ -1,6 +1,6 @@ #include -#include "DashboardMessages.h" +#include "master/DashboardMessages.h" #include "EconomyScan.h" #include "IDashboardAdmin.h" #include "Totp.h" @@ -192,7 +192,9 @@ TEST(DashboardMessagesTests, PlayerPositionsRoundTrip) { } TEST(DashboardMessagesTests, AnnouncementRoundTripAndLimits) { - Announcement announcement{ "Title", "Hello everyone" }; + Announcement announcement; + announcement.title = "Title"; + announcement.message = "Hello everyone"; RakNet::BitStream stream; announcement.Serialize(stream); Announcement read; diff --git a/tests/dWebTests/MessageInspectorTests.cpp b/tests/dWebTests/MessageInspectorTests.cpp index be8020d6a..e26ccbf8a 100644 --- a/tests/dWebTests/MessageInspectorTests.cpp +++ b/tests/dWebTests/MessageInspectorTests.cpp @@ -1,6 +1,6 @@ #include -#include "MessageCapture.h" +#include "master/MessageCapture.h" #include "InspectorFormat.h" using namespace std::chrono_literals; diff --git a/tests/dWebTests/PlayerActionTests.cpp b/tests/dWebTests/PlayerActionTests.cpp index 6a8d652bb..270e6e8e6 100644 --- a/tests/dWebTests/PlayerActionTests.cpp +++ b/tests/dWebTests/PlayerActionTests.cpp @@ -1,6 +1,6 @@ #include -#include "PlayerAction.h" -#include "DataChanged.h" +#include "master/PlayerAction.h" +#include "master/DataChanged.h" TEST(PlayerActionTest, RequestRoundTrip) { PlayerActionRequest request; diff --git a/tests/dWebTests/SecurityFixesTests.cpp b/tests/dWebTests/SecurityFixesTests.cpp index 7b2b355fb..d2add8667 100644 --- a/tests/dWebTests/SecurityFixesTests.cpp +++ b/tests/dWebTests/SecurityFixesTests.cpp @@ -5,7 +5,7 @@ #include "BackupFiles.h" #include "GameLabels.h" #include "LoginThrottle.h" -#include "PlayerAction.h" +#include "master/PlayerAction.h" #include "ServerState.h" // ---- Failed sign-in throttling (DashboardAuthService) ----