diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index d7dc83043..573837333 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, @@ -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 = 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/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(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 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/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 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(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 +#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 67b76872a..7f86d4bdc 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 0e7abfd7a..c77af1029 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 7fc6cf5f1..c6e007984 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 - m_Client->SetProperties(Params[3]); + 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]); + } + 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()); @@ -4204,6 +4230,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()); @@ -4261,6 +4288,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()); @@ -4318,6 +4346,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 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);