From 6bc503151746ea05842009983c7de932fa80cd03 Mon Sep 17 00:00:00 2001 From: satoshinm Date: Sun, 27 Aug 2017 14:10:20 -0700 Subject: Implement Forge protocol handshake support (#3869) --- src/Protocol/CMakeLists.txt | 2 + src/Protocol/ForgeHandshake.cpp | 363 ++++++++++++++++++++++++++++++++++++++++ src/Protocol/ForgeHandshake.h | 60 +++++++ src/Protocol/Protocol_1_10.cpp | 2 + src/Protocol/Protocol_1_11.cpp | 2 + src/Protocol/Protocol_1_12.cpp | 2 + src/Protocol/Protocol_1_8.cpp | 1 + src/Protocol/Protocol_1_9.cpp | 49 ++++-- 8 files changed, 471 insertions(+), 10 deletions(-) create mode 100644 src/Protocol/ForgeHandshake.cpp create mode 100644 src/Protocol/ForgeHandshake.h (limited to 'src/Protocol') 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 + + 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()); @@ -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()); -- cgit v1.2.3