Index: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -451,8 +451,8 @@ void CNetClient::SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface) { - CGameSetupMessage gameSetup(scriptInterface); - gameSetup.m_Data = attrs; + CGameSetupMessage gameSetup; + gameSetup.m_Data = Script::StringifyJSON(ScriptRequest{scriptInterface}, attrs); SendMessage(&gameSetup); } Index: source/network/NetClientTurnManager.h =================================================================== --- source/network/NetClientTurnManager.h +++ source/network/NetClientTurnManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -34,7 +34,7 @@ void OnSimulationMessage(CSimulationMessage* msg) override; - void PostCommand(JS::HandleValue data) override; + void PostCommand(JS::MutableHandleValue data) override; /** * Notify the server that all commands are sent to prepare the connection for termination. Index: source/network/NetClientTurnManager.cpp =================================================================== --- source/network/NetClientTurnManager.cpp +++ source/network/NetClientTurnManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -25,6 +25,7 @@ #include "ps/Replay.h" #include "ps/Profile.h" #include "ps/Util.h" +#include "scriptinterface/JSON.h" #include "simulation2/Simulation2.h" #include @@ -42,12 +43,17 @@ { } -void CNetClientTurnManager::PostCommand(JS::HandleValue data) +void CNetClientTurnManager::PostCommand(JS::MutableHandleValue data) { NETCLIENTTURN_LOG("PostCommand()\n"); + CSimulationMessage msg; + msg.m_Client = m_ClientId; + msg.m_Player = m_PlayerId; + msg.m_Turn = m_CurrentTurn + m_CommandDelay; + msg.m_Data = Script::StringifyJSON(ScriptRequest{m_Simulation2.GetScriptInterface()}, data); + // Transmit command to server - CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + m_CommandDelay, data); m_NetClient.SendMessage(&msg); // Add to our local queue @@ -101,8 +107,12 @@ void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg) { + ScriptRequest rq{m_NetClient.GetScriptInterface()}; // Command received from the server - store it for later execution - AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn); + JS::RootedValue attribs{rq.cx}; + Script::ParseJSON(rq, msg->m_Data, &attribs); + + AddCommand(msg->m_Client, msg->m_Player, attribs, msg->m_Turn); } void CNetClientTurnManager::OnSyncError(u32 turn, const CStr& expectedHash, const std::vector& playerNames) Index: source/network/NetMessage.h =================================================================== --- source/network/NetMessage.h +++ source/network/NetMessage.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -100,58 +100,9 @@ * * @param pData Data buffer * @param dataSize Size of data buffer - * @param scriptInterface Script instance to use when constructing scripted messages * @return The new message created */ - static CNetMessage* CreateMessage(const void* pData, size_t dataSize, const ScriptInterface& scriptInterface); -}; - -/** - * Special message type for simulation commands. - * These commands are exposed as arbitrary JS objects, associated with a specific player. - */ -class CSimulationMessage : public CNetMessage -{ -public: - CSimulationMessage(const ScriptInterface& scriptInterface); - CSimulationMessage(const ScriptInterface& scriptInterface, u32 client, i32 player, u32 turn, JS::HandleValue data); - - /** The compiler can't create a copy constructor because of the PersistentRooted member, - * so we have to write it manually. - * NOTE: It doesn't clone the m_Data member and the copy will reference the same JS::Value! - */ - CSimulationMessage(const CSimulationMessage& orig); - - virtual u8* Serialize(u8* pBuffer) const; - virtual const u8* Deserialize(const u8* pStart, const u8* pEnd); - virtual size_t GetSerializedLength() const; - virtual CStr ToString() const; - - u32 m_Client; - i32 m_Player; - u32 m_Turn; - JS::PersistentRooted m_Data; -private: - const ScriptInterface& m_ScriptInterface; -}; - -/** - * Special message type for updated to game startup settings. - */ -class CGameSetupMessage : public CNetMessage -{ - NONCOPYABLE(CGameSetupMessage); -public: - CGameSetupMessage(const ScriptInterface& scriptInterface); - CGameSetupMessage(const ScriptInterface& scriptInterface, JS::HandleValue data); - virtual u8* Serialize(u8* pBuffer) const; - virtual const u8* Deserialize(const u8* pStart, const u8* pEnd); - virtual size_t GetSerializedLength() const; - virtual CStr ToString() const; - - JS::PersistentRootedValue m_Data; -private: - const ScriptInterface& m_ScriptInterface; + static CNetMessage* CreateMessage(const void* pData, size_t dataSize); }; // This time, the classes are created Index: source/network/NetMessage.cpp =================================================================== --- source/network/NetMessage.cpp +++ source/network/NetMessage.cpp @@ -91,9 +91,7 @@ return "Unknown Message " + CStr::FromInt(GetType()); } -CNetMessage* CNetMessageFactory::CreateMessage(const void* pData, - size_t dataSize, - const ScriptInterface& scriptInterface) +CNetMessage* CNetMessageFactory::CreateMessage(const void* pData, size_t dataSize) { CNetMessage* pNewMessage = NULL; CNetMessage header; @@ -104,7 +102,7 @@ switch (header.GetType()) { case NMT_GAME_SETUP: - pNewMessage = new CGameSetupMessage(scriptInterface); + pNewMessage = new CGameSetupMessage; break; case NMT_PLAYER_ASSIGNMENT: @@ -204,7 +202,7 @@ break; case NMT_SIMULATION_COMMAND: - pNewMessage = new CSimulationMessage(scriptInterface); + pNewMessage = new CSimulationMessage; break; case NMT_CLEAR_ALL_READY: Index: source/network/NetMessageSim.cpp =================================================================== --- source/network/NetMessageSim.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/* Copyright (C) 2021 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#include "precompiled.h" - -#include "NetMessage.h" - -#include "lib/utf8.h" -#include "scriptinterface/ScriptRequest.h" -#include "scriptinterface/JSON.h" -#include "simulation2/serialization/BinarySerializer.h" -#include "simulation2/serialization/StdDeserializer.h" -#include "simulation2/serialization/StdSerializer.h" // for DEBUG_SERIALIZER_ANNOTATE - -#include - -class CBufferBinarySerializerImpl -{ -public: - CBufferBinarySerializerImpl(u8* buffer) : - m_Buffer(buffer) - { - } - - void Put(const char* name, const u8* data, size_t len) - { - #if DEBUG_SERIALIZER_ANNOTATE - std::string tag = "<"; - tag.append(name); - tag.append(">"); - memcpy(m_Buffer, tag.c_str(), tag.length()); - m_Buffer += tag.length(); - #else - UNUSED2(name); - #endif - memcpy(m_Buffer, data, len); - m_Buffer += len; - } - - u8* m_Buffer; -}; - -/** - * Serializer instance that writes directly to a buffer (which must be long enough). - */ -class CBufferBinarySerializer : public CBinarySerializer -{ -public: - CBufferBinarySerializer(const ScriptInterface& scriptInterface, u8* buffer) : - CBinarySerializer(scriptInterface, buffer) - { - } - - u8* GetBuffer() - { - return m_Impl.m_Buffer; - } -}; - -class CLengthBinarySerializerImpl -{ -public: - CLengthBinarySerializerImpl() : - m_Length(0) - { - } - - void Put(const char* name, const u8* UNUSED(data), size_t len) - { - #if DEBUG_SERIALIZER_ANNOTATE - m_Length += 2; // '<' and '>' - m_Length += strlen(name); - #else - UNUSED2(name); - #endif - m_Length += len; - } - - size_t m_Length; -}; - -/** - * Serializer instance that simply counts how many bytes would be written. - */ -class CLengthBinarySerializer : public CBinarySerializer -{ -public: - CLengthBinarySerializer(const ScriptInterface& scriptInterface) : - CBinarySerializer(scriptInterface) - { - } - - size_t GetLength() - { - return m_Impl.m_Length; - } -}; - -CSimulationMessage::CSimulationMessage(const ScriptInterface& scriptInterface) : - CNetMessage(NMT_SIMULATION_COMMAND), m_ScriptInterface(scriptInterface) -{ - ScriptRequest rq(scriptInterface); - m_Data.init(rq.cx); -} - -CSimulationMessage::CSimulationMessage(const ScriptInterface& scriptInterface, u32 client, i32 player, u32 turn, JS::HandleValue data) : - CNetMessage(NMT_SIMULATION_COMMAND), m_ScriptInterface(scriptInterface), - m_Client(client), m_Player(player), m_Turn(turn) -{ - ScriptRequest rq(scriptInterface); - m_Data.init(rq.cx, data); -} - -CSimulationMessage::CSimulationMessage(const CSimulationMessage& orig) : - m_Client(orig.m_Client), - m_Player(orig.m_Player), - m_ScriptInterface(orig.m_ScriptInterface), - m_Turn(orig.m_Turn), - CNetMessage(orig) -{ - ScriptRequest rq(m_ScriptInterface); - m_Data.init(rq.cx, orig.m_Data); -} - -u8* CSimulationMessage::Serialize(u8* pBuffer) const -{ - // TODO: ought to handle serialization exceptions - // TODO: ought to represent common commands more efficiently - u8* pos = CNetMessage::Serialize(pBuffer); - CBufferBinarySerializer serializer(m_ScriptInterface, pos); - serializer.NumberU32_Unbounded("client", m_Client); - serializer.NumberI32_Unbounded("player", m_Player); - serializer.NumberU32_Unbounded("turn", m_Turn); - - serializer.ScriptVal("command", const_cast(&m_Data)); - return serializer.GetBuffer(); -} - -const u8* CSimulationMessage::Deserialize(const u8* pStart, const u8* pEnd) -{ - // TODO: ought to handle serialization exceptions - // TODO: ought to represent common commands more efficiently - const u8* pos = CNetMessage::Deserialize(pStart, pEnd); - std::istringstream stream(std::string(pos, pEnd)); - CStdDeserializer deserializer(m_ScriptInterface, stream); - deserializer.NumberU32_Unbounded("client", m_Client); - deserializer.NumberI32_Unbounded("player", m_Player); - deserializer.NumberU32_Unbounded("turn", m_Turn); - deserializer.ScriptVal("command", &m_Data); - return pEnd; -} - -size_t CSimulationMessage::GetSerializedLength() const -{ - // TODO: serializing twice is stupidly inefficient - we should just - // do it once, store the result, and use it here and in Serialize - CLengthBinarySerializer serializer(m_ScriptInterface); - serializer.NumberU32_Unbounded("client", m_Client); - serializer.NumberI32_Unbounded("player", m_Player); - serializer.NumberU32_Unbounded("turn", m_Turn); - - // TODO: The cast can probably be removed if and when ScriptVal can take a JS::HandleValue instead of - // a JS::MutableHandleValue (relies on JSAPI change). Also search for other casts like this one in that case. - serializer.ScriptVal("command", const_cast(&m_Data)); - return CNetMessage::GetSerializedLength() + serializer.GetLength(); -} - -CStr CSimulationMessage::ToString() const -{ - std::string source = Script::ToString(ScriptRequest(m_ScriptInterface), const_cast(&m_Data)); - - std::stringstream stream; - stream << "CSimulationMessage { m_Client: " << m_Client << ", m_Player: " << m_Player << ", m_Turn: " << m_Turn << ", m_Data: " << source << " }"; - return CStr(stream.str()); -} - - -CGameSetupMessage::CGameSetupMessage(const ScriptInterface& scriptInterface) : - CNetMessage(NMT_GAME_SETUP), m_ScriptInterface(scriptInterface) -{ - ScriptRequest rq(m_ScriptInterface); - m_Data.init(rq.cx); -} - -CGameSetupMessage::CGameSetupMessage(const ScriptInterface& scriptInterface, JS::HandleValue data) : - CNetMessage(NMT_GAME_SETUP), m_ScriptInterface(scriptInterface) -{ - ScriptRequest rq(m_ScriptInterface); - m_Data.init(rq.cx, data); -} - -u8* CGameSetupMessage::Serialize(u8* pBuffer) const -{ - // TODO: ought to handle serialization exceptions - u8* pos = CNetMessage::Serialize(pBuffer); - CBufferBinarySerializer serializer(m_ScriptInterface, pos); - serializer.ScriptVal("command", const_cast(&m_Data)); - return serializer.GetBuffer(); -} - -const u8* CGameSetupMessage::Deserialize(const u8* pStart, const u8* pEnd) -{ - // TODO: ought to handle serialization exceptions - const u8* pos = CNetMessage::Deserialize(pStart, pEnd); - std::istringstream stream(std::string(pos, pEnd)); - CStdDeserializer deserializer(m_ScriptInterface, stream); - deserializer.ScriptVal("command", const_cast(&m_Data)); - return pEnd; -} - -size_t CGameSetupMessage::GetSerializedLength() const -{ - CLengthBinarySerializer serializer(m_ScriptInterface); - serializer.ScriptVal("command", const_cast(&m_Data)); - return CNetMessage::GetSerializedLength() + serializer.GetLength(); -} - -CStr CGameSetupMessage::ToString() const -{ - std::string source = Script::ToString(ScriptRequest(m_ScriptInterface), const_cast(&m_Data)); - - std::stringstream stream; - stream << "CGameSetupMessage { m_Data: " << source << " }"; - return CStr(stream.str()); -} Index: source/network/NetMessages.h =================================================================== --- source/network/NetMessages.h +++ source/network/NetMessages.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -145,6 +145,10 @@ START_NMT_CLASS_(ClearAllReady, NMT_CLEAR_ALL_READY) END_NMT_CLASS() +START_NMT_CLASS_(GameSetup, NMT_GAME_SETUP) + NMT_FIELD(CStr, m_Data) +END_NMT_CLASS() + START_NMT_CLASS_(PlayerAssignment, NMT_PLAYER_ASSIGNMENT) NMT_START_ARRAY(m_Hosts) NMT_FIELD(CStr, m_GUID) @@ -240,6 +244,13 @@ NMT_FIELD(CStr, m_GUID) END_NMT_CLASS() +START_NMT_CLASS_(Simulation, NMT_SIMULATION_COMMAND) + NMT_FIELD_INT(m_Client, u32, 4) + NMT_FIELD_INT(m_Player, i32, 4) + NMT_FIELD_INT(m_Turn, u32, 4) + NMT_FIELD(CStr, m_Data) +END_NMT_CLASS() + END_NMTS() #else Index: source/network/NetServer.h =================================================================== --- source/network/NetServer.h +++ source/network/NetServer.h @@ -258,7 +258,7 @@ /** * Switch in game mode and notify all clients to start the game. */ - void StartGame(const CStr& initAttribs); + void StartGame(const CStr& initAttribs, const bool allowCheats); /** * Make a player name 'nicer' by limiting the length and removing forbidden characters etc. @@ -270,11 +270,6 @@ */ CStrW DeduplicatePlayerName(const CStrW& original); - /** - * Get the script context used for init attributes. - */ - const ScriptInterface& GetScriptInterface(); - /** * Set the turn length to a fixed value. * TODO: we should replace this with some adaptive lag-dependent computation. @@ -331,14 +326,6 @@ void SendHolePunchingMessage(const CStr& ip, u16 port); - /** - * Internal script context for (de)serializing script messages, - * and for storing init attributes. - * (TODO: we shouldn't bother deserializing (except for debug printing of messages), - * we should just forward messages blindly and efficiently.) - */ - ScriptInterface* m_ScriptInterface; - PlayerAssignmentMap m_PlayerAssignments; /** @@ -346,7 +333,8 @@ * NB: this is not guaranteed to be up-to-date until the server is LOADING or INGAME. * At that point, the settings are frozen and ought to be identical to the simulation Init Attributes. */ - JS::PersistentRootedValue m_InitAttributes; + std::string m_InitAttributes; + bool m_AllowCheats; /** * Whether this match requires lobby authentication. Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -40,6 +40,13 @@ #include "scriptinterface/JSON.h" #include "simulation2/Simulation2.h" #include "simulation2/system/TurnManager.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wrestrict" +#define BOOST_DISABLE_PRAGMA_MESSAGE +#include "third_party/jsonspirit/json_spirit_reader_template.h" +#undef BOOST_DISABLE_PRAGMA_MESSAGE +#pragma GCC diagnostic pop #if CONFIG2_MINIUPNPC #include @@ -51,6 +58,10 @@ #include #include +CNetServer* g_NetServer{nullptr}; + +namespace +{ /** * Number of peers to allocate for the enet host. * Limited by ENET_PROTOCOL_MAXIMUM_PEER_ID (4096). @@ -69,7 +80,7 @@ * Smaller numbers may hurt performance; larger numbers will * hurt latency responding to messages from game thread. */ -static const int HOST_SERVICE_TIMEOUT = 50; +constexpr int HOST_SERVICE_TIMEOUT{50}; /** * Once ping goes above turn length * command delay, @@ -79,7 +90,44 @@ */ constexpr u32 NETWORK_BAD_PING = DEFAULT_TURN_LENGTH * COMMAND_DELAY_MP / 2; -CNetServer* g_NetServer = NULL; +struct WrongInitAttributeFormatError : std::runtime_error +{ + using std::runtime_error::runtime_error; +}; + +bool ShouldAllowCheats(const CStr& initAttribs) +{ + json_spirit::Value attribs; + json_spirit::read_string_or_throw(initAttribs, attribs); + + const auto& attribsObj = attribs.get_obj(); + const auto settingsIter = std::find_if(attribsObj.begin(), attribsObj.end(), + [](const auto& property) + { + return property.name_ == "settings"; + }); + + if (settingsIter == attribsObj.end()) + throw WrongInitAttributeFormatError{ + "The initialization attributes don't contain a \"settings\" property."}; + if (settingsIter->value_.type() != json_spirit::Value_type::obj_type) + throw WrongInitAttributeFormatError{"The \"settings\" property isn't an object."}; + + const auto& settingsObj = settingsIter->value_.get_obj(); + const auto cheatsIter = std::find_if(attribsObj.begin(), attribsObj.end(), + [](const auto& property) + { + return property.name_ == "CheatsEnabled"; + }); + + if (cheatsIter == settingsObj.end()) + return false; + + if (cheatsIter->value_.type() != json_spirit::Value_type::bool_type) + throw WrongInitAttributeFormatError{"The \"CheatsEnabled\" property isn't a bool."}; + + return cheatsIter->value_.get_bool(); +} static CStr DebugName(CNetServerSession* session) { @@ -89,6 +137,7 @@ return "[unauthed host]"; return "[" + session->GetGUID().substr(0, 8) + "...]"; } +} // anonymous namespace /* * XXX: We use some non-threadsafe functions from the worker thread. @@ -98,7 +147,6 @@ CNetServerWorker::CNetServerWorker(bool useLobbyAuth) : m_LobbyAuth(useLobbyAuth), m_Shutdown(false), - m_ScriptInterface(NULL), m_NextHostID(1), m_Host(NULL), m_ControllerGUID(), m_Stats(NULL), m_LastConnectionCheck(0) { @@ -368,11 +416,6 @@ // The script context uses the profiler and therefore the thread must be registered before the context is created g_Profiler2.RegisterCurrentThread("Net server"); - // We create a new ScriptContext for this network thread, with a single ScriptInterface. - std::shared_ptr netServerContext = ScriptContext::CreateContext(); - m_ScriptInterface = new ScriptInterface("Engine", "Net server", netServerContext); - m_InitAttributes.init(m_ScriptInterface->GetGeneralJSContext(), JS::UndefinedValue()); - while (true) { if (!RunStep()) @@ -384,8 +427,6 @@ // Clear roots before deleting their context m_SavedCommands.clear(); - - SAFE_DELETE(m_ScriptInterface); } bool CNetServerWorker::RunStep() @@ -394,10 +435,6 @@ // (Do as little work as possible while the mutex is held open, // to avoid performance problems and deadlocks.) - m_ScriptInterface->GetContext().MaybeIncrementalGC(0.5f); - - ScriptRequest rq(m_ScriptInterface); - std::vector newStartGame; std::vector newGameAttributes; std::vector> newLobbyAuths; @@ -421,9 +458,7 @@ LOGERROR("NetServer: Init Attributes cannot be changed after the server starts loading."); else { - JS::RootedValue gameAttributesVal(rq.cx); - Script::ParseJSON(rq, newGameAttributes.back(), &gameAttributesVal); - m_InitAttributes = gameAttributesVal; + m_InitAttributes = std::move(newGameAttributes.back()); } } @@ -520,7 +555,8 @@ if (session) { // Create message from raw data - CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetScriptInterface()); + CNetMessage* msg = + CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength); if (msg) { LOGMESSAGE("Net server: Received message %s of size %lu from %s", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength(), DebugName(session).c_str()); @@ -845,11 +881,6 @@ Broadcast(&message, { NSS_PREGAME, NSS_JOIN_SYNCING, NSS_INGAME }); } -const ScriptInterface& CNetServerWorker::GetScriptInterface() -{ - return *m_ScriptInterface; -} - void CNetServerWorker::SetTurnLength(u32 msecs) { if (m_ServerTurnManager) @@ -1137,8 +1168,7 @@ // Send the init attributes alongside - these should be correct since the game should be // started. CJoinSyncStartMessage message; - message.m_InitAttributes = Script::StringifyJSON( - ScriptRequest{server.GetScriptInterface()}, &server.m_InitAttributes); + message.m_InitAttributes = server.m_InitAttributes; (*sessionIt)->SendMessage(&message); }); @@ -1154,21 +1184,22 @@ CSimulationMessage* message = (CSimulationMessage*)event->GetParamRef(); - // Ignore messages sent by one player on behalf of another player - // unless cheating is enabled - bool cheatsEnabled = false; - const ScriptInterface& scriptInterface = server.GetScriptInterface(); - ScriptRequest rq(scriptInterface); - JS::RootedValue settings(rq.cx); - Script::GetProperty(rq, server.m_InitAttributes, "settings", &settings); - if (Script::HasProperty(rq, settings, "CheatsEnabled")) - Script::GetProperty(rq, settings, "CheatsEnabled", cheatsEnabled); - PlayerAssignmentMap::iterator it = server.m_PlayerAssignments.find(session->GetGUID()); // When cheating is disabled, fail if the player the message claims to // represent does not exist or does not match the sender's player name - if (!cheatsEnabled && (it == server.m_PlayerAssignments.end() || it->second.m_PlayerID != message->m_Player)) + try + { + if (!ShouldAllowCheats(server.m_InitAttributes) && + (it == server.m_PlayerAssignments.end() || it->second.m_PlayerID != message->m_Player)) + { + return true; + } + } + catch (std::runtime_error& e) + { + LOGERROR("%s", e.what()); return true; + } // Send it back to all clients that have finished // the loading screen (and the synchronization when rejoining) @@ -1308,7 +1339,7 @@ return true; CGameStartMessage* message = (CGameStartMessage*)event->GetParamRef(); - server.StartGame(message->m_InitAttributes); + server.StartGame(message->m_InitAttributes, ShouldAllowCheats(message->m_InitAttributes)); return true; } @@ -1502,7 +1533,7 @@ return true; } -void CNetServerWorker::StartGame(const CStr& initAttribs) +void CNetServerWorker::StartGame(const CStr& initAttribs, const bool allowCheats) { for (std::pair& player : m_PlayerAssignments) if (player.second.m_Enabled && player.second.m_PlayerID != -1 && player.second.m_Status == 0) @@ -1532,7 +1563,8 @@ SendPlayerAssignments(); // Update init attributes. They should no longer change. - Script::ParseJSON(ScriptRequest(m_ScriptInterface), initAttribs, &m_InitAttributes); + m_InitAttributes = initAttribs; + m_AllowCheats = allowCheats; CGameStartMessage gameStart; gameStart.m_InitAttributes = initAttribs; Index: source/network/NetSession.cpp =================================================================== --- source/network/NetSession.cpp +++ source/network/NetSession.cpp @@ -183,7 +183,8 @@ } else if (event.type == ENET_EVENT_TYPE_RECEIVE) { - CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, m_Client.GetScriptInterface()); + CNetMessage* msg = + CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength); if (msg) { LOGMESSAGE("Net client: Received message %s of size %lu from server", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength()); Index: source/network/tests/test_Net.h =================================================================== --- source/network/tests/test_Net.h +++ source/network/tests/test_Net.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -185,7 +185,7 @@ &cmd, "type", "debug-print", "message", "[>>> client1 test sim command]\\n"); - client1Game.GetTurnManager()->PostCommand(cmd); + client1Game.GetTurnManager()->PostCommand(&cmd); } { @@ -195,7 +195,7 @@ &cmd, "type", "debug-print", "message", "[>>> client2 test sim command]\\n"); - client2Game.GetTurnManager()->PostCommand(cmd); + client2Game.GetTurnManager()->PostCommand(&cmd); } wait(clients, 100); @@ -269,7 +269,7 @@ "type", "debug-print", "message", "[>>> client1 test sim command 1]\\n"); - client1Game.GetTurnManager()->PostCommand(cmd); + client1Game.GetTurnManager()->PostCommand(&cmd); } wait(clients, 100); @@ -285,7 +285,7 @@ &cmd, "type", "debug-print", "message", "[>>> client1 test sim command 2]\\n"); - client1Game.GetTurnManager()->PostCommand(cmd); + client1Game.GetTurnManager()->PostCommand(&cmd); } debug_printf("==== Disconnecting client 2\n"); @@ -345,7 +345,7 @@ &cmd, "type", "debug-print", "message", "[>>> client1 test sim command 3]\\n"); - client1Game.GetTurnManager()->PostCommand(cmd); + client1Game.GetTurnManager()->PostCommand(&cmd); } @@ -363,7 +363,7 @@ "type", "debug-print", "message", "[>>> client1 test sim command 4]\\n"); - client1Game.GetTurnManager()->PostCommand(cmd); + client1Game.GetTurnManager()->PostCommand(&cmd); } for (size_t i = 0; i < 3; ++i) Index: source/network/tests/test_NetMessage.h =================================================================== --- source/network/tests/test_NetMessage.h +++ source/network/tests/test_NetMessage.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -19,8 +19,9 @@ #include "network/NetMessage.h" -#include "scriptinterface/ScriptInterface.h" +#include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" +#include "scriptinterface/ScriptInterface.h" class TestNetMessage : public CxxTest::TestSuite { @@ -34,8 +35,13 @@ Script::CreateArray(rq, &val); Script::SetPropertyInt(rq, val, 0, 4); - CSimulationMessage msg(script, 1, 2, 3, val); - TS_ASSERT_STR_EQUALS(msg.ToString(), "CSimulationMessage { m_Client: 1, m_Player: 2, m_Turn: 3, m_Data: [4] }"); + CSimulationMessage msg; + msg.m_Client = 1; + msg.m_Player = 2; + msg.m_Turn = 3; + msg.m_Data = Script::StringifyJSON(rq, &val); + TS_ASSERT_STR_EQUALS(msg.ToString(), + "CSimulationMessage { m_Client: 1, m_Player: 2, m_Turn: 3, m_Data: [\\n 4\\n] }"); size_t len = msg.GetSerializedLength(); u8* buf = new u8[len+1]; @@ -43,8 +49,9 @@ TS_ASSERT_EQUALS(msg.Serialize(buf) - (buf+len), 0); TS_ASSERT_EQUALS(buf[len], '!'); - CNetMessage* msg2 = CNetMessageFactory::CreateMessage(buf, len, script); - TS_ASSERT_STR_EQUALS(((CSimulationMessage*)msg2)->ToString(), "CSimulationMessage { m_Client: 1, m_Player: 2, m_Turn: 3, m_Data: [4] }"); + CNetMessage* msg2 = CNetMessageFactory::CreateMessage(buf, len); + TS_ASSERT_STR_EQUALS(((CSimulationMessage*)msg2)->ToString(), + "CSimulationMessage { m_Client: 1, m_Player: 2, m_Turn: 3, m_Data: [\\n 4\\n] }"); delete msg2; delete[] buf; Index: source/simulation2/components/CCmpCommandQueue.cpp =================================================================== --- source/simulation2/components/CCmpCommandQueue.cpp +++ source/simulation2/components/CCmpCommandQueue.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -97,7 +97,7 @@ // TODO: would be nicer to not use globals if (g_Game && g_Game->GetTurnManager()) - g_Game->GetTurnManager()->PostCommand(cmd); + g_Game->GetTurnManager()->PostCommand(&cmd); } void FlushTurn(const std::vector& commands) override Index: source/simulation2/system/LocalTurnManager.h =================================================================== --- source/simulation2/system/LocalTurnManager.h +++ source/simulation2/system/LocalTurnManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -30,7 +30,7 @@ void OnSimulationMessage(CSimulationMessage* msg) override; - void PostCommand(JS::HandleValue data) override; + void PostCommand(JS::MutableHandleValue data) override; void PostCommand(player_id_t playerid, JS::HandleValue data); protected: Index: source/simulation2/system/LocalTurnManager.cpp =================================================================== --- source/simulation2/system/LocalTurnManager.cpp +++ source/simulation2/system/LocalTurnManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -29,7 +29,7 @@ AddCommand(m_ClientId, playerid, data, m_CurrentTurn + m_CommandDelay); } -void CLocalTurnManager::PostCommand(JS::HandleValue data) +void CLocalTurnManager::PostCommand(JS::MutableHandleValue data) { AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + m_CommandDelay); } Index: source/simulation2/system/TurnManager.h =================================================================== --- source/simulation2/system/TurnManager.h +++ source/simulation2/system/TurnManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -126,7 +126,7 @@ /** * Called by simulation code, to add a new command to be distributed to all clients and executed soon. */ - virtual void PostCommand(JS::HandleValue data) = 0; + virtual void PostCommand(JS::MutableHandleValue data) = 0; /** * Called when all commands for a given turn have been received. Index: source/third_party/jsonspirit/README.txt =================================================================== --- source/third_party/jsonspirit/README.txt +++ source/third_party/jsonspirit/README.txt @@ -1,5 +1,6 @@ -This separate JSON library is used for Atlas to avoid the SpiderMonkey dependency. -SpiderMonkey is a fully featured JS engine and even though we already use it for the main engine, it's too heavy-weight to use it in Atlas. +This separate JSON library is used to avoid the SpiderMonkey dependency. +SpiderMonkey is a fully featured JS engine and even though we already use it for the main engine, it's too +heavy-weight to use it in some places. The SpiderMonkey API also changes frequently and we hope that the JSON parsing code needs less changes when we use this separate library. Get the library from here: