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) ----