Cuberite
A lightweight, fast and extensible game server for Minecraft
ForgeHandshake.cpp
Go to the documentation of this file.
1 
2 // ForgeHandshake.cpp
3 
4 // Implements Forge protocol handshaking
5 
6 #include "Globals.h"
7 #include "ForgeHandshake.h"
8 #include "json/json.h"
9 #include "../Server.h"
10 #include "../ByteBuffer.h"
11 #include "../Bindings/PluginManager.h"
12 #include "../ClientHandle.h"
13 #include "../Root.h"
14 
15 
17 namespace Discriminator
18 {
19  static const Int8 ServerHello = 0;
20  static const Int8 ClientHello = 1;
21  static const Int8 ModList = 2;
22  static const Int8 RegistryData = 3;
23  // static const Int8 HandshakeReset = -2;
24  static const Int8 HandshakeAck = -1;
25 }
26 
28 namespace ClientPhase
29 {
30  static const Int8 WAITINGSERVERDATA = 2;
31  static const Int8 WAITINGSERVERCOMPLETE = 3;
32  static const Int8 PENDINGCOMPLETE = 4;
33  static const Int8 COMPLETE = 5;
34 }
35 
37 namespace ServerPhase
38 {
39  static const auto WAITINGCACK = std::byte(2);
40  static const auto COMPLETE = std::byte(3);
41 }
42 
43 
44 
45 
46 
48  IsForgeClient(false),
49  m_Errored(false)
50 {
51 }
52 
53 
54 
55 
56 
57 void cForgeHandshake::SetError(const AString & message)
58 {
59  LOGD("Forge handshake error: %s", message.c_str());
60  m_Errored = true;
61 }
62 
63 
64 
65 
66 
67 void cForgeHandshake::AugmentServerListPing(cClientHandle & a_Client, Json::Value & a_ResponseValue)
68 {
69  auto ProtocolVersion = a_Client.GetProtocolVersion();
70  auto & Mods = cRoot::Get()->GetServer()->GetRegisteredForgeMods(ProtocolVersion);
71 
72  if (Mods.empty())
73  {
74  return;
75  }
76 
77  LOGD("Received server ping from version: %d", ProtocolVersion);
78 
79  Json::Value Modinfo;
80  Modinfo["type"] = "FML";
81 
82  Json::Value ModList(Json::arrayValue);
83  for (auto & item: Mods)
84  {
85  Json::Value Mod;
86  Mod["modid"] = item.first;
87  Mod["version"] = item.second;
88  ModList.append(Mod);
89  }
90  Modinfo["modList"] = ModList;
91 
92  a_ResponseValue["modinfo"] = Modinfo;
93 }
94 
95 
96 
97 
98 
100 {
102 
103  static const std::array<std::string_view, 5> Channels{{ "FML|HS", "FML", "FML|MP", "FML", "FORGE" }};
104  ContiguousByteBuffer ChannelsString;
105  for (auto & Channel: Channels)
106  {
107  ChannelsString.append({ reinterpret_cast<const std::byte *>(Channel.data()), Channel.size() });
108  ChannelsString.push_back(std::byte(0));
109  }
110 
111  a_Client.SendPluginMessage("REGISTER", ChannelsString);
112  SendServerHello(a_Client);
113 }
114 
115 
116 
117 
118 
120 {
121  cByteBuffer Buf(6);
122  // Discriminator | Byte | Always 0 for ServerHello
124  // FML protocol Version | Byte | Determined from NetworkRegistery. Currently 2.
125  Buf.WriteBEInt8(2);
126  // Dimension TODO
127  Buf.WriteBEInt32(0);
128 
129  ContiguousByteBuffer Message;
130  Buf.ReadAll(Message);
131 
132  a_Client.SendPluginMessage("FML|HS", Message);
133 }
134 
135 
136 
137 
138 
140 {
141  AStringMap Mods;
142 
143  if (a_Data.size() < 4)
144  {
145  SetError(fmt::format(FMT_STRING("ParseModList invalid packet, missing length (size = {})"), a_Data.size()));
146  return Mods;
147  }
148 
149  cByteBuffer Buf(a_Data.size());
150  Buf.Write(a_Data.data(), a_Data.size());
151  UInt32 NumMods;
152  if (!Buf.ReadVarInt32(NumMods))
153  {
154  SetError("ParseModList failed to read mod count");
155  return Mods;
156  }
157 
158  for (UInt32 i = 0; i < NumMods; ++i)
159  {
160  AString Name, Version;
161  if (!Buf.ReadVarUTF8String(Name))
162  {
163  SetError(fmt::format(FMT_STRING("ParseModList failed to read mod name at i = {}"), i));
164  break;
165  }
166  if (!Buf.ReadVarUTF8String(Version))
167  {
168  SetError(fmt::format(FMT_STRING("ParseModList failed to read mod version at i = {}"), i));
169  break;
170  }
171  Mods.insert({Name, Version});
172  }
173 
174  return Mods;
175 }
176 
177 
178 
179 
180 
182 {
183  if (a_Data.size() == 2)
184  {
185  const auto FmlProtocolVersion = static_cast<Int8>(a_Data[1]);
186  LOGD("Received ClientHello with FML protocol version %d", FmlProtocolVersion);
187  if (FmlProtocolVersion != 2)
188  {
189  SetError(fmt::format(FMT_STRING("Unsupported FML client protocol version received in ClientHello: {}"), FmlProtocolVersion));
190  }
191  }
192  else
193  {
194  SetError(fmt::format(FMT_STRING("Received unexpected length of ClientHello: {}"), a_Data.size()));
195  }
196 }
197 
198 
199 
200 
201 
203 {
204  LOGD("Received ModList");
205 
206  auto ClientMods = ParseModList(a_Data.substr(1));
207  AString ClientModsString;
208  for (auto & item: ClientMods)
209  {
210  ClientModsString.append(fmt::format(FMT_STRING("{}@{}, "), item.first, item.second));
211  }
212 
213  LOG("Client connected with %zu mods: %s", ClientMods.size(), ClientModsString);
214 
215  a_Client.m_ForgeMods = ClientMods;
216 
217  // Let the plugins know about this event, they may refuse the player:
218  if (cRoot::Get()->GetPluginManager()->CallHookLoginForge(a_Client, ClientMods))
219  {
220  SetError("Modded client refused by plugin");
221  return;
222  }
223 
224  // Send server ModList
225 
226  // Send server-side Forge mods registered by plugins
227  const auto & ServerMods = a_Client.GetForgeMods();
228 
229  const auto ModCount = ServerMods.size();
230 
231  cByteBuffer Buf(256 * ModCount);
232 
234  Buf.WriteVarInt32(static_cast<UInt32>(ModCount));
235  for (const auto & item: ServerMods)
236  {
237  Buf.WriteVarUTF8String(item.first); // name
238  Buf.WriteVarUTF8String(item.second); // version
239  }
240 
241  ContiguousByteBuffer ServerModList;
242  Buf.ReadAll(ServerModList);
243 
244  a_Client.SendPluginMessage("FML|HS", ServerModList);
245 }
246 
247 
248 
249 
250 
252 {
253  if (a_Data.size() != 2)
254  {
255  SetError(fmt::format(FMT_STRING("Unexpected HandshakeAck packet length: {}"), a_Data.size()));
256  return;
257  }
258 
259  const auto Phase = static_cast<Int8>(a_Data[1]);
260  LOGD("Received client HandshakeAck with phase = %d", Phase);
261 
262  switch (Phase)
263  {
265  {
266  cByteBuffer Buf(1024);
268 
269  // TODO: send real registry data
270  bool HasMore = false;
271  AString RegistryName = "potions";
272  UInt32 NumIDs = 0;
273  UInt32 NumSubstitutions = 0;
274  UInt32 NumDummies = 0;
275 
276  Buf.WriteBool(HasMore);
277  Buf.WriteVarUTF8String(RegistryName);
278  Buf.WriteVarInt32(NumIDs);
279  Buf.WriteVarInt32(NumSubstitutions);
280  Buf.WriteVarInt32(NumDummies);
281 
283  Buf.ReadAll(RegistryData);
284  a_Client.SendPluginMessage("FML|HS", RegistryData);
285  break;
286  }
287 
289  {
290  LOGD("Client finished receiving registry data; acknowledging");
291 
293  Ack.push_back(std::byte(Discriminator::HandshakeAck));
294  Ack.push_back(ServerPhase::WAITINGCACK);
295  a_Client.SendPluginMessage("FML|HS", Ack);
296  break;
297  }
298 
300  {
301  LOGD("Client is pending completion; sending complete ack");
302 
304  Ack.push_back(std::byte(Discriminator::HandshakeAck));
305  Ack.push_back(ServerPhase::COMPLETE);
306  a_Client.SendPluginMessage("FML|HS", Ack);
307 
308  break;
309  }
310 
312  {
313  // Now finish logging in:
314  a_Client.FinishAuthenticate();
315  break;
316  }
317 
318  default:
319  {
320  SetError(fmt::format("Received unknown phase in Forge handshake acknowledgement: {}", Phase));
321  break;
322  }
323  }
324 }
325 
326 
327 
328 
329 
331 {
332  if (!IsForgeClient)
333  {
334  SetError(fmt::format(FMT_STRING("Received unexpected Forge data from non-Forge client ({} bytes)"), a_Data.size()));
335  return;
336  }
337  if (m_Errored)
338  {
339  LOGD("Received unexpected Forge data when in errored state, ignored");
340  return;
341  }
342 
343  if (a_Data.size() <= 1)
344  {
345  SetError(fmt::format(FMT_STRING("Received unexpectedly short Forge data ({} bytes)"), a_Data.size()));
346  return;
347  }
348 
349  const auto Discriminator = static_cast<Int8>(a_Data[0]);
350  switch (Discriminator)
351  {
352  case Discriminator::ClientHello: HandleClientHello(a_Client, a_Data); break;
353  case Discriminator::ModList: HandleModList(a_Client, a_Data); break;
354  case Discriminator::HandshakeAck: HandleHandshakeAck(a_Client, a_Data); break;
355 
356  default:
357  {
358  SetError(fmt::format(FMT_STRING("Unexpected Forge packet {0} (0x{0:x}) received"), Discriminator));
359  return;
360  }
361  }
362 }
unsigned int UInt32
Definition: Globals.h:157
std::basic_string_view< std::byte > ContiguousByteBufferView
Definition: Globals.h:376
signed char Int8
Definition: Globals.h:154
#define ASSERT(x)
Definition: Globals.h:276
std::basic_string< std::byte > ContiguousByteBuffer
Definition: Globals.h:375
void LOG(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:55
#define LOGD
Definition: LoggerSimple.h:83
std::string AString
Definition: StringUtils.h:11
std::map< AString, AString > AStringMap
A string dictionary, used for key-value pairs.
Definition: StringUtils.h:16
Discriminator byte values prefixing the FML|HS packets to determine their type.
static const Int8 ServerHello
static const Int8 ModList
static const Int8 ClientHello
static const Int8 HandshakeAck
static const Int8 RegistryData
Client handshake state phases.
static const Int8 PENDINGCOMPLETE
static const Int8 WAITINGSERVERCOMPLETE
static const Int8 WAITINGSERVERDATA
static const Int8 COMPLETE
Server handshake state phases.
static const auto COMPLETE
static const auto WAITINGCACK
An object that can store incoming bytes and lets its clients read the bytes sequentially The bytes ar...
Definition: ByteBuffer.h:32
void ReadAll(ContiguousByteBuffer &a_Data)
Reads all available data into a_Data.
Definition: ByteBuffer.cpp:977
bool WriteBEInt32(Int32 a_Value)
Definition: ByteBuffer.cpp:655
bool ReadVarInt32(UInt32 &a_Value)
Definition: ByteBuffer.cpp:414
bool WriteBool(bool a_Value)
Definition: ByteBuffer.cpp:733
bool WriteVarUTF8String(const AString &a_Value)
Definition: ByteBuffer.cpp:789
bool WriteBEInt8(Int8 a_Value)
Definition: ByteBuffer.cpp:591
bool WriteVarInt32(UInt32 a_Value)
Definition: ByteBuffer.cpp:745
bool ReadVarUTF8String(AString &a_Value)
Definition: ByteBuffer.cpp:458
bool Write(const void *a_Bytes, size_t a_Count)
Writes the bytes specified to the ringbuffer.
Definition: ByteBuffer.cpp:111
void FinishAuthenticate()
Finish logging the user in after authenticating.
const AStringMap & GetForgeMods(void) const
Returns the Forge mods installed on the client.
Definition: ClientHandle.h:282
void SendPluginMessage(const AString &a_Channel, std::string_view a_Message)
AStringMap m_ForgeMods
Forge mods and versions installed on this client.
Definition: ClientHandle.h:437
UInt32 GetProtocolVersion(void) const
Returns the protocol version number of the protocol that the client is talking.
Definition: ClientHandle.h:422
bool m_Errored
True if the Forge handshake is in an errored state.
void AugmentServerListPing(cClientHandle &a_Client, Json::Value &ResponseValue)
Add the registered Forge mods to the server ping list packet.
void HandleClientHello(cClientHandle &a_Client, ContiguousByteBufferView a_Data)
void SendServerHello(cClientHandle &a_Client)
Send the ServerHello packet in the Forge handshake.
AStringMap ParseModList(ContiguousByteBufferView a_Data)
Parse the client ModList packet of installed Forge mods and versions.
void SetError(const AString &message)
Set errored state to prevent further handshake message processing.
void BeginForgeHandshake(cClientHandle &a_Client)
Begin the Forge Modloader Handshake (FML|HS) sequence.
void HandleModList(cClientHandle &a_Client, ContiguousByteBufferView a_Data)
bool IsForgeClient
True if the client advertised itself as a Forge client.
void HandleHandshakeAck(cClientHandle &a_Client, ContiguousByteBufferView a_Data)
void DataReceived(cClientHandle &a_Client, ContiguousByteBufferView a_Data)
Process received data from the client advancing the Forge handshake.
cServer * GetServer(void)
Definition: Root.h:71
static cRoot * Get()
Definition: Root.h:52
const AStringMap & GetRegisteredForgeMods(const UInt32 a_Protocol)
Get the Forge mods (map of ModName -> ModVersionString) registered for a given protocol.
Definition: Server.cpp:282