Implement Forge protocol handshake support (#3869)
This commit is contained in:
parent
447d929da1
commit
6bc5031517
@ -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 =
|
||||
|
@ -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.",
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
363
src/Protocol/ForgeHandshake.cpp
Normal file
363
src/Protocol/ForgeHandshake.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
60
src/Protocol/ForgeHandshake.h
Normal file
60
src/Protocol/ForgeHandshake.h
Normal file
@ -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);
|
||||
};
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
19
src/Server.h
19
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user