From 157f1c6688db8abe72c93a292f43d5a04040e0a4 Mon Sep 17 00:00:00 2001 From: Howaner Date: Thu, 4 Sep 2014 00:29:36 +0200 Subject: Started implementing of the 1.8 protocol. --- src/Protocol/CMakeLists.txt | 2 + src/Protocol/Protocol17x.cpp | 6 +- src/Protocol/Protocol17x.h | 14 +- src/Protocol/Protocol18x.cpp | 385 ++++++++++++++++++++++++++++++++++++ src/Protocol/Protocol18x.h | 76 +++++++ src/Protocol/ProtocolRecognizer.cpp | 25 ++- src/Protocol/ProtocolRecognizer.h | 1 + 7 files changed, 502 insertions(+), 7 deletions(-) create mode 100644 src/Protocol/Protocol18x.cpp create mode 100644 src/Protocol/Protocol18x.h diff --git a/src/Protocol/CMakeLists.txt b/src/Protocol/CMakeLists.txt index 1ba66ff1f..5426c58fa 100644 --- a/src/Protocol/CMakeLists.txt +++ b/src/Protocol/CMakeLists.txt @@ -14,6 +14,7 @@ SET (SRCS Protocol15x.cpp Protocol16x.cpp Protocol17x.cpp + Protocol18x.cpp ProtocolRecognizer.cpp) SET (HDRS @@ -27,6 +28,7 @@ SET (HDRS Protocol15x.h Protocol16x.h Protocol17x.h + Protocol18x.h ProtocolRecognizer.h) if(NOT MSVC) diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 1091b877f..00b115c8f 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -1,10 +1,12 @@ + // Protocol17x.cpp /* Implements the 1.7.x protocol classes: - cProtocol172 - release 1.7.2 protocol (#4) -(others may be added later in the future for the 1.7 release series) + - cProtocol176 + - release 1.7.6 protocol (#5) */ #include "Globals.h" @@ -1710,7 +1712,7 @@ bool cProtocol172::HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) void cProtocol172::HandlePacketStatusPing(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Timestamp); - + cPacketizer Pkt(*this, 0x01); // Ping packet Pkt.WriteInt64(Timestamp); } diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h index ccfa19eb6..ec948e8a1 100644 --- a/src/Protocol/Protocol17x.h +++ b/src/Protocol/Protocol17x.h @@ -5,7 +5,8 @@ Declares the 1.7.x protocol classes: - cProtocol172 - release 1.7.2 protocol (#4) -(others may be added later in the future for the 1.7 release series) + - cProtocol176 + - release 1.7.6 protocol (#5) */ @@ -198,6 +199,11 @@ protected: { m_Out.WriteVarUTF8String(a_Value); } + + void WritePosition(const Vector3i a_Position) + { + WriteInt64(((Int64)a_Position.x & 0x3FFFFFF) << 38 | ((Int64)a_Position.y & 0xFFF) << 26 | ((Int64)a_Position.z & 0x3FFFFFF)); + } void WriteBuf(const char * a_Data, size_t a_Size) { @@ -258,12 +264,12 @@ protected: bool HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType); // Packet handlers while in the Status state (m_State == 1): - void HandlePacketStatusPing (cByteBuffer & a_ByteBuffer); + void HandlePacketStatusPing(cByteBuffer & a_ByteBuffer); virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer); // Packet handlers while in the Login state (m_State == 2): - void HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer); - void HandlePacketLoginStart (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer); + virtual void HandlePacketLoginStart(cByteBuffer & a_ByteBuffer); // Packet handlers while in the Game state (m_State == 3): void HandlePacketAnimation (cByteBuffer & a_ByteBuffer); diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp new file mode 100644 index 000000000..0a455c70e --- /dev/null +++ b/src/Protocol/Protocol18x.cpp @@ -0,0 +1,385 @@ + +// Protocol18x.cpp + +/* +Implements the 1.8.x protocol classes: + - cProtocol180 + - release 1.8.0 protocol (#47) +(others may be added later in the future for the 1.8 release series) +*/ + +#include "Globals.h" +#include "Bindings/PluginManager.h" +#include "json/json.h" +#include "Protocol18x.h" + +#include "../ClientHandle.h" +#include "../CompositeChat.h" +#include "../Root.h" +#include "../Server.h" +#include "../World.h" + +#include "../Entities/Player.h" + +class cProtocol176; + + + + + +const int MAX_ENC_LEN = 512; // Maximum size of the encrypted message; should be 128, but who knows... + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol180: + +cProtocol180::cProtocol180(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol180::SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x23); // Block Change packet + Pkt.WritePosition(Vector3i(a_BlockX, a_BlockY, a_BlockZ)); + + UInt32 Block = ((UInt32)a_BlockType << 4) | ((UInt32)a_BlockMeta & 15); + Pkt.WriteVarInt(Block); +} + + + + + +void cProtocol180::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x22); // Multi Block Change packet + Pkt.WriteInt(a_ChunkX); + Pkt.WriteInt(a_ChunkZ); + Pkt.WriteVarInt((UInt32)a_Changes.size()); + for (sSetBlockVector::const_iterator itr = a_Changes.begin(), end = a_Changes.end(); itr != end; ++itr) + { + short Coords = itr->y | (itr->z << 8) | (itr->x << 12); + Pkt.WriteShort(Coords); + + UInt32 Block = ((UInt32)itr->BlockType << 4) | ((UInt32)itr->BlockMeta & 15); + Pkt.WriteVarInt(Block); + } // for itr - a_Changes[] +} + + + + + +void cProtocol180::SendChat(const AString & a_Message) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x02); // Chat Message packet + Pkt.WriteString(Printf("{\"text\":\"%s\"}", EscapeString(a_Message).c_str())); + Pkt.WriteChar(0); +} + + + + + +void cProtocol180::SendChat(const cCompositeChat & a_Message) +{ + ASSERT(m_State == 3); // In game mode? + + // Compose the complete Json string to send: + Json::Value msg; + cWorld * World = m_Client->GetPlayer()->GetWorld(); + msg["text"] = cClientHandle::FormatMessageType((World == NULL) ? false : World->ShouldUseChatPrefixes(), a_Message.GetMessageType(), a_Message.GetAdditionalMessageTypeData()); // The client crashes without this field being present + const cCompositeChat::cParts & Parts = a_Message.GetParts(); + for (cCompositeChat::cParts::const_iterator itr = Parts.begin(), end = Parts.end(); itr != end; ++itr) + { + Json::Value Part; + switch ((*itr)->m_PartType) + { + case cCompositeChat::ptText: + { + Part["text"] = (*itr)->m_Text; + AddChatPartStyle(Part, (*itr)->m_Style); + break; + } + + case cCompositeChat::ptClientTranslated: + { + const cCompositeChat::cClientTranslatedPart & p = (const cCompositeChat::cClientTranslatedPart &)**itr; + Part["translate"] = p.m_Text; + Json::Value With; + for (AStringVector::const_iterator itrW = p.m_Parameters.begin(), endW = p.m_Parameters.end(); itrW != endW; ++itr) + { + With.append(*itrW); + } + if (!p.m_Parameters.empty()) + { + Part["with"] = With; + } + AddChatPartStyle(Part, p.m_Style); + break; + } + + case cCompositeChat::ptUrl: + { + const cCompositeChat::cUrlPart & p = (const cCompositeChat::cUrlPart &)**itr; + Part["text"] = p.m_Text; + Json::Value Url; + Url["action"] = "open_url"; + Url["value"] = p.m_Url; + Part["clickEvent"] = Url; + AddChatPartStyle(Part, p.m_Style); + break; + } + + case cCompositeChat::ptSuggestCommand: + case cCompositeChat::ptRunCommand: + { + const cCompositeChat::cCommandPart & p = (const cCompositeChat::cCommandPart &)**itr; + Part["text"] = p.m_Text; + Json::Value Cmd; + Cmd["action"] = (p.m_PartType == cCompositeChat::ptRunCommand) ? "run_command" : "suggest_command"; + Cmd["value"] = p.m_Command; + Part["clickEvent"] = Cmd; + AddChatPartStyle(Part, p.m_Style); + break; + } + + case cCompositeChat::ptShowAchievement: + { + const cCompositeChat::cShowAchievementPart & p = (const cCompositeChat::cShowAchievementPart &)**itr; + Part["translate"] = "chat.type.achievement"; + + Json::Value Ach; + Ach["action"] = "show_achievement"; + Ach["value"] = p.m_Text; + + Json::Value AchColourAndName; + AchColourAndName["color"] = "green"; + AchColourAndName["translate"] = p.m_Text; + AchColourAndName["hoverEvent"] = Ach; + + Json::Value Extra; + Extra.append(AchColourAndName); + + Json::Value Name; + Name["text"] = p.m_PlayerName; + + Json::Value With; + With.append(Name); + With.append(Extra); + + Part["with"] = With; + AddChatPartStyle(Part, p.m_Style); + break; + } + } + msg["extra"].append(Part); + } // for itr - Parts[] + + // Send the message to the client: + cPacketizer Pkt(*this, 0x02); + Pkt.WriteString(msg.toStyledString()); + Pkt.WriteChar(0); +} + + + + + +void cProtocol180::SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x04); // Entity Equipment packet + Pkt.WriteVarInt((UInt32)a_Entity.GetUniqueID()); + Pkt.WriteShort(a_SlotNum); + Pkt.WriteItem(a_Item); +} + + + + + +void cProtocol180::SendLogin(const cPlayer & a_Player, const cWorld & a_World) +{ + // Send the Join Game packet: + { + cServer * Server = cRoot::Get()->GetServer(); + cPacketizer Pkt(*this, 0x01); // Join Game packet + Pkt.WriteInt(a_Player.GetUniqueID()); + Pkt.WriteByte((Byte)a_Player.GetEffectiveGameMode() | (Server->IsHardcore() ? 0x08 : 0)); // Hardcore flag bit 4 + Pkt.WriteChar((char)a_World.GetDimension()); + Pkt.WriteByte(2); // TODO: Difficulty (set to Normal) + Pkt.WriteByte(std::min(Server->GetMaxPlayers(), 60)); + Pkt.WriteString("default"); // Level type - wtf? + Pkt.WriteBool(false); // Reduced Debug Info - wtf? + } + m_LastSentDimension = a_World.GetDimension(); + + // Send the spawn position: + { + cPacketizer Pkt(*this, 0x05); // Spawn Position packet + Vector3i Position(a_World.GetSpawnX(), a_World.GetSpawnY(), a_World.GetSpawnZ()); + Pkt.WritePosition(Position); + } + + // Send player abilities: + SendPlayerAbilities(); +} + + + + + +void cProtocol180::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) +{ + cServer * Server = cRoot::Get()->GetServer(); + AString ServerDescription = Server->GetDescription(); + int NumPlayers = Server->GetNumPlayers(); + int MaxPlayers = Server->GetMaxPlayers(); + AString Favicon = Server->GetFaviconData(); + cRoot::Get()->GetPluginManager()->CallHookServerPing(*m_Client, ServerDescription, NumPlayers, MaxPlayers, Favicon); + + // Version: + Json::Value Version; + Version["name"] = "1.8"; + Version["protocol"] = 47; + + // Players: + Json::Value Players; + Players["online"] = NumPlayers; + Players["max"] = MaxPlayers; + // TODO: Add "sample" + + // Description: + Json::Value Description; + Description["text"] = ServerDescription.c_str(); + + // Create the response: + Json::Value ResponseValue; + ResponseValue["version"] = Version; + ResponseValue["players"] = Players; + ResponseValue["description"] = Description; + if (!Favicon.empty()) + { + ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); + } + + Json::StyledWriter Writer; + AString Response = Writer.write(ResponseValue); + + cPacketizer Pkt(*this, 0x00); // Response packet + Pkt.WriteString(Response); +} + + + + + +void cProtocol180::HandlePacketLoginStart(cByteBuffer & a_ByteBuffer) +{ + AString Username; + if (!a_ByteBuffer.ReadVarUTF8String(Username)) + { + m_Client->Kick("Bad username"); + return; + } + + if (!m_Client->HandleHandshake(Username)) + { + // The client is not welcome here, they have been sent a Kick packet already + return; + } + + cServer * Server = cRoot::Get()->GetServer(); + // If auth is required, then send the encryption request: + if (Server->ShouldAuthenticate()) + { + cPacketizer Pkt(*this, 0x01); + Pkt.WriteString(Server->GetServerID()); + const AString & PubKeyDer = Server->GetPublicKeyDER(); + Pkt.WriteVarInt((short)PubKeyDer.size()); + Pkt.WriteBuf(PubKeyDer.data(), PubKeyDer.size()); + Pkt.WriteVarInt(4); + Pkt.WriteInt((int)(intptr_t)this); // Using 'this' as the cryptographic nonce, so that we don't have to generate one each time :) + m_Client->SetUsername(Username); + return; + } + + m_Client->HandleLogin(4, Username); +} + + + + + +void cProtocol180::HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer) +{ + UInt32 EncKeyLength, EncNonceLength; + a_ByteBuffer.ReadVarInt(EncKeyLength); + AString EncKey; + if (!a_ByteBuffer.ReadString(EncKey, EncKeyLength)) + { + return; + } + a_ByteBuffer.ReadVarInt(EncNonceLength); + AString EncNonce; + if (!a_ByteBuffer.ReadString(EncNonce, EncNonceLength)) + { + return; + } + if ((EncKeyLength > MAX_ENC_LEN) || (EncNonceLength > MAX_ENC_LEN)) + { + LOGD("Too long encryption"); + m_Client->Kick("Hacked client"); + return; + } + + // Decrypt EncNonce using privkey + cRsaPrivateKey & rsaDecryptor = cRoot::Get()->GetServer()->GetPrivateKey(); + Int32 DecryptedNonce[MAX_ENC_LEN / sizeof(Int32)]; + int res = rsaDecryptor.Decrypt((const Byte *)EncNonce.data(), EncNonce.size(), (Byte *)DecryptedNonce, sizeof(DecryptedNonce)); + if (res != 4) + { + LOGD("Bad nonce length: got %d, exp %d", res, 4); + m_Client->Kick("Hacked client"); + return; + } + if (ntohl(DecryptedNonce[0]) != (unsigned)(uintptr_t)this) + { + LOGD("Bad nonce value"); + m_Client->Kick("Hacked client"); + return; + } + + // Decrypt the symmetric encryption key using privkey: + Byte DecryptedKey[MAX_ENC_LEN]; + res = rsaDecryptor.Decrypt((const Byte *)EncKey.data(), EncKey.size(), DecryptedKey, sizeof(DecryptedKey)); + if (res != 16) + { + LOGD("Bad key length"); + m_Client->Kick("Hacked client"); + return; + } + + StartEncryption(DecryptedKey); + m_Client->HandleLogin(4, m_Client->GetUsername()); +} + + + + diff --git a/src/Protocol/Protocol18x.h b/src/Protocol/Protocol18x.h new file mode 100644 index 000000000..11a376b17 --- /dev/null +++ b/src/Protocol/Protocol18x.h @@ -0,0 +1,76 @@ + +// Protocol18x.h + +/* +Declares the 1.8.x protocol classes: + - cProtocol180 + - release 1.8 protocol (#47) +(others may be added later in the future for the 1.8 release series) +*/ + + + + + +#pragma once + +#include "Protocol.h" +#include "Protocol17x.h" +#include "../ByteBuffer.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4127) + #pragma warning(disable:4244) + #pragma warning(disable:4231) + #pragma warning(disable:4189) + #pragma warning(disable:4702) +#endif + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + + + + + +// fwd: +namespace Json +{ + class Value; +} + + + + + +class cProtocol180 : + public cProtocol176 +{ + typedef cProtocol176 super; + +public: + + cProtocol180(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + virtual void SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; + virtual void SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) override; + virtual void SendChat(const AString & a_Message) override; + virtual void SendChat(const cCompositeChat & a_Message) override; + virtual void SendPlayerListItem(const cPlayer & a_Player, bool a_IsOnline) override {} + virtual void SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) override; + virtual void SendLogin(const cPlayer & a_Player, const cWorld & a_World) override; + +protected: + + virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; + + // Packet handlers while in the Login state (m_State == 2): + virtual void HandlePacketLoginStart(cByteBuffer & a_ByteBuffer) override; + virtual void HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer) override; +} ; + + + + diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index 8b395230a..642e96880 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -13,6 +13,7 @@ #include "Protocol15x.h" #include "Protocol16x.h" #include "Protocol17x.h" +#include "Protocol18x.h" #include "../ClientHandle.h" #include "../Root.h" #include "../Server.h" @@ -51,7 +52,7 @@ AString cProtocolRecognizer::GetVersionTextFromInt(int a_ProtocolVersion) { case PROTO_VERSION_1_2_5: return "1.2.5"; case PROTO_VERSION_1_3_2: return "1.3.2"; - case PROTO_VERSION_1_4_2: return "1.4.2"; + // case PROTO_VERSION_1_4_2: return "1.4.2"; case PROTO_VERSION_1_4_4: return "1.4.4"; case PROTO_VERSION_1_4_6: return "1.4.6"; case PROTO_VERSION_1_5_0: return "1.5"; @@ -62,6 +63,7 @@ AString cProtocolRecognizer::GetVersionTextFromInt(int a_ProtocolVersion) case PROTO_VERSION_1_6_4: return "1.6.4"; case PROTO_VERSION_1_7_2: return "1.7.2"; case PROTO_VERSION_1_7_6: return "1.7.6"; + case PROTO_VERSION_1_8_0: return "1.8"; } ASSERT(!"Unknown protocol version"); return Printf("Unknown protocol (%d)", a_ProtocolVersion); @@ -1016,6 +1018,27 @@ bool cProtocolRecognizer::TryRecognizeLengthedProtocol(UInt32 a_PacketLengthRema m_Protocol = new cProtocol176(m_Client, ServerAddress, (UInt16)ServerPort, NextState); return true; } + case PROTO_VERSION_1_8_0: + { + AString ServerAddress; + short ServerPort; + UInt32 NextState; + if (!m_Buffer.ReadVarUTF8String(ServerAddress)) + { + break; + } + if (!m_Buffer.ReadBEShort(ServerPort)) + { + break; + } + if (!m_Buffer.ReadVarInt(NextState)) + { + break; + } + m_Buffer.CommitRead(); + m_Protocol = new cProtocol180(m_Client, ServerAddress, (UInt16)ServerPort, NextState); + return true; + } } LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, version %u)", m_Client->GetIPString().c_str(), ProtocolVersion diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index a05aeda70..2e8976a9f 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -51,6 +51,7 @@ public: // These will be kept "under" the next / latest, because the next and latest are only needed for previous protocols PROTO_VERSION_1_7_2 = 4, PROTO_VERSION_1_7_6 = 5, + PROTO_VERSION_1_8_0 = 47, } ; cProtocolRecognizer(cClientHandle * a_Client); -- cgit v1.2.3