mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-23 06:44:38 +01:00
Refactor the network code also change protocol (#25575)
* Change the network protocol and refactor the code, still supports the old format for now * Bump up network version * Update changelog.txt
This commit is contained in:
@@ -3838,3 +3838,4 @@ STR_7005 :Drag an area of footpath
|
||||
STR_7006 :Draw border around image buttons
|
||||
STR_7007 :Ride Type
|
||||
STR_7008 :Unknown ride type ({INT32})
|
||||
STR_7009 :Receiving scripts…
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
------------------------------------------------------------------------
|
||||
- Improved: [#25529] The map selection grid no longer redraws every frame if it has not changed.
|
||||
- Improved: [#25530] Wall dragging can now be cancelled without closing the Scenery window.
|
||||
- Improved: [#25575] Updated the network protocol to a new format that supports larger packets, allowing clients to connect reliably to servers with many objects or large maps.
|
||||
- Change: [#25485] Make the enlarged pressed swatch sprite more pronounced.
|
||||
- Fix: [#22484] Lingering ghost entrance after placing park entrance.
|
||||
- Fix: [#24952] Duplicate track designs when running via Steam without having RCT1 linked.
|
||||
|
||||
@@ -1299,6 +1299,8 @@ namespace OpenRCT2
|
||||
|
||||
UpdateTimeAccumulators(deltaTime);
|
||||
|
||||
Network::Update();
|
||||
|
||||
if (useVariableFrame)
|
||||
{
|
||||
RunVariableFrame(deltaTime);
|
||||
@@ -1307,6 +1309,8 @@ namespace OpenRCT2
|
||||
{
|
||||
RunFixedFrame(deltaTime);
|
||||
}
|
||||
|
||||
Network::Flush();
|
||||
}
|
||||
|
||||
void UpdateTimeAccumulators(float deltaTime)
|
||||
@@ -1332,7 +1336,7 @@ namespace OpenRCT2
|
||||
|
||||
if (_ticksAccumulator < kGameUpdateTimeMS)
|
||||
{
|
||||
const auto sleepTimeSec = (kGameUpdateTimeMS - _ticksAccumulator);
|
||||
const auto sleepTimeSec = std::min(kNetworkUpdateTimeMS, kGameUpdateTimeMS - _ticksAccumulator);
|
||||
Platform::Sleep(static_cast<uint32_t>(sleepTimeSec * 1000.f));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace OpenRCT2
|
||||
// The maximum threshold to advance.
|
||||
constexpr float kGameUpdateMaxThreshold = kGameUpdateTimeMS * kGameMaxUpdates;
|
||||
|
||||
// The network update runs at a different rate to the game update.
|
||||
constexpr uint32_t kNetworkUpdateFPS = 140;
|
||||
// The network update interval in milliseconds, (1000 / 140fps) = ~7.14ms
|
||||
constexpr float kNetworkUpdateTimeMS = 1.0f / kNetworkUpdateFPS;
|
||||
|
||||
constexpr float kGameMinTimeScale = 0.1f;
|
||||
constexpr float kGameMaxTimeScale = 5.0f;
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace OpenRCT2
|
||||
getGameState().entities.UpdateMoneyEffect();
|
||||
|
||||
// Post-tick network update
|
||||
Network::ProcessPending();
|
||||
Network::PostTick();
|
||||
|
||||
// Post-tick game actions.
|
||||
GameActions::ProcessQueue();
|
||||
@@ -181,6 +181,12 @@ namespace OpenRCT2
|
||||
}
|
||||
}
|
||||
|
||||
// Network has to always tick.
|
||||
if (numUpdates == 0)
|
||||
{
|
||||
Network::Tick();
|
||||
}
|
||||
|
||||
// Update the game one or more times
|
||||
for (uint32_t i = 0; i < numUpdates; i++)
|
||||
{
|
||||
@@ -251,7 +257,7 @@ namespace OpenRCT2
|
||||
|
||||
GetContext()->GetReplayManager()->Update();
|
||||
|
||||
Network::Update();
|
||||
Network::Tick();
|
||||
|
||||
auto& gameState = getGameState();
|
||||
|
||||
@@ -344,7 +350,7 @@ namespace OpenRCT2
|
||||
|
||||
GameActions::ProcessQueue();
|
||||
|
||||
Network::ProcessPending();
|
||||
Network::PostTick();
|
||||
Network::Flush();
|
||||
|
||||
gameState.currentTicks++;
|
||||
|
||||
@@ -1575,6 +1575,7 @@ enum : StringId
|
||||
STR_UNKNOWN_RIDE = 6375,
|
||||
|
||||
STR_MULTIPLAYER_RECEIVING_OBJECTS_LIST = 6378,
|
||||
STR_MULTIPLAYER_RECEIVING_SCRIPTS = 7009,
|
||||
|
||||
STR_MULTIPLAYER_RECEIVED_INVALID_DATA = 6379,
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ namespace OpenRCT2::Network
|
||||
void SendTick();
|
||||
bool GamestateSnapshotsEnabled();
|
||||
void Update();
|
||||
void ProcessPending();
|
||||
void Tick();
|
||||
void PostTick();
|
||||
void Flush();
|
||||
|
||||
[[nodiscard]] Auth GetAuthstatus();
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
// It is used for making sure only compatible builds get connected, even within
|
||||
// single OpenRCT2 version.
|
||||
|
||||
constexpr uint8_t kStreamVersion = 1;
|
||||
constexpr uint8_t kStreamVersion = 2;
|
||||
|
||||
const std::string kStreamID = std::string(kOpenRCT2Version) + "-" + std::to_string(kStreamVersion);
|
||||
|
||||
@@ -62,7 +62,7 @@ static constexpr uint32_t kChunkSize = 1024 * 63;
|
||||
|
||||
// If data is sent fast enough it would halt the entire server, process only a maximum amount.
|
||||
// This limit is per connection, the current value was determined by tests with fuzzing.
|
||||
static constexpr uint32_t kMaxPacketsPerUpdate = 100;
|
||||
static constexpr uint32_t kMaxPacketsPerTick = 100;
|
||||
|
||||
#include "../Cheats.h"
|
||||
#include "../ParkImporter.h"
|
||||
@@ -119,6 +119,7 @@ namespace OpenRCT2::Network
|
||||
_actionId = 0;
|
||||
|
||||
client_command_handlers[Command::auth] = &NetworkBase::Client_Handle_AUTH;
|
||||
client_command_handlers[Command::beginMap] = &NetworkBase::Client_Handle_BEGINMAP;
|
||||
client_command_handlers[Command::map] = &NetworkBase::Client_Handle_MAP;
|
||||
client_command_handlers[Command::chat] = &NetworkBase::Client_Handle_CHAT;
|
||||
client_command_handlers[Command::gameAction] = &NetworkBase::Client_Handle_GAME_ACTION;
|
||||
@@ -134,7 +135,6 @@ namespace OpenRCT2::Network
|
||||
client_command_handlers[Command::gameInfo] = &NetworkBase::Client_Handle_GAMEINFO;
|
||||
client_command_handlers[Command::token] = &NetworkBase::Client_Handle_TOKEN;
|
||||
client_command_handlers[Command::objectsList] = &NetworkBase::Client_Handle_OBJECTS_LIST;
|
||||
client_command_handlers[Command::scriptsHeader] = &NetworkBase::Client_Handle_SCRIPTS_HEADER;
|
||||
client_command_handlers[Command::scriptsData] = &NetworkBase::Client_Handle_SCRIPTS_DATA;
|
||||
client_command_handlers[Command::gameState] = &NetworkBase::Client_Handle_GAMESTATE;
|
||||
|
||||
@@ -463,6 +463,21 @@ namespace OpenRCT2::Network
|
||||
}
|
||||
|
||||
void NetworkBase::Update()
|
||||
{
|
||||
switch (GetMode())
|
||||
{
|
||||
case Mode::server:
|
||||
UpdateServer();
|
||||
break;
|
||||
case Mode::client:
|
||||
UpdateClient();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkBase::Tick()
|
||||
{
|
||||
_closeLock = true;
|
||||
|
||||
@@ -474,10 +489,10 @@ namespace OpenRCT2::Network
|
||||
switch (GetMode())
|
||||
{
|
||||
case Mode::server:
|
||||
UpdateServer();
|
||||
TickServer();
|
||||
break;
|
||||
case Mode::client:
|
||||
UpdateClient();
|
||||
TickClient();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -511,6 +526,18 @@ namespace OpenRCT2::Network
|
||||
}
|
||||
|
||||
void NetworkBase::UpdateServer()
|
||||
{
|
||||
for (auto& connection : client_connection_list)
|
||||
{
|
||||
// This can be called multiple times before the connection is removed.
|
||||
if (!connection->IsValid())
|
||||
continue;
|
||||
|
||||
connection->update();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkBase::TickServer()
|
||||
{
|
||||
for (auto& connection : client_connection_list)
|
||||
{
|
||||
@@ -520,6 +547,7 @@ namespace OpenRCT2::Network
|
||||
|
||||
if (!ProcessConnection(*connection))
|
||||
{
|
||||
LOG_INFO("Disconnecting player %s", connection->player->Name.c_str());
|
||||
connection->Disconnect();
|
||||
}
|
||||
else
|
||||
@@ -548,6 +576,11 @@ namespace OpenRCT2::Network
|
||||
}
|
||||
|
||||
void NetworkBase::UpdateClient()
|
||||
{
|
||||
_serverConnection->update();
|
||||
}
|
||||
|
||||
void NetworkBase::TickClient()
|
||||
{
|
||||
assert(_serverConnection != nullptr);
|
||||
|
||||
@@ -594,7 +627,6 @@ namespace OpenRCT2::Network
|
||||
case SocketStatus::connected:
|
||||
{
|
||||
status = Status::connected;
|
||||
_serverConnection->ResetLastPacketTime();
|
||||
Client_Send_TOKEN();
|
||||
char str_authenticating[256];
|
||||
FormatStringLegacy(str_authenticating, 256, STR_MULTIPLAYER_AUTHENTICATING, nullptr);
|
||||
@@ -1319,44 +1351,39 @@ namespace OpenRCT2::Network
|
||||
{
|
||||
LOG_VERBOSE("Server sends objects list with %u items", objects.size());
|
||||
|
||||
if (objects.empty())
|
||||
{
|
||||
Packet packet(Command::objectsList);
|
||||
packet << static_cast<uint32_t>(0) << static_cast<uint32_t>(objects.size());
|
||||
Packet packet(Command::objectsList);
|
||||
|
||||
connection.QueuePacket(std::move(packet));
|
||||
}
|
||||
else
|
||||
// Count.
|
||||
packet << static_cast<uint32_t>(objects.size());
|
||||
|
||||
// List
|
||||
for (size_t i = 0; i < objects.size(); ++i)
|
||||
{
|
||||
for (size_t i = 0; i < objects.size(); ++i)
|
||||
const auto* object = objects[i];
|
||||
|
||||
if (object->Identifier.empty())
|
||||
{
|
||||
const auto* object = objects[i];
|
||||
|
||||
Packet packet(Command::objectsList);
|
||||
packet << static_cast<uint32_t>(i) << static_cast<uint32_t>(objects.size());
|
||||
|
||||
if (object->Identifier.empty())
|
||||
{
|
||||
// DAT
|
||||
LOG_VERBOSE("Object %.8s (checksum %x)", object->ObjectEntry.name, object->ObjectEntry.checksum);
|
||||
packet << static_cast<uint8_t>(0);
|
||||
packet.Write(&object->ObjectEntry, sizeof(RCTObjectEntry));
|
||||
}
|
||||
else
|
||||
{
|
||||
// JSON
|
||||
LOG_VERBOSE("Object %s", object->Identifier.c_str());
|
||||
packet << static_cast<uint8_t>(1);
|
||||
packet.WriteString(object->Identifier);
|
||||
}
|
||||
|
||||
connection.QueuePacket(std::move(packet));
|
||||
// DAT
|
||||
LOG_VERBOSE("Object %.8s (checksum %x)", object->ObjectEntry.name, object->ObjectEntry.checksum);
|
||||
packet << static_cast<uint8_t>(0);
|
||||
packet.Write(&object->ObjectEntry, sizeof(RCTObjectEntry));
|
||||
}
|
||||
else
|
||||
{
|
||||
// JSON
|
||||
LOG_VERBOSE("Object %s", object->Identifier.c_str());
|
||||
packet << static_cast<uint8_t>(1);
|
||||
packet.WriteString(object->Identifier);
|
||||
}
|
||||
}
|
||||
|
||||
connection.QueuePacket(std::move(packet));
|
||||
}
|
||||
|
||||
void NetworkBase::ServerSendScripts(Connection& connection)
|
||||
{
|
||||
Packet packet(Command::scriptsData);
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
using namespace OpenRCT2::Scripting;
|
||||
|
||||
@@ -1366,45 +1393,21 @@ namespace OpenRCT2::Network
|
||||
const auto remotePlugins = scriptEngine.GetRemotePlugins();
|
||||
LOG_VERBOSE("Server sends %zu scripts", remotePlugins.size());
|
||||
|
||||
// Build the data contents for each plugin.
|
||||
MemoryStream pluginData;
|
||||
packet << static_cast<uint32_t>(remotePlugins.size());
|
||||
|
||||
for (auto& plugin : remotePlugins)
|
||||
{
|
||||
const auto& code = plugin->GetCode();
|
||||
|
||||
const auto codeSize = static_cast<uint32_t>(code.size());
|
||||
pluginData.WriteValue(codeSize);
|
||||
pluginData.WriteArray(code.c_str(), code.size());
|
||||
|
||||
packet << codeSize;
|
||||
packet.Write(code.c_str(), code.size());
|
||||
}
|
||||
|
||||
// Send the header packet.
|
||||
Packet packetScriptHeader(Command::scriptsHeader);
|
||||
packetScriptHeader << static_cast<uint32_t>(remotePlugins.size());
|
||||
packetScriptHeader << static_cast<uint32_t>(pluginData.GetLength());
|
||||
connection.QueuePacket(std::move(packetScriptHeader));
|
||||
|
||||
// Segment the plugin data into chunks and send them.
|
||||
const uint8_t* pluginDataBuffer = static_cast<const uint8_t*>(pluginData.GetData());
|
||||
uint32_t dataOffset = 0;
|
||||
while (dataOffset < pluginData.GetLength())
|
||||
{
|
||||
const uint32_t chunkSize = std::min<uint32_t>(pluginData.GetLength() - dataOffset, kChunkSize);
|
||||
|
||||
Packet packet(Command::scriptsData);
|
||||
packet << chunkSize;
|
||||
packet.Write(pluginDataBuffer + dataOffset, chunkSize);
|
||||
|
||||
connection.QueuePacket(std::move(packet));
|
||||
|
||||
dataOffset += chunkSize;
|
||||
}
|
||||
Guard::Assert(dataOffset == pluginData.GetLength());
|
||||
|
||||
#else
|
||||
Packet packetScriptHeader(Command::scriptsHeader);
|
||||
packetScriptHeader << static_cast<uint32_t>(0u);
|
||||
packetScriptHeader << static_cast<uint32_t>(0u);
|
||||
packet << static_cast<uint32_t>(0);
|
||||
#endif
|
||||
|
||||
connection.QueuePacket(std::move(packet));
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Send_HEARTBEAT(Connection& connection) const
|
||||
@@ -1472,8 +1475,8 @@ namespace OpenRCT2::Network
|
||||
objects = objManager.GetPackableObjects();
|
||||
}
|
||||
|
||||
auto header = SaveForNetwork(objects);
|
||||
if (header.empty())
|
||||
auto mapContent = SaveForNetwork(objects);
|
||||
if (mapContent.empty())
|
||||
{
|
||||
if (connection != nullptr)
|
||||
{
|
||||
@@ -1482,21 +1485,21 @@ namespace OpenRCT2::Network
|
||||
}
|
||||
return;
|
||||
}
|
||||
size_t chunksize = kChunkSize;
|
||||
for (size_t i = 0; i < header.size(); i += chunksize)
|
||||
|
||||
Packet packetBeginMap(Command::beginMap);
|
||||
|
||||
Packet packetMap(Command::map);
|
||||
packetMap.Write(mapContent.data(), mapContent.size());
|
||||
|
||||
if (connection != nullptr)
|
||||
{
|
||||
size_t datasize = std::min(chunksize, header.size() - i);
|
||||
Packet packet(Command::map);
|
||||
packet << static_cast<uint32_t>(header.size()) << static_cast<uint32_t>(i);
|
||||
packet.Write(&header[i], datasize);
|
||||
if (connection != nullptr)
|
||||
{
|
||||
connection->QueuePacket(std::move(packet));
|
||||
}
|
||||
else
|
||||
{
|
||||
SendPacketToClients(packet);
|
||||
}
|
||||
connection->QueuePacket(std::move(packetBeginMap));
|
||||
connection->QueuePacket(std::move(packetMap));
|
||||
}
|
||||
else
|
||||
{
|
||||
SendPacketToClients(packetBeginMap);
|
||||
SendPacketToClients(packetMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1740,6 +1743,56 @@ namespace OpenRCT2::Network
|
||||
SendPacketToClients(packet);
|
||||
}
|
||||
|
||||
bool NetworkBase::UpdateConnection(Connection& connection)
|
||||
{
|
||||
connection.update();
|
||||
|
||||
return connection.IsValid();
|
||||
}
|
||||
|
||||
static void displayNetworkProgress(StringId captionStringId)
|
||||
{
|
||||
auto captionString = GetContext()->GetLocalisationService().GetString(captionStringId);
|
||||
auto intent = Intent(INTENT_ACTION_PROGRESS_OPEN);
|
||||
intent.PutExtra(INTENT_EXTRA_MESSAGE, captionString);
|
||||
intent.PutExtra(INTENT_EXTRA_CALLBACK, []() -> void {
|
||||
LOG_INFO("User aborted network operation");
|
||||
OpenRCT2::GetContext()->GetNetwork().Close();
|
||||
});
|
||||
ContextOpenIntent(&intent);
|
||||
}
|
||||
|
||||
static void reportPacketProgress(NetworkBase& network, Connection& connection)
|
||||
{
|
||||
if (network.GetMode() != Mode::client)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto nextPacketCommand = connection.getPendingPacketCommand();
|
||||
const auto bytesReceived = connection.getPendingPacketAvailable();
|
||||
const auto bytesTotal = connection.getPendingPacketSize();
|
||||
|
||||
switch (nextPacketCommand)
|
||||
{
|
||||
case Command::objectsList:
|
||||
displayNetworkProgress(STR_MULTIPLAYER_RECEIVING_OBJECTS_LIST);
|
||||
break;
|
||||
case Command::map:
|
||||
displayNetworkProgress(STR_MULTIPLAYER_DOWNLOADING_MAP);
|
||||
break;
|
||||
case Command::scriptsData:
|
||||
displayNetworkProgress(STR_MULTIPLAYER_RECEIVING_SCRIPTS);
|
||||
break;
|
||||
default:
|
||||
// Nothing to report.
|
||||
return;
|
||||
}
|
||||
|
||||
network.GetContext().SetProgress(
|
||||
static_cast<uint32_t>(bytesReceived), static_cast<uint32_t>(bytesTotal), STR_STRING_M_OF_N_KIB);
|
||||
}
|
||||
|
||||
bool NetworkBase::ProcessConnection(Connection& connection)
|
||||
{
|
||||
ReadPacket packetStatus;
|
||||
@@ -1760,6 +1813,7 @@ namespace OpenRCT2::Network
|
||||
return false;
|
||||
case ReadPacket::success:
|
||||
// done reading in packet
|
||||
reportPacketProgress(*this, connection);
|
||||
ProcessPacket(connection, connection.InboundPacket);
|
||||
if (!connection.IsValid())
|
||||
{
|
||||
@@ -1768,15 +1822,20 @@ namespace OpenRCT2::Network
|
||||
break;
|
||||
case ReadPacket::moreData:
|
||||
// more data required to be read
|
||||
reportPacketProgress(*this, connection);
|
||||
break;
|
||||
case ReadPacket::noData:
|
||||
// could not read anything from socket
|
||||
break;
|
||||
}
|
||||
} while (packetStatus == ReadPacket::success && countProcessed < kMaxPacketsPerUpdate);
|
||||
} while (packetStatus == ReadPacket::success && countProcessed < kMaxPacketsPerTick);
|
||||
|
||||
if (!connection.ReceivedPacketRecently())
|
||||
if (!connection.ReceivedDataRecently())
|
||||
{
|
||||
LOG_INFO(
|
||||
"No data received recently from connection %s, disconnecting connection.",
|
||||
connection.Socket->GetIpAddress().c_str());
|
||||
|
||||
if (!connection.GetLastDisconnectReason())
|
||||
{
|
||||
connection.SetLastDisconnectReason(STR_MULTIPLAYER_NO_DATA);
|
||||
@@ -1812,7 +1871,7 @@ namespace OpenRCT2::Network
|
||||
}
|
||||
|
||||
// This is called at the end of each game tick, this where things should be processed that affects the game state.
|
||||
void NetworkBase::ProcessPending()
|
||||
void NetworkBase::PostTick()
|
||||
{
|
||||
if (GetMode() == Mode::server)
|
||||
{
|
||||
@@ -2302,8 +2361,7 @@ namespace OpenRCT2::Network
|
||||
|
||||
void NetworkBase::ServerHandleHeartbeat(Connection& connection, Packet& packet)
|
||||
{
|
||||
LOG_VERBOSE("Client %s heartbeat", connection.Socket->GetHostName());
|
||||
connection.ResetLastPacketTime();
|
||||
LOG_VERBOSE("Client %s heartbeat", connection.Socket->GetIpAddress().c_str());
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Handle_AUTH(Connection& connection, Packet& packet)
|
||||
@@ -2392,34 +2450,17 @@ namespace OpenRCT2::Network
|
||||
ServerSendToken(connection);
|
||||
}
|
||||
|
||||
static void OpenNetworkProgress(StringId captionStringId)
|
||||
{
|
||||
auto captionString = GetContext()->GetLocalisationService().GetString(captionStringId);
|
||||
auto intent = Intent(INTENT_ACTION_PROGRESS_OPEN);
|
||||
intent.PutExtra(INTENT_EXTRA_MESSAGE, captionString);
|
||||
intent.PutExtra(INTENT_EXTRA_CALLBACK, []() -> void { OpenRCT2::GetContext()->GetNetwork().Close(); });
|
||||
ContextOpenIntent(&intent);
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Handle_OBJECTS_LIST(Connection& connection, Packet& packet)
|
||||
{
|
||||
auto& repo = GetContext().GetObjectRepository();
|
||||
|
||||
uint32_t index = 0;
|
||||
uint32_t totalObjects = 0;
|
||||
packet >> index >> totalObjects;
|
||||
uint32_t objectCount{};
|
||||
packet >> objectCount;
|
||||
|
||||
static constexpr uint32_t kObjectStartIndex = 0;
|
||||
if (index == kObjectStartIndex)
|
||||
std::vector<ObjectEntryDescriptor> missingObjects;
|
||||
|
||||
for (uint32_t i = 0; i < objectCount; ++i)
|
||||
{
|
||||
_missingObjects.clear();
|
||||
}
|
||||
|
||||
if (totalObjects > 0)
|
||||
{
|
||||
OpenNetworkProgress(STR_MULTIPLAYER_RECEIVING_OBJECTS_LIST);
|
||||
GetContext().SetProgress(index + 1, totalObjects);
|
||||
|
||||
uint8_t objectType{};
|
||||
packet >> objectType;
|
||||
|
||||
@@ -2434,7 +2475,7 @@ namespace OpenRCT2::Network
|
||||
{
|
||||
auto objectName = std::string(entry->GetName());
|
||||
LOG_VERBOSE("Requesting object %s with checksum %x from server", objectName.c_str(), entry->checksum);
|
||||
_missingObjects.push_back(ObjectEntryDescriptor(*entry));
|
||||
missingObjects.push_back(ObjectEntryDescriptor(*entry));
|
||||
}
|
||||
else if (object->ObjectEntry.checksum != entry->checksum || object->ObjectEntry.flags != entry->flags)
|
||||
{
|
||||
@@ -2456,69 +2497,35 @@ namespace OpenRCT2::Network
|
||||
{
|
||||
auto objectName = std::string(identifier);
|
||||
LOG_VERBOSE("Requesting object %s from server", objectName.c_str());
|
||||
_missingObjects.push_back(ObjectEntryDescriptor(objectName));
|
||||
missingObjects.push_back(ObjectEntryDescriptor(objectName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index + 1 >= totalObjects)
|
||||
{
|
||||
LOG_VERBOSE("client received object list, it has %u entries", totalObjects);
|
||||
Client_Send_MAPREQUEST(_missingObjects);
|
||||
_missingObjects.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Handle_SCRIPTS_HEADER(Connection& connection, Packet& packet)
|
||||
{
|
||||
uint32_t numScripts{};
|
||||
uint32_t dataSize{};
|
||||
packet >> numScripts >> dataSize;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
_serverScriptsData.data.Clear();
|
||||
_serverScriptsData.pluginCount = numScripts;
|
||||
_serverScriptsData.dataSize = dataSize;
|
||||
#else
|
||||
if (numScripts > 0)
|
||||
{
|
||||
connection.SetLastDisconnectReason("The client requires plugin support.");
|
||||
Close();
|
||||
}
|
||||
#endif
|
||||
LOG_VERBOSE("client received object list, it has %u entries, %zu missing", objectCount, missingObjects.size());
|
||||
Client_Send_MAPREQUEST(missingObjects);
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Handle_SCRIPTS_DATA(Connection& connection, Packet& packet)
|
||||
{
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
uint32_t dataSize{};
|
||||
packet >> dataSize;
|
||||
Guard::Assert(dataSize > 0);
|
||||
auto& scriptEngine = GetContext().GetScriptEngine();
|
||||
|
||||
const auto* data = packet.Read(dataSize);
|
||||
Guard::Assert(data != nullptr);
|
||||
uint32_t count{};
|
||||
packet >> count;
|
||||
|
||||
auto& scriptsData = _serverScriptsData.data;
|
||||
scriptsData.Write(data, dataSize);
|
||||
|
||||
if (scriptsData.GetLength() == _serverScriptsData.dataSize)
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
auto& scriptEngine = GetContext().GetScriptEngine();
|
||||
uint32_t codeSize{};
|
||||
packet >> codeSize;
|
||||
|
||||
scriptsData.SetPosition(0);
|
||||
for (uint32_t i = 0; i < _serverScriptsData.pluginCount; ++i)
|
||||
{
|
||||
const auto codeSize = scriptsData.ReadValue<uint32_t>();
|
||||
const auto scriptData = scriptsData.ReadArray<char>(codeSize);
|
||||
const uint8_t* scriptData = packet.Read(codeSize);
|
||||
|
||||
auto code = std::string_view(reinterpret_cast<const char*>(scriptData.get()), codeSize);
|
||||
scriptEngine.AddNetworkPlugin(code);
|
||||
}
|
||||
Guard::Assert(scriptsData.GetPosition() == scriptsData.GetLength());
|
||||
auto code = std::string_view(reinterpret_cast<const char*>(scriptData), codeSize);
|
||||
scriptEngine.AddNetworkPlugin(code);
|
||||
|
||||
// Empty the current buffer.
|
||||
_serverScriptsData = {};
|
||||
LOG_VERBOSE("Received and loaded network script plugin %u/%u", i + 1, count);
|
||||
}
|
||||
#else
|
||||
connection.SetLastDisconnectReason("The client requires plugin support.");
|
||||
@@ -2768,84 +2775,59 @@ namespace OpenRCT2::Network
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Handle_BEGINMAP([[maybe_unused]] Connection& connection, Packet& packet)
|
||||
{
|
||||
// Start of a new map load, clear the queue now as we have to buffer them
|
||||
// until the map is fully loaded.
|
||||
GameActions::ClearQueue();
|
||||
GameActions::SuspendQueue();
|
||||
|
||||
displayNetworkProgress(STR_LOADING_SAVED_GAME);
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Handle_MAP([[maybe_unused]] Connection& connection, Packet& packet)
|
||||
{
|
||||
uint32_t size, offset;
|
||||
packet >> size >> offset;
|
||||
int32_t chunksize = static_cast<int32_t>(packet.Header.Size - packet.BytesRead);
|
||||
if (chunksize <= 0)
|
||||
// Allow queue processing of game actions again.
|
||||
GameActions::ResumeQueue();
|
||||
|
||||
// This prevents invoking the callback for when the window closes which would close the connection.
|
||||
GetContext().CloseProgress();
|
||||
|
||||
GameUnloadScripts();
|
||||
GameNotifyMapChange();
|
||||
|
||||
auto ms = MemoryStream(packet.Data.data(), packet.Data.size());
|
||||
if (LoadMap(&ms))
|
||||
{
|
||||
return;
|
||||
GameLoadInit();
|
||||
GameLoadScripts();
|
||||
GameNotifyMapChanged();
|
||||
|
||||
// This seems wrong, we want to catch up to that tick so we shouldn't mess with this.
|
||||
//_serverState.tick = getGameState().currentTicks;
|
||||
|
||||
_serverState.state = ServerStatus::ok;
|
||||
_clientMapLoaded = true;
|
||||
gFirstTimeSaving = true;
|
||||
|
||||
// Notify user he is now online and which shortcut key enables chat
|
||||
ChatShowConnectedMessage();
|
||||
|
||||
// Fix invalid vehicle sprite sizes, thus preventing visual corruption of sprites
|
||||
FixInvalidVehicleSpriteSizes();
|
||||
|
||||
// NOTE: Game actions are normally processed before processing the player list.
|
||||
// Given that during map load game actions are buffered we have to process the
|
||||
// player list first to have valid players for the queued game actions.
|
||||
ProcessPlayerList();
|
||||
}
|
||||
if (offset == 0)
|
||||
else
|
||||
{
|
||||
// Start of a new map load, clear the queue now as we have to buffer them
|
||||
// until the map is fully loaded.
|
||||
GameActions::ClearQueue();
|
||||
GameActions::SuspendQueue();
|
||||
// Something went wrong, game is not loaded. Return to main screen.
|
||||
auto loadOrQuitAction = GameActions::LoadOrQuitAction(
|
||||
GameActions::LoadOrQuitModes::OpenSavePrompt, PromptMode::saveBeforeQuit);
|
||||
|
||||
_serverTickData.clear();
|
||||
_clientMapLoaded = false;
|
||||
|
||||
OpenNetworkProgress(STR_MULTIPLAYER_DOWNLOADING_MAP);
|
||||
}
|
||||
if (size > chunk_buffer.size())
|
||||
{
|
||||
chunk_buffer.resize(size);
|
||||
}
|
||||
|
||||
const auto currentProgressKiB = (offset + chunksize) / 1024;
|
||||
const auto totalSizeKiB = size / 1024;
|
||||
|
||||
GetContext().SetProgress(currentProgressKiB, totalSizeKiB, STR_STRING_M_OF_N_KIB);
|
||||
|
||||
std::memcpy(&chunk_buffer[offset], const_cast<void*>(static_cast<const void*>(packet.Read(chunksize))), chunksize);
|
||||
if (offset + chunksize == size)
|
||||
{
|
||||
// Allow queue processing of game actions again.
|
||||
GameActions::ResumeQueue();
|
||||
|
||||
ContextForceCloseWindowByClass(WindowClass::progressWindow);
|
||||
GameUnloadScripts();
|
||||
GameNotifyMapChange();
|
||||
|
||||
bool has_to_free = false;
|
||||
uint8_t* data = &chunk_buffer[0];
|
||||
size_t data_size = size;
|
||||
auto ms = MemoryStream(data, data_size);
|
||||
if (LoadMap(&ms))
|
||||
{
|
||||
GameLoadInit();
|
||||
GameLoadScripts();
|
||||
GameNotifyMapChanged();
|
||||
_serverState.tick = getGameState().currentTicks;
|
||||
// NetworkStatusOpen("Loaded new map from network");
|
||||
_serverState.state = ServerStatus::ok;
|
||||
_clientMapLoaded = true;
|
||||
gFirstTimeSaving = true;
|
||||
|
||||
// Notify user he is now online and which shortcut key enables chat
|
||||
ChatShowConnectedMessage();
|
||||
|
||||
// Fix invalid vehicle sprite sizes, thus preventing visual corruption of sprites
|
||||
FixInvalidVehicleSpriteSizes();
|
||||
|
||||
// NOTE: Game actions are normally processed before processing the player list.
|
||||
// Given that during map load game actions are buffered we have to process the
|
||||
// player list first to have valid players for the queued game actions.
|
||||
ProcessPlayerList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something went wrong, game is not loaded. Return to main screen.
|
||||
auto loadOrQuitAction = GameActions::LoadOrQuitAction(
|
||||
GameActions::LoadOrQuitModes::OpenSavePrompt, PromptMode::saveBeforeQuit);
|
||||
GameActions::Execute(&loadOrQuitAction, getGameState());
|
||||
}
|
||||
if (has_to_free)
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
loadOrQuitAction.Execute(getGameState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2980,7 +2962,7 @@ namespace OpenRCT2::Network
|
||||
packet >> tick >> actionType;
|
||||
|
||||
MemoryStream stream;
|
||||
const size_t size = packet.Header.Size - packet.BytesRead;
|
||||
const size_t size = packet.Header.size - packet.BytesRead;
|
||||
stream.WriteArray(packet.Read(size), size);
|
||||
stream.SetPosition(0);
|
||||
|
||||
@@ -3070,7 +3052,7 @@ namespace OpenRCT2::Network
|
||||
}
|
||||
|
||||
DataSerialiser stream(false);
|
||||
const size_t size = packet.Header.Size - packet.BytesRead;
|
||||
const size_t size = packet.Header.size - packet.BytesRead;
|
||||
stream.GetStream().WriteArray(packet.Read(size), size);
|
||||
stream.GetStream().SetPosition(0);
|
||||
|
||||
@@ -3306,9 +3288,14 @@ namespace OpenRCT2::Network
|
||||
GetContext()->GetNetwork().Update();
|
||||
}
|
||||
|
||||
void ProcessPending()
|
||||
void Tick()
|
||||
{
|
||||
GetContext()->GetNetwork().ProcessPending();
|
||||
GetContext()->GetNetwork().Tick();
|
||||
}
|
||||
|
||||
void PostTick()
|
||||
{
|
||||
GetContext()->GetNetwork().PostTick();
|
||||
}
|
||||
|
||||
void Flush()
|
||||
@@ -4147,10 +4134,13 @@ namespace OpenRCT2::Network
|
||||
void SendGameAction(const GameActions::GameAction* action)
|
||||
{
|
||||
}
|
||||
void Tick()
|
||||
{
|
||||
}
|
||||
void Update()
|
||||
{
|
||||
}
|
||||
void ProcessPending()
|
||||
void PostTick()
|
||||
{
|
||||
}
|
||||
int32_t BeginClient(const std::string& host, int32_t port)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "NetworkTypes.h"
|
||||
#include "NetworkUser.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
@@ -37,10 +38,10 @@ namespace OpenRCT2::Network
|
||||
bool Init();
|
||||
void Close();
|
||||
uint32_t GetServerTick() const noexcept;
|
||||
// FIXME: This is currently the wrong function to override in System, will be refactored later.
|
||||
void Update() override final;
|
||||
void Update() final;
|
||||
void Tick() final;
|
||||
void Flush();
|
||||
void ProcessPending();
|
||||
void PostTick() final;
|
||||
void ProcessPlayerList();
|
||||
auto GetPlayerIteratorByID(uint8_t id) const;
|
||||
auto GetGroupIteratorByID(uint8_t id) const;
|
||||
@@ -61,6 +62,7 @@ namespace OpenRCT2::Network
|
||||
void CloseConnection();
|
||||
Player* AddPlayer(const std::string& name, const std::string& keyhash);
|
||||
void ProcessPacket(Connection& connection, Packet& packet);
|
||||
bool UpdateConnection(Connection& connection);
|
||||
|
||||
public: // Server
|
||||
Connection* GetPlayerConnection(uint8_t id) const;
|
||||
@@ -81,6 +83,7 @@ namespace OpenRCT2::Network
|
||||
void SetupDefaultGroups();
|
||||
void RemovePlayer(std::unique_ptr<Connection>& connection);
|
||||
void UpdateServer();
|
||||
void TickServer();
|
||||
void ServerClientDisconnected(std::unique_ptr<Connection>& connection);
|
||||
bool SaveMap(IStream* stream, const std::vector<const ObjectRepositoryItem*>& objects) const;
|
||||
std::vector<uint8_t> SaveForNetwork(const std::vector<const ObjectRepositoryItem*>& objects) const;
|
||||
@@ -136,6 +139,7 @@ namespace OpenRCT2::Network
|
||||
void ServerClientDisconnected();
|
||||
bool LoadMap(IStream* stream);
|
||||
void UpdateClient();
|
||||
void TickClient();
|
||||
|
||||
// Packet dispatchers.
|
||||
void Client_Send_RequestGameState(uint32_t tick);
|
||||
@@ -152,6 +156,7 @@ namespace OpenRCT2::Network
|
||||
|
||||
// Handlers.
|
||||
void Client_Handle_AUTH(Connection& connection, Packet& packet);
|
||||
void Client_Handle_BEGINMAP([[maybe_unused]] Connection& connection, Packet& packet);
|
||||
void Client_Handle_MAP(Connection& connection, Packet& packet);
|
||||
void Client_Handle_CHAT(Connection& connection, Packet& packet);
|
||||
void Client_Handle_GAME_ACTION(Connection& connection, Packet& packet);
|
||||
@@ -167,10 +172,8 @@ namespace OpenRCT2::Network
|
||||
void Client_Handle_EVENT(Connection& connection, Packet& packet);
|
||||
void Client_Handle_TOKEN(Connection& connection, Packet& packet);
|
||||
void Client_Handle_OBJECTS_LIST(Connection& connection, Packet& packet);
|
||||
void Client_Handle_SCRIPTS_HEADER(Connection& connection, Packet& packet);
|
||||
void Client_Handle_SCRIPTS_DATA(Connection& connection, Packet& packet);
|
||||
void Client_Handle_GAMESTATE(Connection& connection, Packet& packet);
|
||||
|
||||
std::vector<uint8_t> _challenge;
|
||||
std::map<uint32_t, GameActions::GameAction::Callback_t> _gameActionCallbacks;
|
||||
Key _key;
|
||||
@@ -190,7 +193,6 @@ namespace OpenRCT2::Network
|
||||
private: // Common Data
|
||||
using CommandHandler = void (NetworkBase::*)(Connection& connection, Packet& packet);
|
||||
|
||||
std::vector<uint8_t> chunk_buffer;
|
||||
std::ofstream _chat_log_fs;
|
||||
uint32_t _lastUpdateTime = 0;
|
||||
uint32_t _currentDeltaTime = 0;
|
||||
@@ -223,19 +225,11 @@ namespace OpenRCT2::Network
|
||||
std::string spriteHash;
|
||||
};
|
||||
|
||||
struct ServerScriptsData
|
||||
{
|
||||
uint32_t pluginCount{};
|
||||
uint32_t dataSize{};
|
||||
MemoryStream data;
|
||||
};
|
||||
|
||||
std::unordered_map<Command, CommandHandler> client_command_handlers;
|
||||
std::unique_ptr<Connection> _serverConnection;
|
||||
std::map<uint32_t, PlayerListUpdate> _pendingPlayerLists;
|
||||
std::multimap<uint32_t, Player> _pendingPlayerInfo;
|
||||
std::map<uint32_t, ServerTickData> _serverTickData;
|
||||
std::vector<ObjectEntryDescriptor> _missingObjects;
|
||||
std::string _host;
|
||||
std::string _chatLogPath;
|
||||
std::string _chatLogFilenameFormat = "%Y%m%d-%H%M%S.txt";
|
||||
@@ -252,7 +246,6 @@ namespace OpenRCT2::Network
|
||||
SocketStatus _lastConnectStatus = SocketStatus::closed;
|
||||
bool _requireReconnect = false;
|
||||
bool _clientMapLoaded = false;
|
||||
ServerScriptsData _serverScriptsData{};
|
||||
};
|
||||
} // namespace OpenRCT2::Network
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include "NetworkConnection.h"
|
||||
|
||||
#include "../Diagnostic.h"
|
||||
#include "../core/Diagnostics.hpp"
|
||||
#include "../core/String.hpp"
|
||||
#include "../localisation/Formatting.h"
|
||||
#include "../platform/Platform.h"
|
||||
@@ -22,42 +24,80 @@
|
||||
namespace OpenRCT2::Network
|
||||
{
|
||||
static constexpr size_t kDisconnectReasonBufSize = 256;
|
||||
static constexpr size_t kBufferSize = (1024 * 64) - 1; // 64 KiB, maximum packet size.
|
||||
#ifndef DEBUG
|
||||
static constexpr size_t kNoDataTimeout = 20; // Seconds.
|
||||
#endif
|
||||
|
||||
static_assert(kBufferSize <= std::numeric_limits<uint16_t>::max(), "kBufferSize too big, uint16_t is max.");
|
||||
static constexpr size_t kBufferSize = 1024 * 128; // 128 KiB.
|
||||
static constexpr size_t kNoDataTimeout = 40; // Seconds.
|
||||
|
||||
Connection::Connection() noexcept
|
||||
{
|
||||
ResetLastPacketTime();
|
||||
_lastReceiveTime = Platform::GetTicks();
|
||||
}
|
||||
|
||||
void Connection::update()
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
receiveData();
|
||||
SendQueuedData();
|
||||
}
|
||||
|
||||
void Connection::receiveData()
|
||||
{
|
||||
uint8_t buffer[kBufferSize];
|
||||
size_t bytesRead = 0;
|
||||
|
||||
ReadPacket status = Socket->ReceiveData(buffer, sizeof(buffer), &bytesRead);
|
||||
if (status == ReadPacket::disconnected)
|
||||
{
|
||||
Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == ReadPacket::success)
|
||||
{
|
||||
_lastReceiveTime = Platform::GetTicks();
|
||||
_inboundBuffer.insert(_inboundBuffer.end(), buffer, buffer + bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
ReadPacket Connection::readPacket()
|
||||
{
|
||||
size_t bytesRead = 0;
|
||||
uint32_t magic = 0;
|
||||
|
||||
// Read packet header.
|
||||
auto& header = InboundPacket.Header;
|
||||
if (InboundPacket.BytesTransferred < sizeof(InboundPacket.Header))
|
||||
// Check if we have enough data for the magic.
|
||||
if (_inboundBuffer.size() < sizeof(magic))
|
||||
{
|
||||
const size_t missingLength = sizeof(header) - InboundPacket.BytesTransferred;
|
||||
return ReadPacket::moreData;
|
||||
}
|
||||
|
||||
uint8_t* buffer = reinterpret_cast<uint8_t*>(&InboundPacket.Header);
|
||||
// Read magic.
|
||||
std::memcpy(&magic, _inboundBuffer.data(), sizeof(magic));
|
||||
|
||||
ReadPacket status = Socket->ReceiveData(buffer, missingLength, &bytesRead);
|
||||
if (status != ReadPacket::success)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
size_t totalPacketLength = 0;
|
||||
size_t headerSize = 0;
|
||||
|
||||
InboundPacket.BytesTransferred += bytesRead;
|
||||
if (InboundPacket.BytesTransferred < sizeof(InboundPacket.Header))
|
||||
{
|
||||
// If still not enough data for header, keep waiting.
|
||||
return ReadPacket::moreData;
|
||||
}
|
||||
magic = Convert::NetworkToHost(magic);
|
||||
if (magic == PacketHeader::kMagic)
|
||||
{
|
||||
// New format.
|
||||
auto& header = InboundPacket.Header;
|
||||
std::memcpy(&header, _inboundBuffer.data(), sizeof(header));
|
||||
|
||||
header.magic = magic;
|
||||
header.version = Convert::NetworkToHost(header.version);
|
||||
header.size = Convert::NetworkToHost(header.size);
|
||||
header.id = Convert::NetworkToHost(header.id);
|
||||
|
||||
headerSize = sizeof(header);
|
||||
totalPacketLength = sizeof(header) + header.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Legacy format.
|
||||
PacketLegacyHeader header{};
|
||||
std::memcpy(&header, _inboundBuffer.data(), sizeof(header));
|
||||
|
||||
// Normalise values.
|
||||
header.Size = Convert::NetworkToHost(header.Size);
|
||||
@@ -65,61 +105,81 @@ namespace OpenRCT2::Network
|
||||
|
||||
// NOTE: For compatibility reasons for the master server we need to remove sizeof(Header.Id) from the size.
|
||||
// Previously the Id field was not part of the header rather part of the body.
|
||||
header.Size -= std::min<uint16_t>(header.Size, sizeof(header.Id));
|
||||
// We correct the size to have only the length of the body.
|
||||
if (header.Size < sizeof(header.Id))
|
||||
{
|
||||
// This is a malformed packet, disconnect.
|
||||
LOG_INFO(
|
||||
"Received malformed packet (size: %u) from {%s}, disconnecting.", header.Size,
|
||||
Socket->GetIpAddress().c_str());
|
||||
|
||||
// Fall-through: Read rest of packet.
|
||||
Disconnect();
|
||||
return ReadPacket::disconnected;
|
||||
}
|
||||
|
||||
header.Size -= sizeof(header.Id);
|
||||
|
||||
// Fill in new header format.
|
||||
InboundPacket.Header.magic = PacketHeader::kMagic;
|
||||
InboundPacket.Header.size = header.Size;
|
||||
InboundPacket.Header.id = header.Id;
|
||||
|
||||
headerSize = sizeof(header);
|
||||
totalPacketLength = sizeof(header) + header.Size;
|
||||
|
||||
_isLegacyProtocol = true;
|
||||
}
|
||||
|
||||
if (_inboundBuffer.size() < totalPacketLength)
|
||||
{
|
||||
InboundPacket.BytesTransferred = _inboundBuffer.size();
|
||||
return ReadPacket::moreData;
|
||||
}
|
||||
|
||||
// Read packet body.
|
||||
{
|
||||
// NOTE: BytesTransfered includes the header length, this will not underflow.
|
||||
const size_t missingLength = header.Size - (InboundPacket.BytesTransferred - sizeof(header));
|
||||
InboundPacket.BytesTransferred = totalPacketLength;
|
||||
InboundPacket.Write(_inboundBuffer.data() + headerSize, totalPacketLength - headerSize);
|
||||
|
||||
uint8_t buffer[kBufferSize];
|
||||
// Remove read data from buffer.
|
||||
_inboundBuffer.erase(_inboundBuffer.begin(), _inboundBuffer.begin() + totalPacketLength);
|
||||
|
||||
if (missingLength > 0)
|
||||
{
|
||||
ReadPacket status = Socket->ReceiveData(buffer, std::min(missingLength, kBufferSize), &bytesRead);
|
||||
if (status != ReadPacket::success)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
RecordPacketStats(InboundPacket, false);
|
||||
|
||||
InboundPacket.BytesTransferred += bytesRead;
|
||||
InboundPacket.Write(buffer, bytesRead);
|
||||
}
|
||||
|
||||
if (InboundPacket.Data.size() == header.Size)
|
||||
{
|
||||
// Received complete packet.
|
||||
_lastPacketTime = Platform::GetTicks();
|
||||
|
||||
RecordPacketStats(InboundPacket, false);
|
||||
|
||||
return ReadPacket::success;
|
||||
}
|
||||
}
|
||||
|
||||
return ReadPacket::moreData;
|
||||
return ReadPacket::success;
|
||||
}
|
||||
|
||||
static sfl::small_vector<uint8_t, 512> serializePacket(const Packet& packet)
|
||||
static sfl::small_vector<uint8_t, 512> serializePacket(bool legacyProtocol, const Packet& packet)
|
||||
{
|
||||
// NOTE: For compatibility reasons for the master server we need to add sizeof(Header.Id) to the size.
|
||||
// Previously the Id field was not part of the header rather part of the body.
|
||||
const auto bodyLength = packet.Data.size() + sizeof(packet.Header.Id);
|
||||
|
||||
Guard::Assert(bodyLength <= std::numeric_limits<uint16_t>::max(), "Packet size too large");
|
||||
|
||||
auto header = packet.Header;
|
||||
header.Size = static_cast<uint16_t>(bodyLength);
|
||||
header.Size = Convert::HostToNetwork(header.Size);
|
||||
header.Id = ByteSwapBE(header.Id);
|
||||
|
||||
sfl::small_vector<uint8_t, 512> buffer;
|
||||
buffer.reserve(sizeof(header) + packet.Data.size());
|
||||
|
||||
buffer.insert(buffer.end(), reinterpret_cast<uint8_t*>(&header), reinterpret_cast<uint8_t*>(&header) + sizeof(header));
|
||||
if (legacyProtocol)
|
||||
{
|
||||
// NOTE: For compatibility reasons for the master server we need to add sizeof(Header.Id) to the size.
|
||||
// Previously the Id field was not part of the header rather part of the body.
|
||||
const auto bodyLength = packet.Data.size() + sizeof(PacketLegacyHeader::Id);
|
||||
|
||||
Guard::Assert(bodyLength <= std::numeric_limits<uint16_t>::max(), "Packet size too large");
|
||||
|
||||
PacketLegacyHeader header{};
|
||||
header.Size = static_cast<uint16_t>(bodyLength);
|
||||
header.Size = Convert::HostToNetwork(header.Size);
|
||||
header.Id = ByteSwapBE(packet.Header.id);
|
||||
|
||||
buffer.insert(
|
||||
buffer.end(), reinterpret_cast<uint8_t*>(&header), reinterpret_cast<uint8_t*>(&header) + sizeof(header));
|
||||
}
|
||||
else
|
||||
{
|
||||
PacketHeader header{};
|
||||
header.magic = Convert::HostToNetwork(PacketHeader::kMagic);
|
||||
header.version = Convert::HostToNetwork(PacketHeader::kVersion);
|
||||
header.size = Convert::HostToNetwork(static_cast<uint32_t>(packet.Data.size()));
|
||||
header.id = Convert::HostToNetwork(packet.Header.id);
|
||||
|
||||
buffer.insert(
|
||||
buffer.end(), reinterpret_cast<uint8_t*>(&header), reinterpret_cast<uint8_t*>(&header) + sizeof(header));
|
||||
}
|
||||
|
||||
buffer.insert(buffer.end(), packet.Data.begin(), packet.Data.end());
|
||||
|
||||
return buffer;
|
||||
@@ -129,7 +189,7 @@ namespace OpenRCT2::Network
|
||||
{
|
||||
if (AuthStatus == Auth::ok || !packet.CommandRequiresAuth())
|
||||
{
|
||||
const auto payload = serializePacket(packet);
|
||||
const auto payload = serializePacket(_isLegacyProtocol, packet);
|
||||
if (front)
|
||||
{
|
||||
_outboundBuffer.insert(_outboundBuffer.begin(), payload.begin(), payload.end());
|
||||
@@ -168,20 +228,16 @@ namespace OpenRCT2::Network
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::ResetLastPacketTime() noexcept
|
||||
bool Connection::ReceivedDataRecently() const noexcept
|
||||
{
|
||||
_lastPacketTime = Platform::GetTicks();
|
||||
}
|
||||
|
||||
bool Connection::ReceivedPacketRecently() const noexcept
|
||||
{
|
||||
#ifndef DEBUG
|
||||
constexpr auto kTimeoutMs = kNoDataTimeout * 1000;
|
||||
if (Platform::GetTicks() > _lastPacketTime + kTimeoutMs)
|
||||
|
||||
const auto timeSinceLastRecv = Platform::GetTicks() - _lastReceiveTime;
|
||||
if (timeSinceLastRecv > kTimeoutMs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -231,6 +287,22 @@ namespace OpenRCT2::Network
|
||||
stats.bytesReceived[EnumValue(StatisticsGroup::Total)] += packetSize;
|
||||
}
|
||||
}
|
||||
|
||||
Command Connection::getPendingPacketCommand() const noexcept
|
||||
{
|
||||
return InboundPacket.GetCommand();
|
||||
}
|
||||
|
||||
size_t Connection::getPendingPacketSize() const noexcept
|
||||
{
|
||||
return InboundPacket.Header.size;
|
||||
}
|
||||
|
||||
size_t Connection::getPendingPacketAvailable() const noexcept
|
||||
{
|
||||
return InboundPacket.BytesTransferred;
|
||||
}
|
||||
|
||||
} // namespace OpenRCT2::Network
|
||||
|
||||
#endif
|
||||
|
||||
@@ -45,28 +45,35 @@ namespace OpenRCT2::Network
|
||||
|
||||
Connection() noexcept;
|
||||
|
||||
void update();
|
||||
ReadPacket readPacket();
|
||||
void QueuePacket(const Packet& packet, bool front = false);
|
||||
|
||||
Command getPendingPacketCommand() const noexcept;
|
||||
size_t getPendingPacketSize() const noexcept;
|
||||
size_t getPendingPacketAvailable() const noexcept;
|
||||
|
||||
// This will not immediately disconnect the client. The disconnect
|
||||
// will happen post-tick.
|
||||
void Disconnect() noexcept;
|
||||
|
||||
bool IsValid() const;
|
||||
void SendQueuedData();
|
||||
void ResetLastPacketTime() noexcept;
|
||||
bool ReceivedPacketRecently() const noexcept;
|
||||
bool ReceivedDataRecently() const noexcept;
|
||||
|
||||
const utf8* GetLastDisconnectReason() const noexcept;
|
||||
void SetLastDisconnectReason(std::string_view src);
|
||||
void SetLastDisconnectReason(const StringId string_id, void* args = nullptr);
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> _inboundBuffer;
|
||||
std::vector<uint8_t> _outboundBuffer;
|
||||
uint32_t _lastPacketTime = 0;
|
||||
uint32_t _lastReceiveTime = 0;
|
||||
std::string _lastDisconnectReason;
|
||||
bool _isLegacyProtocol = false;
|
||||
|
||||
void RecordPacketStats(const Packet& packet, bool sending);
|
||||
void receiveData();
|
||||
};
|
||||
} // namespace OpenRCT2::Network
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
namespace OpenRCT2::Network
|
||||
{
|
||||
Packet::Packet(Command id) noexcept
|
||||
: Header{ 0, id }
|
||||
: Header{ PacketHeader::kMagic, PacketHeader::kVersion, 0, id }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace OpenRCT2::Network
|
||||
|
||||
Command Packet::GetCommand() const noexcept
|
||||
{
|
||||
return Header.Id;
|
||||
return Header.id;
|
||||
}
|
||||
|
||||
void Packet::Clear() noexcept
|
||||
|
||||
@@ -19,12 +19,23 @@
|
||||
namespace OpenRCT2::Network
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct PacketHeader
|
||||
struct PacketLegacyHeader
|
||||
{
|
||||
uint16_t Size = 0;
|
||||
Command Id = Command::invalid;
|
||||
};
|
||||
static_assert(sizeof(PacketHeader) == 6);
|
||||
static_assert(sizeof(PacketLegacyHeader) == 6);
|
||||
|
||||
struct PacketHeader
|
||||
{
|
||||
static constexpr uint32_t kMagic = 0x3254524F; // 'ORT2'
|
||||
static constexpr uint16_t kVersion = 2;
|
||||
|
||||
uint32_t magic{};
|
||||
uint16_t version{};
|
||||
uint32_t size{};
|
||||
Command id{};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct Packet final
|
||||
@@ -49,7 +60,7 @@ namespace OpenRCT2::Network
|
||||
template<typename T>
|
||||
Packet& operator>>(T& value)
|
||||
{
|
||||
if (BytesRead + sizeof(value) > Header.Size)
|
||||
if (BytesRead + sizeof(value) > Header.size)
|
||||
{
|
||||
value = T{};
|
||||
}
|
||||
|
||||
@@ -82,9 +82,10 @@ namespace OpenRCT2::Network
|
||||
playerInfo,
|
||||
requestGameState,
|
||||
gameState,
|
||||
scriptsHeader,
|
||||
scriptsHeader, // Deprecated.
|
||||
scriptsData,
|
||||
heartbeat,
|
||||
beginMap,
|
||||
max,
|
||||
invalid = static_cast<uint32_t>(-1),
|
||||
};
|
||||
|
||||
@@ -982,17 +982,4 @@ namespace OpenRCT2::Network
|
||||
|
||||
} // namespace OpenRCT2::Network
|
||||
|
||||
namespace OpenRCT2::Convert
|
||||
{
|
||||
uint16_t HostToNetwork(uint16_t value)
|
||||
{
|
||||
return htons(value);
|
||||
}
|
||||
|
||||
uint16_t NetworkToHost(uint16_t value)
|
||||
{
|
||||
return ntohs(value);
|
||||
}
|
||||
} // namespace OpenRCT2::Convert
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/Endianness.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -106,6 +108,24 @@ namespace OpenRCT2::Network
|
||||
|
||||
namespace OpenRCT2::Convert
|
||||
{
|
||||
uint16_t HostToNetwork(uint16_t value);
|
||||
uint16_t NetworkToHost(uint16_t value);
|
||||
template<typename T>
|
||||
constexpr T HostToNetwork(T value)
|
||||
{
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
{
|
||||
return value; // already network order
|
||||
}
|
||||
else
|
||||
{
|
||||
return ByteSwapBE(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr T NetworkToHost(T value)
|
||||
{
|
||||
// Conversion is symmetric
|
||||
return HostToNetwork(value);
|
||||
}
|
||||
|
||||
} // namespace OpenRCT2::Convert
|
||||
|
||||
Reference in New Issue
Block a user