diff options
-rw-r--r-- | Server/Plugins/APIDump/APIDesc.lua | 76 | ||||
-rw-r--r-- | Server/Plugins/APIDump/Classes/Plugins.lua | 4 | ||||
-rw-r--r-- | Server/Plugins/APIDump/Classes/World.lua | 4 | ||||
-rw-r--r-- | src/Bindings/ManualBindings.cpp | 59 | ||||
-rw-r--r-- | src/Bindings/Plugin.h | 1 | ||||
-rw-r--r-- | src/Bindings/PluginLua.cpp | 10 | ||||
-rw-r--r-- | src/Bindings/PluginLua.h | 1 | ||||
-rw-r--r-- | src/Bindings/PluginManager.cpp | 18 | ||||
-rw-r--r-- | src/Bindings/PluginManager.h | 2 | ||||
-rw-r--r-- | src/ClientHandle.cpp | 25 | ||||
-rw-r--r-- | src/ClientHandle.h | 29 | ||||
-rw-r--r-- | src/Cuboid.cpp | 8 | ||||
-rw-r--r-- | src/Cuboid.h | 7 | ||||
-rw-r--r-- | src/Protocol/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/Protocol/ForgeHandshake.cpp | 363 | ||||
-rw-r--r-- | src/Protocol/ForgeHandshake.h | 60 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_10.cpp | 2 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_11.cpp | 2 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_12.cpp | 2 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_8.cpp | 1 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_9.cpp | 49 | ||||
-rw-r--r-- | src/Server.cpp | 50 | ||||
-rw-r--r-- | src/Server.h | 19 | ||||
-rw-r--r-- | src/UUID.cpp | 4 | ||||
-rw-r--r-- | src/World.cpp | 21 |
25 files changed, 782 insertions, 37 deletions
diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index d7dc83043..674729624 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -1338,6 +1338,16 @@ end }, Notes = "Returns the brand that the client has sent in their MC|Brand plugin message.", }, + GetForgeMods = + { + Returns = + { + { + Type = "table", + }, + }, + Notes = "Returns the Forge mods installed on the client.", + }, GetIPString = { Returns = @@ -1456,6 +1466,16 @@ end }, Notes = "Returns true if the client has registered to receive messages on the specified plugin channel.", }, + IsForgeClient = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if the client is modded with Forge.", + }, IsUUIDOnline = { IsStatic = true, @@ -8650,7 +8670,7 @@ a_Player:OpenWindow(Window); { { Name = "MobType", - Type = "Globals#eMonsterType", + Type = "eMonsterType", }, }, Returns = @@ -8710,7 +8730,7 @@ a_Player:OpenWindow(Window); { { Name = "MobType", - Type = "Globals#eMonsterType", + Type = "eMonsterType", }, }, Notes = "Returns the type of this mob ({{Globals#eMonsterType|mtXXX}} constant)", @@ -8801,7 +8821,7 @@ a_Player:OpenWindow(Window); { { Name = "MobType", - Type = "Globals#eMonsterType", + Type = "eMonsterType", }, }, Returns = @@ -8819,7 +8839,7 @@ a_Player:OpenWindow(Window); { { Name = "MobType", - Type = "Globals#eMonsterType", + Type = "eMonsterType", }, }, Returns = @@ -8910,7 +8930,7 @@ a_Player:OpenWindow(Window); { { Name = "MobType", - Type = "Globals#eMonsterType", + Type = "eMonsterType", }, }, Notes = "Returns the mob type ({{Globals#eMonsterType|mtXXX}} constant) parsed from the string type (\"creeper\"), or mtInvalidType if unrecognized.", @@ -9976,7 +9996,7 @@ a_Player:OpenWindow(Window); { { Name = "GameMode", - Type = "Globals#eGameMode", + Type = "eGameMode", }, }, Notes = "(OBSOLETE) Returns the current resolved game mode of the player. If the player is set to inherit the world's gamemode, returns that instead. See also GetGameMode() and IsGameModeXXX() functions. Note that this function is the same as GetGameMode(), use that function instead.", @@ -10076,7 +10096,7 @@ a_Player:OpenWindow(Window); { { Name = "GameMode", - Type = "Globals#eGameMode", + Type = "eGameMode", }, }, Notes = "Returns the player's gamemode. The player may have their gamemode unassigned, in which case they inherit the gamemode from the current {{cWorld|world}}.<br /> <b>NOTE:</b> Instead of comparing the value returned by this function to the gmXXX constants, use the IsGameModeXXX() functions. These functions handle the gamemode inheritance automatically.", @@ -10861,7 +10881,7 @@ a_Player:OpenWindow(Window); { { Name = "NewGameMode", - Type = "Globals#eGameMode", + Type = "eGameMode", }, }, Notes = "Sets the gamemode for the player. The new gamemode overrides the world's default gamemode, unless it is set to gmInherit.", @@ -11892,6 +11912,25 @@ end }, Notes = "Returns true if the specified player is queued to be transferred to a World.", }, + RegisterForgeMod = + { + Params = + { + { + Name = "ModName", + Type = "string", + }, + { + Name = "ModVersion", + Type = "string", + }, + { + Name = "ProtocolVersionNumber", + Type = "number", + }, + }, + Notes = "Add a Forge mod name/version to the server ping list.", + }, SetMaxPlayers = { Params = @@ -11913,6 +11952,21 @@ end }, Notes = "Returns true iff the server is set to authenticate players (\"online mode\").", }, + UnregisterForgeMod = + { + Params = + { + { + Name = "ModName", + Type = "string", + }, + { + Name = "ProtocolVersionNumber", + Type = "number", + }, + }, + Notes = "Remove a Forge mod name/version from the server ping list.", + }, }, }, cStringCompression = @@ -13831,7 +13885,7 @@ end { { Name = "BiomeType", - Type = "Globals#EMCSBiome", + Type = "EMCSBiome", }, }, Notes = "Converts a string representation to a {{Globals#BiomeTypes|BiomeType}} enumerated value. Returns biInvalidBiome if the input is not a recognized biome.", @@ -13849,7 +13903,7 @@ end { { Name = "DamageType", - Type = "Globals#eDamageType", + Type = "eDamageType", }, }, Notes = "Converts a string representation to a {{Globals#DamageType|DamageType}} enumerated value. Returns -1 if the inupt is not a recognized damage type.", @@ -13867,7 +13921,7 @@ end { { Name = "Dimension", - Type = "Globals#eDimension", + Type = "eDimension", }, }, Notes = "Converts a string representation to a {{Globals#eDimension|eDimension}} enumerated value. Returns dimNotSet if the input is not a recognized dimension.", diff --git a/Server/Plugins/APIDump/Classes/Plugins.lua b/Server/Plugins/APIDump/Classes/Plugins.lua index e22f4e3a0..6c9df7902 100644 --- a/Server/Plugins/APIDump/Classes/Plugins.lua +++ b/Server/Plugins/APIDump/Classes/Plugins.lua @@ -796,6 +796,10 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage); { Notes = "Called when a Login packet is sent to the client, before the client is queued for authentication.", }, + HOOK_LOGIN_FORGE = + { + Notes = "Called when a Forge client has sent its ModList to the server, during the login handshake.", + }, HOOK_PLAYER_ANIMATION = { Notes = "Called when a client send the Animation packet.", diff --git a/Server/Plugins/APIDump/Classes/World.lua b/Server/Plugins/APIDump/Classes/World.lua index d523f3881..63c2162e6 100644 --- a/Server/Plugins/APIDump/Classes/World.lua +++ b/Server/Plugins/APIDump/Classes/World.lua @@ -1820,7 +1820,7 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i { { Name = "ShrapnelLevel", - Type = "Globals#eShrapnelLevel", + Type = "eShrapnelLevel", }, }, Notes = "Returns the shrapnel level, representing the block types that are propelled outwards following an explosion. Based on this value and a random picker, blocks are selectively converted to physics entities (FallingSand) and flung outwards.", @@ -2830,7 +2830,7 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i { { Name = "ShrapnelLevel", - Type = "Globals#eShrapnelLevel", + Type = "eShrapnelLevel", }, }, Notes = "Sets the Shrapnel level of the world.", diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index c87e9ed20..ee9cb61e9 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -33,6 +33,7 @@ #include "../HTTP/UrlParser.h" #include "../Item.h" #include "../LineBlockTracer.h" +#include "../Server.h" #include "../Root.h" #include "../StringCompression.h" #include "../WebAdmin.h" @@ -2365,6 +2366,27 @@ static int tolua_cClientHandle_SendPluginMessage(lua_State * L) +static int tolua_cClientHandle_GetForgeMods(lua_State * L) +{ + cLuaState S(L); + if ( + !S.CheckParamSelf("cClientHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + cClientHandle * Client; + S.GetStackValue(1, Client); + + S.Push(Client->GetForgeMods()); + return 1; +} + + + + + static int tolua_cClientHandle_GetUUID(lua_State * tolua_S) { // Check the params: @@ -3399,6 +3421,37 @@ static int tolua_cRoot_GetFurnaceRecipe(lua_State * tolua_S) +static int tolua_cServer_RegisterForgeMod(lua_State * a_LuaState) +{ + cLuaState L(a_LuaState); + if ( + !L.CheckParamSelf("cServer") || + !L.CheckParamString(2, 3) || + !L.CheckParamNumber(4) || + !L.CheckParamEnd(5) + ) + { + return 0; + } + + cServer * Server; + AString Name, Version; + UInt32 Protocol; + L.GetStackValues(1, Server, Name, Version, Protocol); + + if (!Server->RegisterForgeMod(Name, Version, Protocol)) + { + tolua_error(L, "duplicate Forge mod name registration", nullptr); + return 0; + } + + return 0; +} + + + + + static int tolua_cScoreboard_GetTeamNames(lua_State * L) { cLuaState S(L); @@ -4007,6 +4060,8 @@ void cManualBindings::Bind(lua_State * tolua_S) tolua_beginmodule(tolua_S, "cClientHandle"); tolua_constant(tolua_S, "MAX_VIEW_DISTANCE", cClientHandle::MAX_VIEW_DISTANCE); tolua_constant(tolua_S, "MIN_VIEW_DISTANCE", cClientHandle::MIN_VIEW_DISTANCE); + + tolua_function(tolua_S, "GetForgeMods", tolua_cClientHandle_GetForgeMods); tolua_function(tolua_S, "SendPluginMessage", tolua_cClientHandle_SendPluginMessage); tolua_function(tolua_S, "GetUUID", tolua_cClientHandle_GetUUID); tolua_function(tolua_S, "GenerateOfflineUUID", tolua_cClientHandle_GenerateOfflineUUID); @@ -4164,6 +4219,10 @@ void cManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "GetTeamNames", tolua_cScoreboard_GetTeamNames); tolua_endmodule(tolua_S); + tolua_beginmodule(tolua_S, "cServer"); + tolua_function(tolua_S, "RegisterForgeMod", tolua_cServer_RegisterForgeMod); + tolua_endmodule(tolua_S); + tolua_beginmodule(tolua_S, "cStringCompression"); tolua_function(tolua_S, "CompressStringZLIB", tolua_CompressStringZLIB); tolua_function(tolua_S, "UncompressStringZLIB", tolua_UncompressStringZLIB); diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h index 22e8f15e2..fc0e2b4fc 100644 --- a/src/Bindings/Plugin.h +++ b/src/Bindings/Plugin.h @@ -69,6 +69,7 @@ public: virtual bool OnKilled (cEntity & a_Victim, TakeDamageInfo & a_TDI, AString & a_DeathMessage) = 0; virtual bool OnKilling (cEntity & a_Victim, cEntity * a_Killer, TakeDamageInfo & a_TDI) = 0; virtual bool OnLogin (cClientHandle & a_Client, UInt32 a_ProtocolVersion, const AString & a_Username) = 0; + virtual bool OnLoginForge (cClientHandle & a_Client, const AStringMap & a_Mods) = 0; virtual bool OnPlayerAnimation (cPlayer & a_Player, int a_Animation) = 0; virtual bool OnPlayerBreakingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; virtual bool OnPlayerBrokenBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index 5af336a95..9105f2555 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -550,6 +550,15 @@ bool cPluginLua::OnLogin(cClientHandle & a_Client, UInt32 a_ProtocolVersion, con +bool cPluginLua::OnLoginForge(cClientHandle & a_Client, const AStringMap & a_Mods) +{ + return CallSimpleHooks(cPluginManager::HOOK_LOGIN_FORGE, &a_Client, a_Mods); +} + + + + + bool cPluginLua::OnPlayerAnimation(cPlayer & a_Player, int a_Animation) { return CallSimpleHooks(cPluginManager::HOOK_PLAYER_ANIMATION, &a_Player, a_Animation); @@ -1059,6 +1068,7 @@ const char * cPluginLua::GetHookFnName(int a_HookType) case cPluginManager::HOOK_HANDSHAKE: return "OnHandshake"; case cPluginManager::HOOK_KILLING: return "OnKilling"; case cPluginManager::HOOK_LOGIN: return "OnLogin"; + case cPluginManager::HOOK_LOGIN_FORGE: return "OnLoginForge"; case cPluginManager::HOOK_PLAYER_BREAKING_BLOCK: return "OnPlayerBreakingBlock"; case cPluginManager::HOOK_PLAYER_BROKEN_BLOCK: return "OnPlayerBrokenBlock"; case cPluginManager::HOOK_PLAYER_EATING: return "OnPlayerEating"; diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index 4de5751e7..7904fe115 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -90,6 +90,7 @@ public: virtual bool OnKilled (cEntity & a_Victim, TakeDamageInfo & a_TDI, AString & a_DeathMessage) override; virtual bool OnKilling (cEntity & a_Victim, cEntity * a_Killer, TakeDamageInfo & a_TDI) override; virtual bool OnLogin (cClientHandle & a_Client, UInt32 a_ProtocolVersion, const AString & a_Username) override; + virtual bool OnLoginForge (cClientHandle & a_Client, const AStringMap & a_Mods) override; virtual bool OnPlayerAnimation (cPlayer & a_Player, int a_Animation) override; virtual bool OnPlayerBreakingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual bool OnPlayerBrokenBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 1d977fcde..7c4712f0f 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -789,6 +789,24 @@ bool cPluginManager::CallHookLogin(cClientHandle & a_Client, UInt32 a_ProtocolVe +bool cPluginManager::CallHookLoginForge(cClientHandle & a_Client, AStringMap & a_Mods) +{ + FIND_HOOK(HOOK_LOGIN_FORGE) + VERIFY_HOOK; + + for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr) + { + if ((*itr)->OnLoginForge(a_Client, a_Mods)) + { + return true; + } + } + return false; +} + + + + bool cPluginManager::CallHookPlayerAnimation(cPlayer & a_Player, int a_Animation) { diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index f3fc3551a..66f7d290a 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -102,6 +102,7 @@ public: HOOK_KILLED, HOOK_KILLING, HOOK_LOGIN, + HOOK_LOGIN_FORGE, HOOK_PLAYER_BREAKING_BLOCK, HOOK_PLAYER_BROKEN_BLOCK, HOOK_PLAYER_DESTROYED, @@ -248,6 +249,7 @@ public: bool CallHookKilled (cEntity & a_Victim, TakeDamageInfo & a_TDI, AString & a_DeathMessage); bool CallHookKilling (cEntity & a_Victim, cEntity * a_Killer, TakeDamageInfo & a_TDI); bool CallHookLogin (cClientHandle & a_Client, UInt32 a_ProtocolVersion, const AString & a_Username); + bool CallHookLoginForge (cClientHandle & a_Client, AStringMap & a_Mods); bool CallHookPlayerAnimation (cPlayer & a_Player, int a_Animation); bool CallHookPlayerBreakingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); bool CallHookPlayerBrokenBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index caa2d8fd8..dbd6d4b4e 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -64,6 +64,7 @@ float cClientHandle::FASTBREAK_PERCENTAGE; cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : m_LastSentDimension(dimNotSet), + m_ForgeHandshake(this), m_CurrentViewDistance(a_ViewDistance), m_RequestedViewDistance(a_ViewDistance), m_IPString(a_IPString), @@ -320,7 +321,6 @@ void cClientHandle::Authenticate(const AString & a_Name, const cUUID & a_UUID, c // Atomically increment player count (in server thread) cRoot::Get()->GetServer()->PlayerCreated(); - cWorld * World; { cCSLock lock(m_CSState); /* @@ -351,6 +351,25 @@ void cClientHandle::Authenticate(const AString & a_Name, const cUUID & a_UUID, c // Send login success (if the protocol supports it): m_Protocol->SendLoginSuccess(); + if (m_ForgeHandshake.m_IsForgeClient) + { + m_ForgeHandshake.BeginForgeHandshake(a_Name, a_UUID, a_Properties); + } + else + { + FinishAuthenticate(a_Name, a_UUID, a_Properties); + } + } +} + + + + + +void cClientHandle::FinishAuthenticate(const AString & a_Name, const cUUID & a_UUID, const Json::Value & a_Properties) +{ + cWorld * World; + { // Spawn player (only serversided, so data is loaded) m_PlayerPtr = cpp14::make_unique<cPlayer>(m_Self, GetUsername()); m_Player = m_PlayerPtr.get(); @@ -854,6 +873,10 @@ void cClientHandle::HandlePluginMessage(const AString & a_Channel, const AString { UnregisterPluginChannels(BreakApartPluginChannels(a_Message)); } + else if (a_Channel == "FML|HS") + { + m_ForgeHandshake.DataReceived(this, a_Message.c_str(), a_Message.size()); + } else if (!HasPluginChannel(a_Channel)) { // Ignore if client sent something but didn't register the channel first diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 09188f2ae..4a4898179 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -16,6 +16,7 @@ #include "json/json.h" #include "ChunkSender.h" #include "EffectID.h" +#include "Protocol/ForgeHandshake.h" #include "UUID.h" @@ -255,8 +256,26 @@ public: // tolua_export /** Returns the client brand received in the MC|Brand plugin message or set by a plugin. */ const AString & GetClientBrand(void) const { return m_ClientBrand; } + /** Returns the Forge mods installed on the client. */ + const AStringMap & GetForgeMods(void) const { return m_ForgeMods; } + + /** Returns true if the client is modded with Forge. */ + bool IsForgeClient(void) const { return m_ForgeHandshake.m_IsForgeClient; } + // tolua_end + /** Add the Forge mod list to the server ping response. */ + void ForgeAugmentServerListPing(Json::Value & a_Response) + { + m_ForgeHandshake.AugmentServerListPing(a_Response); + } + + /** Mark a client connection as using Forge. Set by the protocol. */ + void SetIsForgeClient() + { + m_ForgeHandshake.m_IsForgeClient = true; + } + /** Returns true if the client wants the chunk specified to be sent (in m_ChunksToSend) */ bool WantsSendChunk(int a_ChunkX, int a_ChunkZ); @@ -375,10 +394,17 @@ private: friend class cServer; // Needs access to SetSelf() + friend class cForgeHandshake; // Needs access to FinishAuthenticate() /** The type used for storing the names of registered plugin channels. */ typedef std::set<AString> cChannels; + /** Forge handshake state machine. */ + cForgeHandshake m_ForgeHandshake; + + /** Forge mods and versions installed on this client. */ + AStringMap m_ForgeMods; + /** The actual view distance used, the minimum of client's requested view distance and world's max view distance. */ int m_CurrentViewDistance; @@ -526,6 +552,9 @@ private: float m_BreakProgress; + /** Finish logging the user in after authenticating. */ + void FinishAuthenticate(const AString & a_Name, const cUUID & a_UUID, const Json::Value & a_Properties); + /** Returns true if the rate block interactions is within a reasonable limit (bot protection) */ bool CheckBlockInteractionsRate(void); diff --git a/src/Cuboid.cpp b/src/Cuboid.cpp index d87a6ef5e..c638eb636 100644 --- a/src/Cuboid.cpp +++ b/src/Cuboid.cpp @@ -10,8 +10,12 @@ //////////////////////////////////////////////////////////////////////////////// // cCuboid: - - +cCuboid & cCuboid::operator =(cCuboid a_Other) +{ + std::swap(p1, a_Other.p1); + std::swap(p2, a_Other.p2); + return *this; +} void cCuboid::Assign(int a_X1, int a_Y1, int a_Z1, int a_X2, int a_Y2, int a_Z2) diff --git a/src/Cuboid.h b/src/Cuboid.h index b39d3ad4a..b9d5f8cfa 100644 --- a/src/Cuboid.h +++ b/src/Cuboid.h @@ -13,9 +13,16 @@ public: Vector3i p1, p2; cCuboid(void) {} + cCuboid(const cCuboid & a_Cuboid) : p1(a_Cuboid.p1), p2(a_Cuboid.p2) {} cCuboid(const Vector3i & a_p1, const Vector3i & a_p2) : p1(a_p1), p2(a_p2) {} cCuboid(int a_X1, int a_Y1, int a_Z1) : p1(a_X1, a_Y1, a_Z1), p2(a_X1, a_Y1, a_Z1) {} + // tolua_end + + cCuboid & operator =(cCuboid a_Other); + + // tolua_begin + // DEPRECATED, use cCuboid(Vector3i, Vector3i) instead cCuboid(int a_X1, int a_Y1, int a_Z1, int a_X2, int a_Y2, int a_Z2) : p1(a_X1, a_Y1, a_Z1), p2(a_X2, a_Y2, a_Z2) { diff --git a/src/Protocol/CMakeLists.txt b/src/Protocol/CMakeLists.txt index f21c81f83..00ffeb255 100644 --- a/src/Protocol/CMakeLists.txt +++ b/src/Protocol/CMakeLists.txt @@ -5,6 +5,7 @@ include_directories ("${PROJECT_SOURCE_DIR}/../") SET (SRCS Authenticator.cpp ChunkDataSerializer.cpp + ForgeHandshake.cpp MojangAPI.cpp Packetizer.cpp Protocol_1_8.cpp @@ -18,6 +19,7 @@ SET (SRCS SET (HDRS Authenticator.h ChunkDataSerializer.h + ForgeHandshake.h MojangAPI.h Packetizer.h Protocol.h diff --git a/src/Protocol/ForgeHandshake.cpp b/src/Protocol/ForgeHandshake.cpp new file mode 100644 index 000000000..48b89baf4 --- /dev/null +++ b/src/Protocol/ForgeHandshake.cpp @@ -0,0 +1,363 @@ + +// ForgeHandshake.cpp + +// Implements Forge protocol handshaking + +#include "Globals.h" +#include "ForgeHandshake.h" +#include "json/json.h" +#include "../Server.h" +#include "../ByteBuffer.h" +#include "../Bindings/PluginManager.h" +#include "../ClientHandle.h" +#include "../Root.h" + + +/** Discriminator byte values prefixing the FML|HS packets to determine their type. */ +namespace Discriminator +{ + static const Int8 ServerHello = 0; + static const Int8 ClientHello = 1; + static const Int8 ModList = 2; + static const Int8 RegistryData = 3; + // static const Int8 HandshakeReset = -2; + static const Int8 HandshakeAck = -1; +} + +/** Client handshake state phases. */ +namespace ClientPhase +{ + static const Int8 WAITINGSERVERDATA = 2; + static const Int8 WAITINGSERVERCOMPLETE = 3; + static const Int8 PENDINGCOMPLETE = 4; + static const Int8 COMPLETE = 5; +} + +/** Server handshake state phases. */ +namespace ServerPhase +{ + static const Int8 WAITINGCACK = 2; + static const Int8 COMPLETE = 3; +} + + + + + +cForgeHandshake::cForgeHandshake(cClientHandle *a_Client) : + m_IsForgeClient(false), + m_Errored(false), + m_Client(a_Client) +{ +} + + + + + +void cForgeHandshake::SetError(const AString & message) +{ + LOGD("Forge handshake error: %s", message.c_str()); + m_Errored = true; +} + + + + + +void cForgeHandshake::AugmentServerListPing(Json::Value & a_ResponseValue) +{ + auto ProtocolVersion = m_Client->GetProtocolVersion(); + auto & Mods = cRoot::Get()->GetServer()->GetRegisteredForgeMods(ProtocolVersion); + + if (Mods.empty()) + { + return; + } + + LOGD("Received server ping from version: %d", ProtocolVersion); + + Json::Value Modinfo; + Modinfo["type"] = "FML"; + + Json::Value ModList(Json::arrayValue); + for (auto & item: Mods) + { + Json::Value Mod; + Mod["modid"] = item.first; + Mod["version"] = item.second; + ModList.append(Mod); + } + Modinfo["modList"] = ModList; + + a_ResponseValue["modinfo"] = Modinfo; +} + + + + + +void cForgeHandshake::BeginForgeHandshake(const AString & a_Name, const cUUID & a_UUID, const Json::Value & a_Properties) +{ + ASSERT(m_IsForgeClient); + + m_Name = a_Name; + m_UUID = a_UUID; + m_Properties = a_Properties; + + static const std::array<AString, 5> Channels{{ "FML|HS", "FML", "FML|MP", "FML", "FORGE" }}; + AString ChannelsString; + for (auto & Channel: Channels) + { + ChannelsString.append(Channel); + ChannelsString.push_back('\0'); + } + + m_Client->SendPluginMessage("REGISTER", ChannelsString); + SendServerHello(); +} + + + + + +void cForgeHandshake::SendServerHello() +{ + AString Message; + cByteBuffer Buf(6); + // Discriminator | Byte | Always 0 for ServerHello + Buf.WriteBEInt8(Discriminator::ServerHello); + // FML protocol Version | Byte | Determined from NetworkRegistery. Currently 2. + Buf.WriteBEInt8(2); + // Dimension TODO + Buf.WriteBEInt32(0); + Buf.ReadAll(Message); + + m_Client->SendPluginMessage("FML|HS", Message); +} + + + + + +AStringMap cForgeHandshake::ParseModList(const char * a_Data, size_t a_Size) +{ + AStringMap Mods; + + if (a_Size < 4) + { + SetError(Printf("ParseModList invalid packet, missing length (size = " SIZE_T_FMT ")", a_Size)); + return Mods; + } + + cByteBuffer Buf(a_Size); + Buf.Write(a_Data, a_Size); + UInt32 NumMods; + if (!Buf.ReadVarInt32(NumMods)) + { + SetError("ParseModList failed to read mod count"); + return Mods; + } + + for (UInt32 i = 0; i < NumMods; ++i) + { + AString Name, Version; + if (!Buf.ReadVarUTF8String(Name)) + { + SetError(Printf("ParseModList failed to read mod name at i = %d", i)); + break; + } + if (!Buf.ReadVarUTF8String(Version)) + { + SetError(Printf("ParseModList failed to read mod version at i = %d", i)); + break; + } + Mods.insert({Name, Version}); + } + + return Mods; +} + + + + +void cForgeHandshake::HandleClientHello(cClientHandle * a_Client, const char * a_Data, size_t a_Size) +{ + if (a_Size == 2) + { + int FmlProtocolVersion = a_Data[1]; + LOGD("Received ClientHello with FML protocol version %d", FmlProtocolVersion); + if (FmlProtocolVersion != 2) + { + SetError(Printf("Unsupported FML client protocol version received in ClientHello: %d", FmlProtocolVersion)); + } + } + else + { + SetError(Printf("Received unexpected length of ClientHello: " SIZE_T_FMT, a_Size)); + } +} + + + + +void cForgeHandshake::HandleModList(cClientHandle * a_Client, const char * a_Data, size_t a_Size) +{ + LOGD("Received ModList"); + + auto ClientMods = ParseModList(a_Data + 1, a_Size - 1); + AString ClientModsString; + for (auto & item: ClientMods) + { + AppendPrintf(ClientModsString, "%s@%s, ", item.first.c_str(), item.second.c_str()); + } + + LOG("Client connected with " SIZE_T_FMT " mods: %s", ClientMods.size(), ClientModsString.c_str()); + + m_Client->m_ForgeMods = ClientMods; + + // Let the plugins know about this event, they may refuse the player: + if (cRoot::Get()->GetPluginManager()->CallHookLoginForge(*a_Client, ClientMods)) + { + SetError("Modded client refused by plugin"); + return; + } + + // Send server ModList + + // Send server-side Forge mods registered by plugins + const auto & ServerMods = m_Client->GetForgeMods(); + + const auto ModCount = ServerMods.size(); + + cByteBuffer Buf(256 * ModCount); + + Buf.WriteBEInt8(Discriminator::ModList); + Buf.WriteVarInt32(static_cast<UInt32>(ModCount)); + for (const auto & item: ServerMods) + { + Buf.WriteVarUTF8String(item.first); // name + Buf.WriteVarUTF8String(item.second); // version + } + AString ServerModList; + Buf.ReadAll(ServerModList); + + m_Client->SendPluginMessage("FML|HS", ServerModList); +} + + + + +void cForgeHandshake::HandleHandshakeAck(cClientHandle * a_Client, const char * a_Data, size_t a_Size) +{ + if (a_Size != 2) + { + SetError(Printf("Unexpected HandshakeAck packet length: " SIZE_T_FMT "", a_Size)); + return; + } + + auto Phase = a_Data[1]; + LOGD("Received client HandshakeAck with phase = %d", Phase); + + switch (Phase) + { + case ClientPhase::WAITINGSERVERDATA: + { + cByteBuffer Buf(1024); + Buf.WriteBEInt8(Discriminator::RegistryData); + + // TODO: send real registry data + bool HasMore = false; + AString RegistryName = "potions"; + UInt32 NumIDs = 0; + UInt32 NumSubstitutions = 0; + UInt32 NumDummies = 0; + + Buf.WriteBool(HasMore); + Buf.WriteVarUTF8String(RegistryName); + Buf.WriteVarInt32(NumIDs); + Buf.WriteVarInt32(NumSubstitutions); + Buf.WriteVarInt32(NumDummies); + + AString RegistryData; + Buf.ReadAll(RegistryData); + m_Client->SendPluginMessage("FML|HS", RegistryData); + break; + } + + case ClientPhase::WAITINGSERVERCOMPLETE: + { + LOGD("Client finished receiving registry data; acknowledging"); + + AString Ack; + Ack.push_back(Discriminator::HandshakeAck); + Ack.push_back(ServerPhase::WAITINGCACK); + m_Client->SendPluginMessage("FML|HS", Ack); + break; + } + + case ClientPhase::PENDINGCOMPLETE: + { + LOGD("Client is pending completion; sending complete ack"); + + AString Ack; + Ack.push_back(Discriminator::HandshakeAck); + Ack.push_back(ServerPhase::COMPLETE); + m_Client->SendPluginMessage("FML|HS", Ack); + + break; + } + + case ClientPhase::COMPLETE: + { + // Now finish logging in + m_Client->FinishAuthenticate(m_Name, m_UUID, m_Properties); + break; + } + + default: + { + SetError(Printf("Received unknown phase in Forge handshake acknowledgement: %d", Phase)); + break; + } + } +} + + + + + +void cForgeHandshake::DataReceived(cClientHandle * a_Client, const char * a_Data, size_t a_Size) +{ + if (!m_IsForgeClient) + { + SetError(Printf("Received unexpected Forge data from non-Forge client (" SIZE_T_FMT " bytes)", a_Size)); + return; + } + if (m_Errored) + { + LOGD("Received unexpected Forge data when in errored state, ignored"); + return; + } + + if (a_Size <= 1) + { + SetError(Printf("Received unexpectedly short Forge data (" SIZE_T_FMT " bytes)", a_Size)); + return; + } + + auto Discriminator = a_Data[0]; + + switch (Discriminator) + { + case Discriminator::ClientHello: HandleClientHello(a_Client, a_Data, a_Size); break; + case Discriminator::ModList: HandleModList(a_Client, a_Data, a_Size); break; + case Discriminator::HandshakeAck: HandleHandshakeAck(a_Client, a_Data, a_Size); break; + + default: + { + SetError(Printf("Unexpected Forge packet %d received", Discriminator)); + return; + } + } +} diff --git a/src/Protocol/ForgeHandshake.h b/src/Protocol/ForgeHandshake.h new file mode 100644 index 000000000..f7be9e958 --- /dev/null +++ b/src/Protocol/ForgeHandshake.h @@ -0,0 +1,60 @@ + +// ForgeHandshake.h + +// Implements Forge protocol handshaking + +#pragma once + +#include <stddef.h> +#include "UUID.h" +#include "json/json.h" + +// fwd: +class cClientHandle; + + + + + +class cForgeHandshake +{ +public: + /** True if the client advertised itself as a Forge client. */ + bool m_IsForgeClient; + + cForgeHandshake(cClientHandle * client); + + /** Add the registered Forge mods to the server ping list packet. */ + void AugmentServerListPing(Json::Value & ResponseValue); + + /** Begin the Forge Modloader Handshake (FML|HS) sequence. */ + void BeginForgeHandshake(const AString & a_Name, const cUUID & a_UUID, const Json::Value & a_Properties); + + /** Send the ServerHello packet in the Forge handshake. */ + void SendServerHello(); + + /** Process received data from the client advancing the Forge handshake. */ + void DataReceived(cClientHandle * a_Client, const char * a_Data, size_t a_Size); + +private: + /** True if the Forge handshake is in an errored state. */ + bool m_Errored; + + /** The client handle undergoing this Forge handshake. */ + cClientHandle * m_Client; + + /** Values saved from BeginForgeHandshake() for continuing the normal handshake after Forge completes. */ + AString m_Name; + cUUID m_UUID; + Json::Value m_Properties; + + void HandleClientHello(cClientHandle * a_Client, const char * a_Data, size_t a_Size); + void HandleModList(cClientHandle * a_Client, const char * a_Data, size_t a_Size); + void HandleHandshakeAck(cClientHandle * a_Client, const char * a_Data, size_t a_Size); + + /** Set errored state to prevent further handshake message processing. */ + void SetError(const AString & message); + + /** Parse the client ModList packet of installed Forge mods and versions. */ + AStringMap ParseModList(const char * a_Data, size_t a_Size); +}; diff --git a/src/Protocol/Protocol_1_10.cpp b/src/Protocol/Protocol_1_10.cpp index 936f5d8a7..63b80dace 100644 --- a/src/Protocol/Protocol_1_10.cpp +++ b/src/Protocol/Protocol_1_10.cpp @@ -15,6 +15,7 @@ Implements the 1.10 protocol classes: #include "../Root.h" #include "../Server.h" +#include "../ClientHandle.h" #include "../WorldStorage/FastNBT.h" @@ -346,6 +347,7 @@ void cProtocol_1_10_0::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); diff --git a/src/Protocol/Protocol_1_11.cpp b/src/Protocol/Protocol_1_11.cpp index c562503bd..b9b6e9ac3 100644 --- a/src/Protocol/Protocol_1_11.cpp +++ b/src/Protocol/Protocol_1_11.cpp @@ -586,6 +586,7 @@ void cProtocol_1_11_0::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); @@ -1209,6 +1210,7 @@ void cProtocol_1_11_1::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); diff --git a/src/Protocol/Protocol_1_12.cpp b/src/Protocol/Protocol_1_12.cpp index 43ab682eb..a8e38a4e0 100644 --- a/src/Protocol/Protocol_1_12.cpp +++ b/src/Protocol/Protocol_1_12.cpp @@ -399,6 +399,7 @@ void cProtocol_1_12::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); @@ -1667,6 +1668,7 @@ void cProtocol_1_12_1::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); diff --git a/src/Protocol/Protocol_1_8.cpp b/src/Protocol/Protocol_1_8.cpp index 2824c4a13..a0afe8c15 100644 --- a/src/Protocol/Protocol_1_8.cpp +++ b/src/Protocol/Protocol_1_8.cpp @@ -2169,6 +2169,7 @@ void cProtocol_1_8_0::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); diff --git a/src/Protocol/Protocol_1_9.cpp b/src/Protocol/Protocol_1_9.cpp index b9310bea5..7eeba06b4 100644 --- a/src/Protocol/Protocol_1_9.cpp +++ b/src/Protocol/Protocol_1_9.cpp @@ -124,21 +124,46 @@ cProtocol_1_9_0::cProtocol_1_9_0(cClientHandle * a_Client, const AString & a_Ser m_IsEncrypted(false) { - // BungeeCord handling: - // If BC is setup with ip_forward == true, it sends additional data in the login packet's ServerAddress field: - // hostname\00ip-address\00uuid\00profile-properties-as-json AStringVector Params; - if (cRoot::Get()->GetServer()->ShouldAllowBungeeCord() && SplitZeroTerminatedStrings(a_ServerAddress, Params) && (Params.size() == 4)) + SplitZeroTerminatedStrings(a_ServerAddress, Params); + + if (Params.size() >= 2) { - LOGD("Player at %s connected via BungeeCord", Params[1].c_str()); m_ServerAddress = Params[0]; - m_Client->SetIPString(Params[1]); - cUUID UUID; - UUID.FromString(Params[2]); - m_Client->SetUUID(UUID); + if (Params[1] == "FML") + { + LOGD("Forge client connected!"); + m_Client->SetIsForgeClient(); + } + else if (Params.size() == 4) + { + if (cRoot::Get()->GetServer()->ShouldAllowBungeeCord()) + { + // BungeeCord handling: + // If BC is setup with ip_forward == true, it sends additional data in the login packet's ServerAddress field: + // hostname\00ip-address\00uuid\00profile-properties-as-json + + LOGD("Player at %s connected via BungeeCord", Params[1].c_str()); + + m_Client->SetIPString(Params[1]); + + cUUID UUID; + UUID.FromString(Params[2]); + m_Client->SetUUID(UUID); - m_Client->SetProperties(Params[3]); + m_Client->SetProperties(Params[3]); + } + else + { + LOG("BungeeCord is disabled, but client sent additional data, set AllowBungeeCord=1 if you want to allow it"); + } + } + else + { + LOG("Unknown additional data sent in server address (BungeeCord/FML?): " SIZE_T_FMT " parameters", Params.size()); + // TODO: support FML + BungeeCord? (what parameters does it send in that case?) https://github.com/SpigotMC/BungeeCord/issues/899 + } } // Create the comm log file, if so requested: @@ -2194,6 +2219,7 @@ void cProtocol_1_9_0::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); @@ -4205,6 +4231,7 @@ void cProtocol_1_9_1::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); @@ -4262,6 +4289,7 @@ void cProtocol_1_9_2::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); @@ -4319,6 +4347,7 @@ void cProtocol_1_9_4::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) ResponseValue["version"] = Version; ResponseValue["players"] = Players; ResponseValue["description"] = Description; + m_Client->ForgeAugmentServerListPing(ResponseValue); if (!Favicon.empty()) { ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); diff --git a/src/Server.cpp b/src/Server.cpp index 70d594f2d..6ddb14ae5 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -235,6 +235,56 @@ bool cServer::InitServer(cSettingsRepositoryInterface & a_Settings, bool a_Shoul +bool cServer::RegisterForgeMod(const AString & a_ModName, const AString & a_ModVersion, UInt32 a_ProtocolVersionNumber) +{ + auto & Mods = RegisteredForgeMods(a_ProtocolVersionNumber); + + return Mods.insert({a_ModName, a_ModVersion}).second; +} + + + + + +void cServer::UnregisterForgeMod(const AString & a_ModName, UInt32 a_ProtocolVersionNumber) +{ + auto & Mods = RegisteredForgeMods(a_ProtocolVersionNumber); + + auto it = Mods.find(a_ModName); + if (it != Mods.end()) + { + Mods.erase(it); + } +} + + + + +AStringMap & cServer::RegisteredForgeMods(const UInt32 a_Protocol) +{ + auto it = m_ForgeModsByVersion.find(a_Protocol); + + if (it == m_ForgeModsByVersion.end()) + { + AStringMap mods; + m_ForgeModsByVersion.insert({a_Protocol, mods}); + return m_ForgeModsByVersion.find(a_Protocol)->second; + } + + return it->second; +} + + + + +const AStringMap & cServer::GetRegisteredForgeMods(const UInt32 a_Protocol) +{ + return RegisteredForgeMods(a_Protocol); +} + + + + bool cServer::IsPlayerInQueue(AString a_Username) { cCSLock Lock(m_CSClients); diff --git a/src/Server.h b/src/Server.h index 633f6de70..ffdee64d9 100644 --- a/src/Server.h +++ b/src/Server.h @@ -71,6 +71,16 @@ public: size_t GetNumPlayers(void) const { return m_PlayerCount; } void SetMaxPlayers(size_t a_MaxPlayers) { m_MaxPlayers = a_MaxPlayers; } + // tolua_end + + /** Add a Forge mod to the server ping list. */ + bool RegisterForgeMod(const AString & a_ModName, const AString & a_ModVersion, UInt32 a_ProtocolVersionNumber); + + // tolua_begin + + /** Remove a Forge mod to the server ping list. */ + void UnregisterForgeMod(const AString & a_ModName, UInt32 a_ProtocolVersionNumber); + /** Check if the player is queued to be transferred to a World. Returns true is Player is found in queue. */ bool IsPlayerInQueue(AString a_Username); @@ -145,6 +155,9 @@ public: from the settings. */ bool ShouldAllowMultiWorldTabCompletion(void) const { return m_ShouldAllowMultiWorldTabCompletion; } + /** Get the Forge mods (map of ModName -> ModVersionString) registered for a given protocol. */ + const AStringMap & GetRegisteredForgeMods(const UInt32 a_Protocol); + private: friend class cRoot; // so cRoot can create and destroy cServer @@ -202,6 +215,9 @@ private: size_t m_MaxPlayers; bool m_bIsHardcore; + /** Map of protocol version to Forge mods (map of ModName -> ModVersionString) */ + std::map<UInt32, AStringMap> m_ForgeModsByVersion; + /** True - allow same username to login more than once False - only once */ bool m_bAllowMultiLogin; @@ -241,6 +257,9 @@ private: cServer(void); + /** Get the Forge mods registered for a given protocol, for modification */ + AStringMap & RegisteredForgeMods(const UInt32 a_Protocol); + /** Loads, or generates, if missing, RSA keys for protocol encryption */ void PrepareKeys(void); diff --git a/src/UUID.cpp b/src/UUID.cpp index e150b3603..e2713157d 100644 --- a/src/UUID.cpp +++ b/src/UUID.cpp @@ -68,11 +68,11 @@ static Byte FromHexDigit(char a_Hex) } if (('a' <= a_Hex) && (a_Hex <= 'f')) { - return static_cast<Byte>(a_Hex - 'a'); + return static_cast<Byte>(10 + (a_Hex - 'a')); } if (('A' <= a_Hex) && (a_Hex <= 'F')) { - return static_cast<Byte>(a_Hex - 'A'); + return static_cast<Byte>(10 + (a_Hex - 'A')); } return 0xff; } diff --git a/src/World.cpp b/src/World.cpp index 04ca1709e..acec3049e 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -995,18 +995,21 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La } // Add entities waiting in the queue to be added: + cEntityList EntitiesToAdd; { + // Don't access chunkmap while holding lock cCSLock Lock(m_CSEntitiesToAdd); - for (auto & Entity : m_EntitiesToAdd) - { - Entity->SetWorld(this); - auto EntityPtr = Entity.get(); - m_ChunkMap->AddEntity(std::move(Entity)); - ASSERT(!EntityPtr->IsTicking()); - EntityPtr->SetIsTicking(true); - } - m_EntitiesToAdd.clear(); + std::swap(EntitiesToAdd, m_EntitiesToAdd); + } + for (auto & Entity : EntitiesToAdd) + { + Entity->SetWorld(this); + auto EntityPtr = Entity.get(); + m_ChunkMap->AddEntity(std::move(Entity)); + ASSERT(!EntityPtr->IsTicking()); + EntityPtr->SetIsTicking(true); } + EntitiesToAdd.clear(); // Add players waiting in the queue to be added: AddQueuedPlayers(); |