From a4f327118b06ced1cd4510b7d20d34da83aa78a3 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 14 May 2016 12:12:42 -0700 Subject: 1.9 / 1.9.2 / 1.9.3 / 1.9.4 protocol support (#3135) * Semistable update to 15w31a I'm going through snapshots in a sequential order since it should make things easier, and since protocol version history is written. * Update to 15w34b protocol Also, fix an issue with the Entity Equipment packet from the past version. Clients are able to connect and do stuff! * Partially update to 15w35e Chunk data doesn't work, but the client joins. I'm waiting to do chunk data because chunk data has an incomplete format until 15w36d. * Add '/blk' debug command This command lets one see what block they are looking at, and makes figuring out what's supposed to be where in a highly broken chunk possible. * Fix CRLF normalization in CheckBasicStyle.lua Normally, this doesn't cause an issue, but when running from cygwin, it detects the CR as whitespace and creates thousands of violations for every single line. Lua, when run on windows, will normalize automatically, but when run via cygwin, it won't. The bug was simply that gsub was returning a replaced version, but not changing the parameter, so the replaced version was ignored. * Update to 15w40b This includes chunk serialization. Fully functional chunk serialization for 1.9. I'm not completely happy with the chunk serialization as-is (correct use of palettes would be great), but cuberite also doesn't skip sending empty chunks so this performance optimization should probably come later. The creation of a full buffer is suboptimal, but it's the easiest way to implement this code. * Write long-by-long rather than creating a buffer This is a bit faster and should be equivalent. However, the code still doesn't look too good. * Update to 15w41a protocol This includes the new set passengers packet, which works off of the ridden entity, not the rider. That means, among other things, that information about the previously ridden vehicle is needed when detaching. So a new method with that info was added. * Update to 15w45a * 15w51b protocol * Update to 1.9.0 protocol Closes #3067. There are still a few things that need to be worked out (picking up items, effects, particles, and most importantly inventory), but in general this should work. I'll make a few more changes tomorrow to get the rest of the protocol set up, along with 1.9.1/1.9.2 (which did make a few changes). Chunks, however, _are_ working, along with most other parts of the game (placing/breaking blocks). * Fix item pickup packet not working That was a silly mistake, but at least it was an easy one. * 1.9.2 protocol support * Fix version info found in server list ping Thus, the client reports that it can connect rather than saying that the server is out of date. This required creating separate classes for 1.9.1 and 1.9.2, unfortunately. * Fix build errors generated by clang These didn't happen in MSVC. * Add protocol19x.cpp and protocol19x.h to CMakeLists * Ignore warnings in protocol19x that are ignored in protocol18x * Document BLOCK_FACE and DIG_STATUS constants * Fix BLOCK_FACE links and add separate section for DIG_STATUS * Fix bat animation and object spawning The causes of both of these are explained in #3135, but the gist is that both were typos. * Implement Use Item packet This means that buckets, bows, fishing rods, and several other similar items now work when not looking at a block. * Handle DIG_STATUS_SWAP_ITEM_IN_HAND * Add support for spawn eggs and potions The items are transformed from the 1.9 version to the 1.8 version when reading and transformed back when sending. * Remove spammy potion debug logging * Fix wolf collar color metadata The wrong type was being used, causing several clientside issues (including the screen going black). * Fix 1.9 chunk sending in the nether The nether and the end don't send skylight. * Fix clang build errors * Fix water bottles becoming mundane potions This happened because the can become splash potion bit got set incorrectly. Water bottles and mundane potions are only differentiated by the fact that water bottles have a metadata of 0, so setting that bit made it a mundane potion. Also add missing break statements to the read item NBT switch, which would otherwise break items with custom names and also cause incorrect "Unimplemented NBT data when parsing!" logging. * Copy Protocol18x as Protocol19x Aditionally, method and class names have been swapped to clean up other diffs. This commit is only added to make the following diffs more readable; it doesn't make any other changes (beyond class names). * Make thrown potions use the correct appearence This was caused by potions now using metadata. * Add missing api doc for cSplashPotionEntity::GetItem * Fix compile error in SplashPotionEntity.cpp * Fix fix of cSplashPotionEntity API doc * Temporarilly disable fall damage particles These were causing issues in 1.9 due to the changed effect ID. * Properly send a kick packet when connecting with an invalid version This means that the client no longer waits on the server screen with no indication whatsoever. However, right now the server list ping isn't implemented for unknown versions, so it'll only load "Old" on the ping. I also added a GetVarIntSize method to cByteBuffer. This helps clean up part of the code here (and I think it could clean up other parts), but it may make sense for it to be moved elsewhere (or declared in a different way). * Handle server list pings from unrecognized versions This isn't the cleanest way of writing it (it feels odd to use ProtocolRecognizer to send packets, and the addition of m_InPingForUnrecognizedVersion feels like the wrong technique), but it works and I can't think of a better way (apart from creating a full separate protocol class to handle only the ping... which would be worse). * Use cPacketizer for the disconnect packet This also should fix clang build errors. * Add 1.9.3 / 1.9.4 support * Fix incorrect indentation in APIDesc --- CONTRIBUTORS | 1 + Server/Plugins/APIDump/APIDesc.lua | 30 + Server/Plugins/APIDump/Classes/Projectiles.lua | 1 + Server/Plugins/Debuggers/Debuggers.lua | 28 + Server/Plugins/Debuggers/Info.lua | 6 + src/ByteBuffer.cpp | 18 + src/ByteBuffer.h | 3 + src/CheckBasicStyle.lua | 2 +- src/Chunk.cpp | 14 +- src/Chunk.h | 3 +- src/ChunkMap.cpp | 18 +- src/ChunkMap.h | 3 +- src/ChunkSender.cpp | 2 +- src/ClientHandle.cpp | 17 +- src/ClientHandle.h | 3 +- src/Defines.h | 13 +- src/Entities/Entity.cpp | 12 +- src/Entities/Pawn.cpp | 5 +- src/Entities/SplashPotionEntity.cpp | 1 + src/Entities/SplashPotionEntity.h | 2 + src/Mobs/Creeper.h | 1 + src/Protocol/CMakeLists.txt | 3 + src/Protocol/ChunkDataSerializer.cpp | 298 +- src/Protocol/ChunkDataSerializer.h | 8 +- src/Protocol/Protocol.h | 3 +- src/Protocol/Protocol17x.cpp | 18 +- src/Protocol/Protocol17x.h | 3 +- src/Protocol/Protocol18x.cpp | 18 +- src/Protocol/Protocol18x.h | 3 +- src/Protocol/Protocol19x.cpp | 4306 ++++++++++++++++++++++++ src/Protocol/Protocol19x.h | 360 ++ src/Protocol/ProtocolRecognizer.cpp | 240 +- src/Protocol/ProtocolRecognizer.h | 16 +- src/World.cpp | 13 +- src/World.h | 3 +- 35 files changed, 5374 insertions(+), 101 deletions(-) create mode 100644 src/Protocol/Protocol19x.cpp create mode 100644 src/Protocol/Protocol19x.h diff --git a/CONTRIBUTORS b/CONTRIBUTORS index a0ff1a980..5cea4df24 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -30,6 +30,7 @@ mtilden nesco NiLSPACE (formerly STR_Warrior) p-mcgowan +pokechu22 rs2k SamJBarney Schwertspize diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 800396b4e..43e805a50 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -2965,6 +2965,28 @@ end }, Constants = { + BLOCK_FACE_XM = { Notes = "Interacting with the X- face of the block" }, + BLOCK_FACE_XP = { Notes = "Interacting with the X+ face of the block" }, + BLOCK_FACE_YM = { Notes = "Interacting with the Y- face of the block" }, + BLOCK_FACE_YP = { Notes = "Interacting with the Y+ face of the block" }, + BLOCK_FACE_ZM = { Notes = "Interacting with the Z- face of the block" }, + BLOCK_FACE_ZP = { Notes = "Interacting with the Z+ face of the block" }, + BLOCK_FACE_NONE = { Notes = "Interacting with no block face - swinging the item in the air" }, + BLOCK_FACE_EAST = { Notes = "(DEPRECATED!) Please use BLOCK_FACE_XM instead. Interacting with the eastern face of the block." }, + BLOCK_FACE_WEST = { Notes = "(DEPRECATED!) Please use BLOCK_FACE_XP instead. Interacting with the western face of the block." }, + BLOCK_FACE_BOTTOM = { Notes = "(DEPRECATED!) Please use BLOCK_FACE_YM instead. Interacting with the bottom face of the block." }, + BLOCK_FACE_TOP = { Notes = "(DEPRECATED!) Please use BLOCK_FACE_YP instead. Interacting with the top face of the block." }, + BLOCK_FACE_NORTH = { Notes = "(DEPRECATED!) Please use BLOCK_FACE_ZM instead. Interacting with the northern face of the block." }, + BLOCK_FACE_SOUTH = { Notes = "(DEPRECATED!) Please use BLOCK_FACE_ZP instead. Interacting with the southern face of the block." }, + BLOCK_FACE_MAX = { Notes = "Used for range checking - highest legal value for an {{Globals#BlockFaces|eBlockFace}}" }, + BLOCK_FACE_MIN = { Notes = "Used for range checking - lowest legal value for an {{Globals#BlockFaces|eBlockFace}}" }, + DIG_STATUS_STARTED = { Notes = "The player has started digging" }, + DIG_STATUS_CANCELLED = { Notes = "The player has let go of the mine block key before finishing mining the block" }, + DIG_STATUS_FINISHED = { Notes = "The player thinks that it has finished mining a block" }, + DIG_STATUS_DROP_HELD = { Notes = "The player has dropped a single item using the Drop Item key (default: Q)" }, + DIG_STATUS_DROP_STACK = { Notes = "The player has dropped a full stack of items using the Drop Item key (default: Q) while holding down a specific modifier key (in windows, control)" }, + DIG_STATUS_SHOOT_EAT = { Notes = "The player has finished shooting a bow or finished eating" }, + DIG_STATUS_SWAP_ITEM_IN_HAND = { Notes = "The player has swapped their held item with the item in their offhand slot (1.9)" }, esBed = { Notes = "A bed explosion. The SourceData param is the {{Vector3i|position}} of the bed." }, esEnderCrystal = { Notes = "An ender crystal entity explosion. The SourceData param is the {{cEntity|ender crystal entity}} object." }, esGhastFireball = { Notes = "A ghast fireball explosion. The SourceData param is the {{cGhastFireballEntity|ghast fireball entity}} object." }, @@ -3048,6 +3070,14 @@ end {{TakeDamageInfo}} structure, as well as in {{cEntity}}'s damage-related API functions. ]], }, + DigStatuses = + { + Include = "^DIG_STATUS_.*", + TextBefore = [[ + These constants are used to describe digging statuses, but in reality cover several more cases. + They are used with {{OnPlayerLeftClick|HOOK_PLAYER_LEFT_CLICK}}. + ]], + }, GameMode = { Include = { "^gm.*", "^eGameMode_.*" }, diff --git a/Server/Plugins/APIDump/Classes/Projectiles.lua b/Server/Plugins/APIDump/Classes/Projectiles.lua index 748f58b71..e6d347313 100644 --- a/Server/Plugins/APIDump/Classes/Projectiles.lua +++ b/Server/Plugins/APIDump/Classes/Projectiles.lua @@ -131,6 +131,7 @@ return GetEntityEffect = { Params = "", Return = "{{cEntityEffect}}", Notes = "Returns the entity effect in this potion" }, GetEntityEffectType = { Params = "", Return = "{{cEntityEffect|Entity effect type}}", Notes = "Returns the effect type of this potion" }, GetPotionColor = { Params = "", Return = "number", Notes = "Returns the color index of the particles emitted by this potion" }, + GetItem = { Params = "", Return = "{{cItem}}", Notes = "Gets the potion item that was thrown." }, SetEntityEffect = { Params = "{{cEntityEffect}}", Return = "", Notes = "Sets the entity effect for this potion" }, SetEntityEffectType = { Params = "{{cEntityEffect|Entity effect type}}", Return = "", Notes = "Sets the effect type of this potion" }, SetPotionColor = { Params = "number", Return = "", Notes = "Sets the color index of the particles for this potion" }, diff --git a/Server/Plugins/Debuggers/Debuggers.lua b/Server/Plugins/Debuggers/Debuggers.lua index 422993932..c11052071 100644 --- a/Server/Plugins/Debuggers/Debuggers.lua +++ b/Server/Plugins/Debuggers/Debuggers.lua @@ -2156,3 +2156,31 @@ end + +function HandleBlkCmd(a_Split, a_Player) + -- Gets info about the block the player is looking at. + local World = a_Player:GetWorld(); + + local Callbacks = { + OnNextBlock = function(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta) + if (a_BlockType ~= E_BLOCK_AIR) then + a_Player:SendMessage("Block at " .. a_BlockX .. ", " .. a_BlockY .. ", " .. a_BlockZ .. " is " .. a_BlockType .. ":" .. a_BlockMeta) + return true; + end + end + }; + + local EyePos = a_Player:GetEyePosition(); + local LookVector = a_Player:GetLookVector(); + LookVector:Normalize(); + + local End = EyePos + LookVector * 50; + + cLineBlockTracer.Trace(World, Callbacks, EyePos.x, EyePos.y, EyePos.z, End.x, End.y, End.z); + + return true; +end + + + + diff --git a/Server/Plugins/Debuggers/Info.lua b/Server/Plugins/Debuggers/Info.lua index 99d3ebe74..51406c27c 100644 --- a/Server/Plugins/Debuggers/Info.lua +++ b/Server/Plugins/Debuggers/Info.lua @@ -202,6 +202,12 @@ g_PluginInfo = Handler = HandleRemoveXp, HelpString = "Remove all xp" }, + ["/blk"] = + { + Permission = "debuggers", + Handler = HandleBlkCmd, + HelpString = "Gets info about the block you are looking at" + }, }, -- Commands ConsoleCommands = diff --git a/src/ByteBuffer.cpp b/src/ByteBuffer.cpp index 526b8b754..16620becd 100644 --- a/src/ByteBuffer.cpp +++ b/src/ByteBuffer.cpp @@ -1034,3 +1034,21 @@ void cByteBuffer::CheckValid(void) const +size_t cByteBuffer::GetVarIntSize(UInt32 a_Value) +{ + size_t Count = 0; + + do + { + // If the value cannot be expressed in 7 bits, it needs to take up another byte + Count++; + a_Value >>= 7; + } while (a_Value != 0); + + return Count; +} + + + + + diff --git a/src/ByteBuffer.h b/src/ByteBuffer.h index df8443dd2..128a907b2 100644 --- a/src/ByteBuffer.h +++ b/src/ByteBuffer.h @@ -129,6 +129,9 @@ public: /** Checks if the internal state is valid (read and write positions in the correct bounds) using ASSERTs */ void CheckValid(void) const; + /** Gets the number of bytes that are needed to represent the given VarInt */ + static size_t GetVarIntSize(UInt32 a_Value); + protected: char * m_Buffer; size_t m_BufferSize; // Total size of the ringbuffer diff --git a/src/CheckBasicStyle.lua b/src/CheckBasicStyle.lua index bd9430e59..8d188bb67 100755 --- a/src/CheckBasicStyle.lua +++ b/src/CheckBasicStyle.lua @@ -237,7 +237,7 @@ local function ProcessFile(a_FileName) local lineCounter = 1 local lastIndentLevel = 0 local isLastLineControl = false - all:gsub("\r\n", "\n") -- normalize CRLF into LF-only + all = all:gsub("\r\n", "\n") -- normalize CRLF into LF-only string.gsub(all .. "\n", "[^\n]*\n", -- Iterate over each line, while preserving empty lines function(a_Line) -- Check against each violation pattern: diff --git a/src/Chunk.cpp b/src/Chunk.cpp index ff0841440..363de9933 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -2785,7 +2785,7 @@ cChunk * cChunk::GetRelNeighborChunkAdjustCoords(int & a_RelX, int & a_RelZ) con -void cChunk::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle) +void cChunk::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { for (auto ClientHandle : m_LoadedByClient) { @@ -2883,6 +2883,18 @@ void cChunk::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHandl +void cChunk::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + for (auto ClientHandle : m_LoadedByClient) + { + ClientHandle->SendDetachEntity(a_Entity, a_PreviousVehicle); + } // for itr - LoadedByClient[] +} + + + + + void cChunk::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude) { for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) diff --git a/src/Chunk.h b/src/Chunk.h index 3260a056f..c7987723b 100644 --- a/src/Chunk.h +++ b/src/Chunk.h @@ -340,12 +340,13 @@ public: // Broadcast various packets to all clients of this chunk: // (Please keep these alpha-sorted) - void BroadcastAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle); + void BroadcastAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle); void BroadcastBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude = nullptr); void BroadcastBlockBreakAnimation(UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage, const cClientHandle * a_Exclude = nullptr); void BroadcastBlockEntity (int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = nullptr); void BroadcastCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); void BroadcastDestroyEntity (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); + void BroadcastDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle); void BroadcastEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityEquipment (const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityHeadLook (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 7d1685210..83c1c12dd 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -338,7 +338,7 @@ cChunk * cChunkMap::FindChunk(int a_ChunkX, int a_ChunkZ) -void cChunkMap::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle) +void cChunkMap::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { cCSLock Lock(m_CSLayers); cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); @@ -442,6 +442,22 @@ void cChunkMap::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHa +void cChunkMap::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); + if (Chunk == nullptr) + { + return; + } + // It's perfectly legal to broadcast packets even to invalid chunks! + Chunk->BroadcastDetachEntity(a_Entity, a_PreviousVehicle); +} + + + + + void cChunkMap::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude) { cCSLock Lock(m_CSLayers); diff --git a/src/ChunkMap.h b/src/ChunkMap.h index 560987b4f..bbde06dcf 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -71,13 +71,14 @@ public: // Broadcast respective packets to all clients of the chunk where the event is taking place // (Please keep these alpha-sorted) - void BroadcastAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle); + void BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle); void BroadcastBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude = nullptr); void BroadcastBlockBreakAnimation(UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage, const cClientHandle * a_Exclude = nullptr); void BroadcastBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude); void BroadcastCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); void BroadcastCollectPickup(const cPickup & a_Pickup, const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); void BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); + void BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle); void BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityHeadLook(const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); diff --git a/src/ChunkSender.cpp b/src/ChunkSender.cpp index 1d55cf743..ddcd3b534 100644 --- a/src/ChunkSender.cpp +++ b/src/ChunkSender.cpp @@ -257,7 +257,7 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, std::unordered_setSendAttachEntity(a_Entity, a_Vehicle); } @@ -2319,6 +2325,15 @@ void cClientHandle::SendDestroyEntity(const cEntity & a_Entity) +void cClientHandle::SendDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + m_Protocol->SendDetachEntity(a_Entity, a_PreviousVehicle); +} + + + + + void cClientHandle::SendDisconnect(const AString & a_Reason) { // Destruction (Destroy()) is called when the client disconnects, not when a disconnect packet (or anything else) is sent diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 0bcf0b9fe..c49de647f 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -144,7 +144,7 @@ public: // tolua_export // The following functions send the various packets: // (Please keep these alpha-sorted) - void SendAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle); + void SendAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle); void SendBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType); void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage); void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); // tolua_export @@ -158,6 +158,7 @@ public: // tolua_export void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer); void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player); void SendDestroyEntity (const cEntity & a_Entity); + void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle); void SendDisconnect (const AString & a_Reason); void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display); void SendEditSign (int a_BlockX, int a_BlockY, int a_BlockZ); diff --git a/src/Defines.h b/src/Defines.h index 63ea3b8e5..37b80cca3 100644 --- a/src/Defines.h +++ b/src/Defines.h @@ -60,12 +60,13 @@ enum eBlockFace /** PlayerDigging status constants */ enum { - DIG_STATUS_STARTED = 0, - DIG_STATUS_CANCELLED = 1, - DIG_STATUS_FINISHED = 2, - DIG_STATUS_DROP_STACK= 3, - DIG_STATUS_DROP_HELD = 4, - DIG_STATUS_SHOOT_EAT = 5, + DIG_STATUS_STARTED = 0, + DIG_STATUS_CANCELLED = 1, + DIG_STATUS_FINISHED = 2, + DIG_STATUS_DROP_STACK = 3, + DIG_STATUS_DROP_HELD = 4, + DIG_STATUS_SHOOT_EAT = 5, + DIG_STATUS_SWAP_ITEM_IN_HAND = 6, } ; diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 30aa87f37..6e1dec957 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -1927,10 +1927,13 @@ void cEntity::AttachTo(cEntity * a_AttachTo) Detach(); } - // Attach to the new entity: + // Update state information m_AttachedTo = a_AttachTo; a_AttachTo->m_Attachee = this; - m_World->BroadcastAttachEntity(*this, a_AttachTo); + if (a_AttachTo != nullptr) + { + m_World->BroadcastAttachEntity(*this, *a_AttachTo); + } } @@ -1941,12 +1944,13 @@ void cEntity::Detach(void) { if (m_AttachedTo == nullptr) { - // Attached to no entity, our work is done + // Already not attached to any entity, our work is done return; } + m_World->BroadcastDetachEntity(*this, *m_AttachedTo); + m_AttachedTo->m_Attachee = nullptr; m_AttachedTo = nullptr; - m_World->BroadcastAttachEntity(*this, nullptr); } diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp index 69c2db5e7..462fa5057 100644 --- a/src/Entities/Pawn.cpp +++ b/src/Entities/Pawn.cpp @@ -418,8 +418,9 @@ void cPawn::HandleFalling(void) TakeDamage(dtFalling, nullptr, Damage, Damage, 0); // Fall particles - int ParticleSize = static_cast((std::min(15, Damage) - 1.f) * ((50.f - 20.f) / (15.f - 1.f)) + 20.f); - GetWorld()->BroadcastSoundParticleEffect(EffectID::PARTICLE_FALL_PARTICLES, POSX_TOINT, POSY_TOINT - 1, POSZ_TOINT, ParticleSize); + // TODO: Re-enable this when effects in 1.9 aren't broken (right now this uses the wrong effect ID in 1.9 and the right one in 1.8 and 1.7) + // int ParticleSize = static_cast((std::min(15, Damage) - 1.f) * ((50.f - 20.f) / (15.f - 1.f)) + 20.f); + // GetWorld()->BroadcastSoundParticleEffect(EffectID::PARTICLE_FALL_PARTICLES, POSX_TOINT, POSY_TOINT - 1, POSZ_TOINT, ParticleSize); } m_bTouchGround = true; diff --git a/src/Entities/SplashPotionEntity.cpp b/src/Entities/SplashPotionEntity.cpp index e61df0525..af4008e83 100644 --- a/src/Entities/SplashPotionEntity.cpp +++ b/src/Entities/SplashPotionEntity.cpp @@ -80,6 +80,7 @@ cSplashPotionEntity::cSplashPotionEntity( const cItem & a_Item ) : super(pkSplashPotion, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25), + m_Item(a_Item), m_DestroyTimer(-1) { SetSpeed(a_Speed); diff --git a/src/Entities/SplashPotionEntity.h b/src/Entities/SplashPotionEntity.h index c9831ce88..85aa5046f 100644 --- a/src/Entities/SplashPotionEntity.h +++ b/src/Entities/SplashPotionEntity.h @@ -41,6 +41,7 @@ public: cEntityEffect::eType GetEntityEffectType(void) const { return m_EntityEffectType; } cEntityEffect GetEntityEffect(void) const { return m_EntityEffect; } int GetPotionColor(void) const { return m_PotionColor; } + const cItem & GetItem(void) const { return m_Item; } void SetEntityEffectType(cEntityEffect::eType a_EntityEffectType) { m_EntityEffectType = a_EntityEffectType; } void SetEntityEffect(cEntityEffect a_EntityEffect) { m_EntityEffect = a_EntityEffect; } @@ -53,6 +54,7 @@ protected: cEntityEffect::eType m_EntityEffectType; cEntityEffect m_EntityEffect; int m_PotionColor; + cItem m_Item; // cProjectileEntity overrides: diff --git a/src/Mobs/Creeper.h b/src/Mobs/Creeper.h index 73243d11e..aea36def3 100644 --- a/src/Mobs/Creeper.h +++ b/src/Mobs/Creeper.h @@ -25,6 +25,7 @@ public: bool IsBlowing(void) const {return m_bIsBlowing; } bool IsCharged(void) const {return m_bIsCharged; } + bool IsBurnedWithFlintAndSteel(void) const {return m_BurnedWithFlintAndSteel; } private: diff --git a/src/Protocol/CMakeLists.txt b/src/Protocol/CMakeLists.txt index cd4f33c60..3668f0c42 100644 --- a/src/Protocol/CMakeLists.txt +++ b/src/Protocol/CMakeLists.txt @@ -11,6 +11,7 @@ SET (SRCS Packetizer.cpp Protocol17x.cpp Protocol18x.cpp + Protocol19x.cpp ProtocolRecognizer.cpp ) @@ -22,10 +23,12 @@ SET (HDRS Protocol.h Protocol17x.h Protocol18x.h + Protocol19x.h ProtocolRecognizer.h ) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set_source_files_properties(Protocol19x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=old-style-cast -Wno-error=sign-conversion -Wno-error=conversion -Wno-error=switch-enum -Wno-error=switch") set_source_files_properties(Protocol18x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=old-style-cast -Wno-error=sign-conversion -Wno-error=conversion -Wno-error=switch-enum -Wno-error=switch") set_source_files_properties(Protocol17x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum ") endif() diff --git a/src/Protocol/ChunkDataSerializer.cpp b/src/Protocol/ChunkDataSerializer.cpp index 848a6f597..31f7e8d9c 100644 --- a/src/Protocol/ChunkDataSerializer.cpp +++ b/src/Protocol/ChunkDataSerializer.cpp @@ -10,6 +10,7 @@ #include "zlib/zlib.h" #include "ByteBuffer.h" #include "Protocol18x.h" +#include "Protocol19x.h" @@ -19,13 +20,15 @@ cChunkDataSerializer::cChunkDataSerializer( const cChunkDef::BlockNibbles & a_BlockMetas, const cChunkDef::BlockNibbles & a_BlockLight, const cChunkDef::BlockNibbles & a_BlockSkyLight, - const unsigned char * a_BiomeData + const unsigned char * a_BiomeData, + const eDimension a_Dimension ) : m_BlockTypes(a_BlockTypes), m_BlockMetas(a_BlockMetas), m_BlockLight(a_BlockLight), m_BlockSkyLight(a_BlockSkyLight), - m_BiomeData(a_BiomeData) + m_BiomeData(a_BiomeData), + m_Dimension(a_Dimension) { } @@ -45,6 +48,8 @@ const AString & cChunkDataSerializer::Serialize(int a_Version, int a_ChunkX, int { case RELEASE_1_3_2: Serialize39(data); break; case RELEASE_1_8_0: Serialize47(data, a_ChunkX, a_ChunkZ); break; + case RELEASE_1_9_0: Serialize107(data, a_ChunkX, a_ChunkZ); break; + case RELEASE_1_9_4: Serialize110(data, a_ChunkX, a_ChunkZ); break; // TODO: Other protocol versions may serialize the data differently; implement here default: @@ -189,3 +194,292 @@ void cChunkDataSerializer::Serialize47(AString & a_Data, int a_ChunkX, int a_Chu + +void cChunkDataSerializer::Serialize107(AString & a_Data, int a_ChunkX, int a_ChunkZ) +{ + // This function returns the fully compressed packet (including packet size), not the raw packet! + + // Create the packet: + cByteBuffer Packet(512 KiB); + Packet.WriteVarInt32(0x20); // Packet id (Chunk Data packet) + Packet.WriteBEInt32(a_ChunkX); + Packet.WriteBEInt32(a_ChunkZ); + Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag + Packet.WriteVarInt32(0x0000ffff); // We're aways sending the full chunk with no additional data, so the bitmap is 0xffff + // Write the chunk size: + const size_t NumChunkSections = 16; + const size_t ChunkSectionBlocks = 16 * 16 * 16; + const size_t BitsPerEntry = 13; + const size_t Mask = (1 << BitsPerEntry) - 1; // Creates a mask that is 13 bits long, ie 0b1111111111111 + const size_t ChunkSectionDataArraySize = (ChunkSectionBlocks * BitsPerEntry) / 8 / 8; // Convert from bit count to long count + size_t ChunkSectionSize = ( + 1 + // Bits per block - set to 13, so the global palette is used and the palette has a length of 0 + 1 + // Palette length + 2 + // Data array length VarInt - 2 bytes for the current value + ChunkSectionDataArraySize * 8 + // Actual block data - multiplied by 8 because first number is longs + sizeof(m_BlockLight) / NumChunkSections // Block light + ); + + if (m_Dimension == dimOverworld) + { + // Sky light is only sent in the overworld. + ChunkSectionSize += sizeof(m_BlockSkyLight) / NumChunkSections; + } + + const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width; + size_t ChunkSize = ( + ChunkSectionSize * 16 + + BiomeDataSize + ); + Packet.WriteVarInt32(static_cast(ChunkSize)); + + // Write each chunk section... + for (size_t SectionIndex = 0; SectionIndex < 16; SectionIndex++) + { + Packet.WriteBEUInt8(BitsPerEntry); + Packet.WriteVarInt32(0); // Palette length is 0 + Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); + + size_t StartIndex = SectionIndex * ChunkSectionBlocks; + + UInt64 TempLong = 0; // Temporary value that will be stored into + UInt64 CurrentlyWrittenIndex = 0; // "Index" of the long that would be written to + + for (size_t Index = 0; Index < ChunkSectionBlocks; Index++) + { + UInt64 Value = static_cast(m_BlockTypes[StartIndex + Index] << 4); + if (Index % 2 == 0) + { + Value |= m_BlockMetas[(StartIndex + Index) / 2] & 0x0f; + } + else + { + Value |= m_BlockMetas[(StartIndex + Index) / 2] >> 4; + } + Value &= Mask; // It shouldn't go out of bounds, but it's still worth being careful + + // Painful part where we write data into the long array. Based off of the normal code. + size_t BitPosition = Index * BitsPerEntry; + size_t FirstIndex = BitPosition / 64; + size_t SecondIndex = ((Index + 1) * BitsPerEntry - 1) / 64; + size_t BitOffset = BitPosition % 64; + + if (FirstIndex != CurrentlyWrittenIndex) + { + // Write the current data before modifiying it. + Packet.WriteBEUInt64(TempLong); + TempLong = 0; + CurrentlyWrittenIndex = FirstIndex; + } + + TempLong |= (Value << BitOffset); + + if (FirstIndex != SecondIndex) + { + // Part of the data is now in the second long; write the first one first + Packet.WriteBEUInt64(TempLong); + CurrentlyWrittenIndex = SecondIndex; + + TempLong = (Value >> (64 - BitOffset)); + } + } + // The last long will generally not be written + Packet.WriteBEUInt64(TempLong); + + // Light - stored as a nibble, so we need half sizes + // As far as I know, there isn't a method to only write a range of the array + for (size_t Index = 0; Index < ChunkSectionBlocks / 2; Index++) + { + Packet.WriteBEUInt8(m_BlockLight[(StartIndex / 2) + Index]); + } + if (m_Dimension == dimOverworld) + { + // Skylight is only sent in the overworld; the nether and end do not use it + for (size_t Index = 0; Index < ChunkSectionBlocks / 2; Index++) + { + Packet.WriteBEUInt8(m_BlockSkyLight[(StartIndex / 2) + Index]); + } + } + } + + // Write the biome data + Packet.WriteBuf(m_BiomeData, BiomeDataSize); + + AString PacketData; + Packet.ReadAll(PacketData); + Packet.CommitRead(); + + cByteBuffer Buffer(20); + if (PacketData.size() >= 256) + { + if (!cProtocol190::CompressPacket(PacketData, a_Data)) + { + ASSERT(!"Packet compression failed."); + a_Data.clear(); + return; + } + } + else + { + AString PostData; + Buffer.WriteVarInt32(static_cast(Packet.GetUsedSpace() + 1)); + Buffer.WriteVarInt32(0); + Buffer.ReadAll(PostData); + Buffer.CommitRead(); + + a_Data.clear(); + a_Data.reserve(PostData.size() + PacketData.size()); + a_Data.append(PostData.data(), PostData.size()); + a_Data.append(PacketData.data(), PacketData.size()); + } +} + + + + + +void cChunkDataSerializer::Serialize110(AString & a_Data, int a_ChunkX, int a_ChunkZ) +{ + // This function returns the fully compressed packet (including packet size), not the raw packet! + + // Create the packet: + cByteBuffer Packet(512 KiB); + Packet.WriteVarInt32(0x20); // Packet id (Chunk Data packet) + Packet.WriteBEInt32(a_ChunkX); + Packet.WriteBEInt32(a_ChunkZ); + Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag + Packet.WriteVarInt32(0x0000ffff); // We're aways sending the full chunk with no additional data, so the bitmap is 0xffff + // Write the chunk size: + const size_t NumChunkSections = 16; + const size_t ChunkSectionBlocks = 16 * 16 * 16; + const size_t BitsPerEntry = 13; + const size_t Mask = (1 << BitsPerEntry) - 1; // Creates a mask that is 13 bits long, ie 0b1111111111111 + const size_t ChunkSectionDataArraySize = (ChunkSectionBlocks * BitsPerEntry) / 8 / 8; // Convert from bit count to long count + size_t ChunkSectionSize = ( + 1 + // Bits per block - set to 13, so the global palette is used and the palette has a length of 0 + 1 + // Palette length + 2 + // Data array length VarInt - 2 bytes for the current value + ChunkSectionDataArraySize * 8 + // Actual block data - multiplied by 8 because first number is longs + sizeof(m_BlockLight) / NumChunkSections // Block light + ); + + if (m_Dimension == dimOverworld) + { + // Sky light is only sent in the overworld. + ChunkSectionSize += sizeof(m_BlockSkyLight) / NumChunkSections; + } + + const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width; + size_t ChunkSize = ( + ChunkSectionSize * 16 + + BiomeDataSize + ); + Packet.WriteVarInt32(static_cast(ChunkSize)); + + // Write each chunk section... + for (size_t SectionIndex = 0; SectionIndex < 16; SectionIndex++) + { + Packet.WriteBEUInt8(BitsPerEntry); + Packet.WriteVarInt32(0); // Palette length is 0 + Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); + + size_t StartIndex = SectionIndex * ChunkSectionBlocks; + + UInt64 TempLong = 0; // Temporary value that will be stored into + UInt64 CurrentlyWrittenIndex = 0; // "Index" of the long that would be written to + + for (size_t Index = 0; Index < ChunkSectionBlocks; Index++) + { + UInt64 Value = static_cast(m_BlockTypes[StartIndex + Index] << 4); + if (Index % 2 == 0) + { + Value |= m_BlockMetas[(StartIndex + Index) / 2] & 0x0f; + } + else + { + Value |= m_BlockMetas[(StartIndex + Index) / 2] >> 4; + } + Value &= Mask; // It shouldn't go out of bounds, but it's still worth being careful + + // Painful part where we write data into the long array. Based off of the normal code. + size_t BitPosition = Index * BitsPerEntry; + size_t FirstIndex = BitPosition / 64; + size_t SecondIndex = ((Index + 1) * BitsPerEntry - 1) / 64; + size_t BitOffset = BitPosition % 64; + + if (FirstIndex != CurrentlyWrittenIndex) + { + // Write the current data before modifiying it. + Packet.WriteBEUInt64(TempLong); + TempLong = 0; + CurrentlyWrittenIndex = FirstIndex; + } + + TempLong |= (Value << BitOffset); + + if (FirstIndex != SecondIndex) + { + // Part of the data is now in the second long; write the first one first + Packet.WriteBEUInt64(TempLong); + CurrentlyWrittenIndex = SecondIndex; + + TempLong = (Value >> (64 - BitOffset)); + } + } + // The last long will generally not be written + Packet.WriteBEUInt64(TempLong); + + // Light - stored as a nibble, so we need half sizes + // As far as I know, there isn't a method to only write a range of the array + for (size_t Index = 0; Index < ChunkSectionBlocks / 2; Index++) + { + Packet.WriteBEUInt8(m_BlockLight[(StartIndex / 2) + Index]); + } + if (m_Dimension == dimOverworld) + { + // Skylight is only sent in the overworld; the nether and end do not use it + for (size_t Index = 0; Index < ChunkSectionBlocks / 2; Index++) + { + Packet.WriteBEUInt8(m_BlockSkyLight[(StartIndex / 2) + Index]); + } + } + } + + // Write the biome data + Packet.WriteBuf(m_BiomeData, BiomeDataSize); + + // Identify 1.9.4's tile entity list as empty + Packet.WriteBEUInt8(0); + + AString PacketData; + Packet.ReadAll(PacketData); + Packet.CommitRead(); + + cByteBuffer Buffer(20); + if (PacketData.size() >= 256) + { + if (!cProtocol190::CompressPacket(PacketData, a_Data)) + { + ASSERT(!"Packet compression failed."); + a_Data.clear(); + return; + } + } + else + { + AString PostData; + Buffer.WriteVarInt32(static_cast(Packet.GetUsedSpace() + 1)); + Buffer.WriteVarInt32(0); + Buffer.ReadAll(PostData); + Buffer.CommitRead(); + + a_Data.clear(); + a_Data.reserve(PostData.size() + PacketData.size()); + a_Data.append(PostData.data(), PostData.size()); + a_Data.append(PacketData.data(), PacketData.size()); + } +} + + + + diff --git a/src/Protocol/ChunkDataSerializer.h b/src/Protocol/ChunkDataSerializer.h index 823a93f15..1e87967c7 100644 --- a/src/Protocol/ChunkDataSerializer.h +++ b/src/Protocol/ChunkDataSerializer.h @@ -17,6 +17,7 @@ protected: const cChunkDef::BlockNibbles & m_BlockLight; const cChunkDef::BlockNibbles & m_BlockSkyLight; const unsigned char * m_BiomeData; + const eDimension m_Dimension; typedef std::map Serializations; @@ -24,12 +25,16 @@ protected: void Serialize39(AString & a_Data); // Release 1.3.1 to 1.7.10 void Serialize47(AString & a_Data, int a_ChunkX, int a_ChunkZ); // Release 1.8 + void Serialize107(AString & a_Data, int a_ChunkX, int a_ChunkZ); // Release 1.9 + void Serialize110(AString & a_Data, int a_ChunkX, int a_ChunkZ); // Release 1.9.4 public: enum { RELEASE_1_3_2 = 39, RELEASE_1_8_0 = 47, + RELEASE_1_9_0 = 107, + RELEASE_1_9_4 = 110, } ; cChunkDataSerializer( @@ -37,7 +42,8 @@ public: const cChunkDef::BlockNibbles & a_BlockMetas, const cChunkDef::BlockNibbles & a_BlockLight, const cChunkDef::BlockNibbles & a_BlockSkyLight, - const unsigned char * a_BiomeData + const unsigned char * a_BiomeData, + const eDimension a_Dimension ); const AString & Serialize(int a_Version, int a_ChunkX, int a_ChunkZ); // Returns one of the internal m_Serializations[] diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 62faf0b28..a00923394 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -64,7 +64,7 @@ public: virtual void DataReceived(const char * a_Data, size_t a_Size) = 0; // Sending stuff to clients (alphabetically sorted): - virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle) = 0; + virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle) = 0; virtual void SendBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) = 0; virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) = 0; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; @@ -74,6 +74,7 @@ public: virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) = 0; virtual void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player) = 0; virtual void SendDestroyEntity (const cEntity & a_Entity) = 0; + virtual void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) = 0; virtual void SendDisconnect (const AString & a_Reason) = 0; virtual void SendEditSign (int a_BlockX, int a_BlockY, int a_BlockZ) = 0; ///< Request the client to open up the sign editor for the sign (1.6+) virtual void SendEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) = 0; diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 69b87e4d1..843675333 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -160,13 +160,13 @@ void cProtocol172::DataReceived(const char * a_Data, size_t a_Size) -void cProtocol172::SendAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle) +void cProtocol172::SendAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { ASSERT(m_State == 3); // In game mode? cPacketizer Pkt(*this, 0x1b); // Attach Entity packet Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEUInt32((a_Vehicle != nullptr) ? a_Vehicle->GetUniqueID() : 0); + Pkt.WriteBEUInt32(a_Vehicle.GetUniqueID()); Pkt.WriteBool(false); } @@ -324,6 +324,20 @@ void cProtocol172::SendDestroyEntity(const cEntity & a_Entity) +void cProtocol172::SendDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1b); // Attach Entity packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt32(0); + Pkt.WriteBool(false); +} + + + + + void cProtocol172::SendDisconnect(const AString & a_Reason) { switch (m_State) diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h index 24d8f8898..859a33f05 100644 --- a/src/Protocol/Protocol17x.h +++ b/src/Protocol/Protocol17x.h @@ -61,7 +61,7 @@ public: virtual void DataReceived(const char * a_Data, size_t a_Size) override; /** Sending stuff to clients (alphabetically sorted): */ - virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle) override; + virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle) override; virtual void SendBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) override; virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; @@ -71,6 +71,7 @@ public: virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; virtual void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player) override; virtual void SendDestroyEntity (const cEntity & a_Entity) override; + virtual void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) override; virtual void SendDisconnect (const AString & a_Reason) override; virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override; virtual void SendEditSign (int a_BlockX, int a_BlockY, int a_BlockZ) override; ///< Request the client to open up the sign editor for the sign (1.6+) diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index eab6c4721..ae9571f03 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -171,13 +171,13 @@ void cProtocol180::DataReceived(const char * a_Data, size_t a_Size) -void cProtocol180::SendAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle) +void cProtocol180::SendAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { ASSERT(m_State == 3); // In game mode? cPacketizer Pkt(*this, 0x1b); // Attach Entity packet Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEUInt32((a_Vehicle != nullptr) ? a_Vehicle->GetUniqueID() : 0); + Pkt.WriteBEUInt32(a_Vehicle.GetUniqueID()); Pkt.WriteBool(false); } @@ -317,6 +317,20 @@ void cProtocol180::SendDestroyEntity(const cEntity & a_Entity) +void cProtocol180::SendDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1b); // Attach Entity packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt32(0); + Pkt.WriteBool(false); +} + + + + + void cProtocol180::SendDisconnect(const AString & a_Reason) { switch (m_State) diff --git a/src/Protocol/Protocol18x.h b/src/Protocol/Protocol18x.h index 8a446a1b9..08a51f342 100644 --- a/src/Protocol/Protocol18x.h +++ b/src/Protocol/Protocol18x.h @@ -60,7 +60,7 @@ public: virtual void DataReceived(const char * a_Data, size_t a_Size) override; /** Sending stuff to clients (alphabetically sorted): */ - virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle) override; + virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle) override; virtual void SendBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) override; virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; @@ -70,6 +70,7 @@ public: virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; virtual void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player) override; virtual void SendDestroyEntity (const cEntity & a_Entity) override; + virtual void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) override; virtual void SendDisconnect (const AString & a_Reason) override; virtual void SendEditSign (int a_BlockX, int a_BlockY, int a_BlockZ) override; ///< Request the client to open up the sign editor for the sign (1.6+) virtual void SendEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) override; diff --git a/src/Protocol/Protocol19x.cpp b/src/Protocol/Protocol19x.cpp new file mode 100644 index 000000000..4a9732fb0 --- /dev/null +++ b/src/Protocol/Protocol19x.cpp @@ -0,0 +1,4306 @@ + +// Protocol19x.cpp + +/* +Implements the 1.9.x protocol classes: + - cProtocol190 + - release 1.9.0 protocol (#107) + - cProtocol191 + - release 1.9.1 protocol (#108) + - cProtocol192 + - release 1.9.2 protocol (#109) + - cProtocol194 + - release 1.9.4 protocol (#110) +(others may be added later in the future for the 1.9 release series) +*/ + +#include "Globals.h" +#include "json/json.h" +#include "Protocol19x.h" +#include "ChunkDataSerializer.h" +#include "PolarSSL++/Sha1Checksum.h" +#include "Packetizer.h" + +#include "../ClientHandle.h" +#include "../Root.h" +#include "../Server.h" +#include "../World.h" +#include "../EffectID.h" +#include "../StringCompression.h" +#include "../CompositeChat.h" +#include "../Statistics.h" + +#include "../WorldStorage/FastNBT.h" +#include "../WorldStorage/EnchantmentSerializer.h" + +#include "../Entities/ExpOrb.h" +#include "../Entities/Minecart.h" +#include "../Entities/FallingBlock.h" +#include "../Entities/Painting.h" +#include "../Entities/Pickup.h" +#include "../Entities/Player.h" +#include "../Entities/ItemFrame.h" +#include "../Entities/ArrowEntity.h" +#include "../Entities/FireworkEntity.h" +#include "../Entities/SplashPotionEntity.h" + +#include "../Items/ItemSpawnEgg.h" + +#include "../Mobs/IncludeAllMonsters.h" +#include "../UI/Window.h" + +#include "../BlockEntities/BeaconEntity.h" +#include "../BlockEntities/CommandBlockEntity.h" +#include "../BlockEntities/MobHeadEntity.h" +#include "../BlockEntities/MobSpawnerEntity.h" +#include "../BlockEntities/FlowerPotEntity.h" +#include "Bindings/PluginManager.h" + + + + + +/** The slot number that the client uses to indicate "outside the window". */ +static const Int16 SLOT_NUM_OUTSIDE = -999; + + + + + +#define HANDLE_READ(ByteBuf, Proc, Type, Var) \ + Type Var; \ + if (!ByteBuf.Proc(Var))\ + {\ + return;\ + } + + + + + +#define HANDLE_PACKET_READ(ByteBuf, Proc, Type, Var) \ + Type Var; \ + { \ + if (!ByteBuf.Proc(Var)) \ + { \ + ByteBuf.CheckValid(); \ + return false; \ + } \ + ByteBuf.CheckValid(); \ + } + + + + + +const int MAX_ENC_LEN = 512; // Maximum size of the encrypted message; should be 128, but who knows... +const uLongf MAX_COMPRESSED_PACKET_LEN = 200 KiB; // Maximum size of compressed packets. + + + + + +// fwd: main.cpp: +extern bool g_ShouldLogCommIn, g_ShouldLogCommOut; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol190: + +cProtocol190::cProtocol190(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client), + m_ServerAddress(a_ServerAddress), + m_ServerPort(a_ServerPort), + m_State(a_State), + m_ReceivedData(32 KiB), + m_IsEncrypted(false), + m_LastSentDimension(dimNotSet) +{ + + // 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)) + { + LOGD("Player at %s connected via BungeeCord", Params[1].c_str()); + m_ServerAddress = Params[0]; + m_Client->SetIPString(Params[1]); + m_Client->SetUUID(cMojangAPI::MakeUUIDShort(Params[2])); + m_Client->SetProperties(Params[3]); + } + + // Create the comm log file, if so requested: + if (g_ShouldLogCommIn || g_ShouldLogCommOut) + { + static int sCounter = 0; + cFile::CreateFolder("CommLogs"); + AString IP(a_Client->GetIPString()); + ReplaceString(IP, ":", "_"); + AString FileName = Printf("CommLogs/%x_%d__%s.log", + static_cast(time(nullptr)), + sCounter++, + IP.c_str() + ); + if (!m_CommLogFile.Open(FileName, cFile::fmWrite)) + { + LOG("Cannot log communication to file, the log file \"%s\" cannot be opened for writing.", FileName.c_str()); + } + } +} + + + + + +void cProtocol190::DataReceived(const char * a_Data, size_t a_Size) +{ + if (m_IsEncrypted) + { + Byte Decrypted[512]; + while (a_Size > 0) + { + size_t NumBytes = (a_Size > sizeof(Decrypted)) ? sizeof(Decrypted) : a_Size; + m_Decryptor.ProcessData(Decrypted, reinterpret_cast(a_Data), NumBytes); + AddReceivedData(reinterpret_cast(Decrypted), NumBytes); + a_Size -= NumBytes; + a_Data += NumBytes; + } + } + else + { + AddReceivedData(a_Data, a_Size); + } +} + + + + + +void cProtocol190::SendAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) +{ + ASSERT(m_State == 3); // In game mode? + cPacketizer Pkt(*this, 0x40); // Set passangers packet + Pkt.WriteVarInt32(a_Vehicle.GetUniqueID()); + Pkt.WriteVarInt32(1); // 1 passenger + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol190::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0a); // Block Action packet + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); + Pkt.WriteBEInt8(a_Byte1); + Pkt.WriteBEInt8(a_Byte2); + Pkt.WriteVarInt32(a_BlockType); +} + + + + + +void cProtocol190::SendBlockBreakAnim(UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x08); // Block Break Animation packet + Pkt.WriteVarInt32(a_EntityID); + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); + Pkt.WriteBEInt8(a_Stage); +} + + + + + +void cProtocol190::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, 0x0b); // Block Change packet + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); + Pkt.WriteVarInt32((static_cast(a_BlockType) << 4) | (static_cast(a_BlockMeta) & 15)); +} + + + + + +void cProtocol190::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x10); // Multi Block Change packet + Pkt.WriteBEInt32(a_ChunkX); + Pkt.WriteBEInt32(a_ChunkZ); + Pkt.WriteVarInt32(static_cast(a_Changes.size())); + for (sSetBlockVector::const_iterator itr = a_Changes.begin(), end = a_Changes.end(); itr != end; ++itr) + { + Int16 Coords = static_cast(itr->m_RelY | (itr->m_RelZ << 8) | (itr->m_RelX << 12)); + Pkt.WriteBEInt16(Coords); + Pkt.WriteVarInt32(static_cast(itr->m_BlockType & 0xFFF) << 4 | (itr->m_BlockMeta & 0xF)); + } // for itr - a_Changes[] +} + + + + + +void cProtocol190::SendChat(const AString & a_Message, eChatType a_Type) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0f); // Chat Message packet + Pkt.WriteString(Printf("{\"text\":\"%s\"}", EscapeString(a_Message).c_str())); + Pkt.WriteBEInt8(a_Type); +} + + + + + +void cProtocol190::SendChat(const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) +{ + ASSERT(m_State == 3); // In game mode? + + + // Send the message to the client: + cPacketizer Pkt(*this, 0x0f); // Chat Message packet + Pkt.WriteString(a_Message.CreateJsonString(a_ShouldUseChatPrefixes)); + Pkt.WriteBEInt8(a_Type); +} + + + + + +void cProtocol190::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) +{ + ASSERT(m_State == 3); // In game mode? + + // Serialize first, before creating the Packetizer (the packetizer locks a CS) + // This contains the flags and bitmasks, too + const AString & ChunkData = a_Serializer.Serialize(cChunkDataSerializer::RELEASE_1_9_0, a_ChunkX, a_ChunkZ); + + cCSLock Lock(m_CSPacket); + SendData(ChunkData.data(), ChunkData.size()); +} + + + + + +void cProtocol190::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x49); // Collect Item packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteVarInt32(a_Player.GetUniqueID()); +} + + + + + +void cProtocol190::SendDestroyEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x30); // Destroy Entities packet + Pkt.WriteVarInt32(1); + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol190::SendDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + ASSERT(m_State == 3); // In game mode? + cPacketizer Pkt(*this, 0x40); // Set passangers packet + Pkt.WriteVarInt32(a_PreviousVehicle.GetUniqueID()); + Pkt.WriteVarInt32(0); // No passangers +} + + + + + +void cProtocol190::SendDisconnect(const AString & a_Reason) +{ + switch (m_State) + { + case 2: + { + // During login: + cPacketizer Pkt(*this, 0); + Pkt.WriteString(Printf("{\"text\":\"%s\"}", EscapeString(a_Reason).c_str())); + break; + } + case 3: + { + // In-game: + cPacketizer Pkt(*this, 0x1a); + Pkt.WriteString(Printf("{\"text\":\"%s\"}", EscapeString(a_Reason).c_str())); + break; + } + } +} + + + + + +void cProtocol190::SendEditSign(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2a); // Sign Editor Open packet + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); +} + + + + + +void cProtocol190::SendEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4c); // Entity Effect packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_EffectID)); + Pkt.WriteBEUInt8(static_cast(a_Amplifier)); + Pkt.WriteVarInt32(static_cast(a_Duration)); + Pkt.WriteBool(false); // Hide particles +} + + + + + +void cProtocol190::SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3c); // Entity Equipment packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // Needs to be adjusted due to the insertion of offhand at slot 1 + if (a_SlotNum > 0) + { + a_SlotNum++; + } + Pkt.WriteVarInt32(a_SlotNum); + WriteItem(Pkt, a_Item); +} + + + + + +void cProtocol190::SendEntityHeadLook(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x34); // Entity Head Look packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteByteAngle(a_Entity.GetHeadYaw()); +} + + + + + +void cProtocol190::SendEntityLook(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x27); // Entity Look packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol190::SendEntityMetadata(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x39); // Entity Metadata packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + WriteEntityMetadata(Pkt, a_Entity); + Pkt.WriteBEUInt8(0xff); // The termination byte +} + + + + + +void cProtocol190::SendEntityProperties(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4b); // Entity Properties packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + WriteEntityProperties(Pkt, a_Entity); +} + + + + + +void cProtocol190::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x25); // Entity Relative Move packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // TODO: 1.9 changed these from chars to shorts, meaning that there can be more percision and data. Other code needs to be updated for that. + Pkt.WriteBEInt16(a_RelX * 128); + Pkt.WriteBEInt16(a_RelY * 128); + Pkt.WriteBEInt16(a_RelZ * 128); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol190::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x26); // Entity Look And Relative Move packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // TODO: 1.9 changed these from chars to shorts, meaning that there can be more percision and data. Other code needs to be updated for that. + Pkt.WriteBEInt16(a_RelX * 128); + Pkt.WriteBEInt16(a_RelY * 128); + Pkt.WriteBEInt16(a_RelZ * 128); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol190::SendEntityStatus(const cEntity & a_Entity, char a_Status) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1b); // Entity Status packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt8(a_Status); +} + + + + + +void cProtocol190::SendEntityVelocity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3b); // Entity Velocity packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // 400 = 8000 / 20 ... Conversion from our speed in m / s to 8000 m / tick + Pkt.WriteBEInt16(static_cast(a_Entity.GetSpeedX() * 400)); + Pkt.WriteBEInt16(static_cast(a_Entity.GetSpeedY() * 400)); + Pkt.WriteBEInt16(static_cast(a_Entity.GetSpeedZ() * 400)); +} + + + + + +void cProtocol190::SendExplosion(double a_BlockX, double a_BlockY, double a_BlockZ, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d & a_PlayerMotion) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1c); // Explosion packet + Pkt.WriteBEFloat(static_cast(a_BlockX)); + Pkt.WriteBEFloat(static_cast(a_BlockY)); + Pkt.WriteBEFloat(static_cast(a_BlockZ)); + Pkt.WriteBEFloat(static_cast(a_Radius)); + Pkt.WriteBEUInt32(static_cast(a_BlocksAffected.size())); + for (cVector3iArray::const_iterator itr = a_BlocksAffected.begin(), end = a_BlocksAffected.end(); itr != end; ++itr) + { + Pkt.WriteBEInt8(static_cast(itr->x)); + Pkt.WriteBEInt8(static_cast(itr->y)); + Pkt.WriteBEInt8(static_cast(itr->z)); + } // for itr - a_BlockAffected[] + Pkt.WriteBEFloat(static_cast(a_PlayerMotion.x)); + Pkt.WriteBEFloat(static_cast(a_PlayerMotion.y)); + Pkt.WriteBEFloat(static_cast(a_PlayerMotion.z)); +} + + + + + +void cProtocol190::SendGameMode(eGameMode a_GameMode) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1e); // Change Game State packet + Pkt.WriteBEUInt8(3); // Reason: Change game mode + Pkt.WriteBEFloat(static_cast(a_GameMode)); // The protocol really represents the value with a float! +} + + + + + +void cProtocol190::SendHealth(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3e); // Update Health packet + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteBEFloat(static_cast(Player->GetHealth())); + Pkt.WriteVarInt32(static_cast(Player->GetFoodLevel())); + Pkt.WriteBEFloat(static_cast(Player->GetFoodSaturationLevel())); +} + + + + + +void cProtocol190::SendHideTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(3); // Hide title +} + + + + + +void cProtocol190::SendInventorySlot(char a_WindowID, short a_SlotNum, const cItem & a_Item) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x16); // Set Slot packet + Pkt.WriteBEInt8(a_WindowID); + Pkt.WriteBEInt16(a_SlotNum); + WriteItem(Pkt, a_Item); +} + + + + + +void cProtocol190::SendKeepAlive(UInt32 a_PingID) +{ + // Drop the packet if the protocol is not in the Game state yet (caused a client crash): + if (m_State != 3) + { + LOGWARNING("Trying to send a KeepAlive packet to a player who's not yet fully logged in (%d). The protocol class prevented the packet.", m_State); + return; + } + + cPacketizer Pkt(*this, 0x1f); // Keep Alive packet + Pkt.WriteVarInt32(a_PingID); +} + + + + + +void cProtocol190::SendLogin(const cPlayer & a_Player, const cWorld & a_World) +{ + // Send the Join Game packet: + { + cServer * Server = cRoot::Get()->GetServer(); + cPacketizer Pkt(*this, 0x23); // Join Game packet + Pkt.WriteBEUInt32(a_Player.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_Player.GetEffectiveGameMode()) | (Server->IsHardcore() ? 0x08 : 0)); // Hardcore flag bit 4 + Pkt.WriteBEInt8(static_cast(a_World.GetDimension())); + Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal) + Pkt.WriteBEUInt8(static_cast(Clamp(Server->GetMaxPlayers(), 0, 255))); + 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, 0x43); // Spawn Position packet + Pkt.WritePosition64(FloorC(a_World.GetSpawnX()), FloorC(a_World.GetSpawnY()), FloorC(a_World.GetSpawnZ())); + } + + // Send the server difficulty: + { + cPacketizer Pkt(*this, 0x0d); // Server difficulty packet + Pkt.WriteBEInt8(1); + } + + // Send player abilities: + SendPlayerAbilities(); +} + + + + +void cProtocol190::SendLoginSuccess(void) +{ + ASSERT(m_State == 2); // State: login? + + // Enable compression: + { + cPacketizer Pkt(*this, 0x03); // Set compression packet + Pkt.WriteVarInt32(256); + } + + m_State = 3; // State = Game + + { + cPacketizer Pkt(*this, 0x02); // Login success packet + Pkt.WriteString(cMojangAPI::MakeUUIDDashed(m_Client->GetUUID())); + Pkt.WriteString(m_Client->GetUsername()); + } +} + + + + + +void cProtocol190::SendPaintingSpawn(const cPainting & a_Painting) +{ + ASSERT(m_State == 3); // In game mode? + double PosX = a_Painting.GetPosX(); + double PosY = a_Painting.GetPosY(); + double PosZ = a_Painting.GetPosZ(); + + cPacketizer Pkt(*this, 0x04); // Spawn Painting packet + Pkt.WriteVarInt32(a_Painting.GetUniqueID()); + // TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now. + Pkt.WriteBEUInt64(0); + Pkt.WriteBEUInt64(a_Painting.GetUniqueID()); + Pkt.WriteString(a_Painting.GetName().c_str()); + Pkt.WritePosition64(static_cast(PosX), static_cast(PosY), static_cast(PosZ)); + Pkt.WriteBEInt8(static_cast(a_Painting.GetProtocolFacing())); +} + + + + + +void cProtocol190::SendMapData(const cMap & a_Map, int a_DataStartX, int a_DataStartY) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x24); // Map packet + Pkt.WriteVarInt32(a_Map.GetID()); + Pkt.WriteBEUInt8(static_cast(a_Map.GetScale())); + + Pkt.WriteBool(true); + Pkt.WriteVarInt32(static_cast(a_Map.GetDecorators().size())); + for (const auto & Decorator : a_Map.GetDecorators()) + { + Pkt.WriteBEUInt8(static_cast((static_cast(Decorator.GetType()) << 4) | (Decorator.GetRot() & 0xF))); + Pkt.WriteBEUInt8(static_cast(Decorator.GetPixelX())); + Pkt.WriteBEUInt8(static_cast(Decorator.GetPixelZ())); + } + + Pkt.WriteBEUInt8(128); + Pkt.WriteBEUInt8(128); + Pkt.WriteBEUInt8(static_cast(a_DataStartX)); + Pkt.WriteBEUInt8(static_cast(a_DataStartY)); + Pkt.WriteVarInt32(static_cast(a_Map.GetData().size())); + for (auto itr = a_Map.GetData().cbegin(); itr != a_Map.GetData().cend(); ++itr) + { + Pkt.WriteBEUInt8(*itr); + } +} + + + + + +void cProtocol190::SendPickupSpawn(const cPickup & a_Pickup) +{ + ASSERT(m_State == 3); // In game mode? + + { + cPacketizer Pkt(*this, 0x00); // Spawn Object packet + Pkt.WriteVarInt32(a_Pickup.GetUniqueID()); + // TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now. + Pkt.WriteBEUInt64(0); + Pkt.WriteBEUInt64(a_Pickup.GetUniqueID()); + Pkt.WriteBEUInt8(2); // Type = Pickup + Pkt.WriteBEDouble(a_Pickup.GetPosX()); + Pkt.WriteBEDouble(a_Pickup.GetPosY()); + Pkt.WriteBEDouble(a_Pickup.GetPosZ()); + Pkt.WriteByteAngle(a_Pickup.GetYaw()); + Pkt.WriteByteAngle(a_Pickup.GetPitch()); + Pkt.WriteBEInt32(0); // No object data + Pkt.WriteBEInt16(0); // No velocity + Pkt.WriteBEInt16(0); + Pkt.WriteBEInt16(0); + } + + { + cPacketizer Pkt(*this, 0x39); // Entity Metadata packet + Pkt.WriteVarInt32(a_Pickup.GetUniqueID()); + Pkt.WriteBEUInt8(5); // Index 5: Item + Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(Pkt, a_Pickup.GetItem()); + Pkt.WriteBEUInt8(0xff); // End of metadata + } +} + + + + + +void cProtocol190::SendPlayerAbilities(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2b); // Player Abilities packet + Byte Flags = 0; + cPlayer * Player = m_Client->GetPlayer(); + if (Player->IsGameModeCreative()) + { + Flags |= 0x01; + Flags |= 0x08; // Godmode, used for creative + } + if (Player->IsFlying()) + { + Flags |= 0x02; + } + if (Player->CanFly()) + { + Flags |= 0x04; + } + Pkt.WriteBEUInt8(Flags); + Pkt.WriteBEFloat(static_cast(0.05 * Player->GetFlyingMaxSpeed())); + Pkt.WriteBEFloat(static_cast(0.1 * Player->GetNormalMaxSpeed())); +} + + + + + +void cProtocol190::SendEntityAnimation(const cEntity & a_Entity, char a_Animation) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x06); // Animation packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt8(a_Animation); +} + + + + + +void cProtocol190::SendParticleEffect(const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount) +{ + ASSERT(m_State == 3); // In game mode? + int ParticleID = GetParticleID(a_ParticleName); + + cPacketizer Pkt(*this, 0x22); // Particle effect packet + Pkt.WriteBEInt32(ParticleID); + Pkt.WriteBool(false); + Pkt.WriteBEFloat(a_SrcX); + Pkt.WriteBEFloat(a_SrcY); + Pkt.WriteBEFloat(a_SrcZ); + Pkt.WriteBEFloat(a_OffsetX); + Pkt.WriteBEFloat(a_OffsetY); + Pkt.WriteBEFloat(a_OffsetZ); + Pkt.WriteBEFloat(a_ParticleData); + Pkt.WriteBEInt32(a_ParticleAmount); +} + + + + + +void cProtocol190::SendParticleEffect(const AString & a_ParticleName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array a_Data) +{ + ASSERT(m_State == 3); // In game mode? + int ParticleID = GetParticleID(a_ParticleName); + + cPacketizer Pkt(*this, 0x22); // Particle effect packet + Pkt.WriteBEInt32(ParticleID); + Pkt.WriteBool(false); + Pkt.WriteBEFloat(a_Src.x); + Pkt.WriteBEFloat(a_Src.y); + Pkt.WriteBEFloat(a_Src.z); + Pkt.WriteBEFloat(a_Offset.x); + Pkt.WriteBEFloat(a_Offset.y); + Pkt.WriteBEFloat(a_Offset.z); + Pkt.WriteBEFloat(a_ParticleData); + Pkt.WriteBEInt32(a_ParticleAmount); + switch (ParticleID) + { + // iconcrack + case 36: + { + Pkt.WriteVarInt32(static_cast(a_Data[0])); + Pkt.WriteVarInt32(static_cast(a_Data[1])); + break; + } + // blockcrack + // blockdust + case 37: + case 38: + { + Pkt.WriteVarInt32(static_cast(a_Data[0])); + break; + } + default: + { + break; + } + } +} + + + + + +void cProtocol190::SendPlayerListAddPlayer(const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2d); // Playerlist Item packet + Pkt.WriteVarInt32(0); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteString(a_Player.GetPlayerListName()); + + const Json::Value & Properties = a_Player.GetClientHandle()->GetProperties(); + Pkt.WriteVarInt32(Properties.size()); + for (auto & Node : Properties) + { + Pkt.WriteString(Node.get("name", "").asString()); + Pkt.WriteString(Node.get("value", "").asString()); + AString Signature = Node.get("signature", "").asString(); + if (Signature.empty()) + { + Pkt.WriteBool(false); + } + else + { + Pkt.WriteBool(true); + Pkt.WriteString(Signature); + } + } + + Pkt.WriteVarInt32(static_cast(a_Player.GetGameMode())); + Pkt.WriteVarInt32(static_cast(a_Player.GetClientHandle()->GetPing())); + Pkt.WriteBool(false); +} + + + + + +void cProtocol190::SendPlayerListRemovePlayer(const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2d); // Playerlist Item packet + Pkt.WriteVarInt32(4); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); +} + + + + + +void cProtocol190::SendPlayerListUpdateGameMode(const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2d); // Playerlist Item packet + Pkt.WriteVarInt32(1); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteVarInt32(static_cast(a_Player.GetGameMode())); +} + + + + + +void cProtocol190::SendPlayerListUpdatePing(const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + auto ClientHandle = a_Player.GetClientHandlePtr(); + if (ClientHandle != nullptr) + { + cPacketizer Pkt(*this, 0x2d); // Playerlist Item packet + Pkt.WriteVarInt32(2); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteVarInt32(static_cast(ClientHandle->GetPing())); + } +} + + + + + +void cProtocol190::SendPlayerListUpdateDisplayName(const cPlayer & a_Player, const AString & a_CustomName) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2d); // Playerlist Item packet + Pkt.WriteVarInt32(3); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); + + if (a_CustomName.empty()) + { + Pkt.WriteBool(false); + } + else + { + Pkt.WriteBool(true); + Pkt.WriteString(Printf("{\"text\":\"%s\"}", a_CustomName.c_str())); + } +} + + + + + +void cProtocol190::SendPlayerMaxSpeed(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4b); // Entity Properties + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteVarInt32(Player->GetUniqueID()); + Pkt.WriteBEInt32(1); // Count + Pkt.WriteString("generic.movementSpeed"); + // The default game speed is 0.1, multiply that value by the relative speed: + Pkt.WriteBEDouble(0.1 * Player->GetNormalMaxSpeed()); + if (Player->IsSprinting()) + { + Pkt.WriteVarInt32(1); // Modifier count + Pkt.WriteBEUInt64(0x662a6b8dda3e4c1c); + Pkt.WriteBEUInt64(0x881396ea6097278d); // UUID of the modifier + Pkt.WriteBEDouble(Player->GetSprintingMaxSpeed() - Player->GetNormalMaxSpeed()); + Pkt.WriteBEUInt8(2); + } + else + { + Pkt.WriteVarInt32(0); // Modifier count + } +} + + + + + +void cProtocol190::SendPlayerMoveLook(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2e); // Player Position And Look packet + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteBEDouble(Player->GetPosX()); + Pkt.WriteBEDouble(Player->GetPosY()); + Pkt.WriteBEDouble(Player->GetPosZ()); + Pkt.WriteBEFloat(static_cast(Player->GetYaw())); + Pkt.WriteBEFloat(static_cast(Player->GetPitch())); + Pkt.WriteBEUInt8(0); + Pkt.WriteVarInt32(0); // Teleport ID - not implemented here +} + + + + + +void cProtocol190::SendPlayerPosition(void) +{ + // There is no dedicated packet for this, send the whole thing: + SendPlayerMoveLook(); +} + + + + + +void cProtocol190::SendPlayerSpawn(const cPlayer & a_Player) +{ + // Called to spawn another player for the client + cPacketizer Pkt(*this, 0x05); // Spawn Player packet + Pkt.WriteVarInt32(a_Player.GetUniqueID()); + Pkt.WriteUUID(cMojangAPI::MakeUUIDShort(a_Player.GetUUID())); + Pkt.WriteBEDouble(a_Player.GetPosX()); + Pkt.WriteBEDouble(a_Player.GetPosY() + 0.001); // The "+ 0.001" is there because otherwise the player falls through the block they were standing on. + Pkt.WriteBEDouble(a_Player.GetPosZ()); + Pkt.WriteByteAngle(a_Player.GetYaw()); + Pkt.WriteByteAngle(a_Player.GetPitch()); + Pkt.WriteBEUInt8(6); // Start metadata - Index 6: Health + Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + Pkt.WriteBEFloat(static_cast(a_Player.GetHealth())); + Pkt.WriteBEUInt8(2); // Index 2: Custom name + Pkt.WriteBEUInt8(METADATA_TYPE_STRING); + Pkt.WriteString(a_Player.GetName()); + Pkt.WriteBEUInt8(0xff); // Metadata: end +} + + + + + +void cProtocol190::SendPluginMessage(const AString & a_Channel, const AString & a_Message) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x18); // Plugin message packet + Pkt.WriteString(a_Channel); + Pkt.WriteBuf(a_Message.data(), a_Message.size()); +} + + + + + +void cProtocol190::SendRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x31); // Remove entity effect packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_EffectID)); +} + + + + + +void cProtocol190::SendResetTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(4); // Reset title +} + + + + + +void cProtocol190::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) +{ + if ((m_LastSentDimension == a_Dimension) && !a_ShouldIgnoreDimensionChecks) + { + // Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do (unless we are respawning from death) + return; + } + + cPacketizer Pkt(*this, 0x33); // Respawn packet + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteBEInt32(static_cast(a_Dimension)); + Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal) + Pkt.WriteBEUInt8(static_cast(Player->GetEffectiveGameMode())); + Pkt.WriteString("default"); + m_LastSentDimension = a_Dimension; +} + + + + + +void cProtocol190::SendExperience(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3d); // Experience Packet + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteBEFloat(Player->GetXpPercentage()); + Pkt.WriteVarInt32(static_cast(Player->GetXpLevel())); + Pkt.WriteVarInt32(static_cast(Player->GetCurrentXp())); +} + + + + + +void cProtocol190::SendExperienceOrb(const cExpOrb & a_ExpOrb) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x01); // Spawn experience orb packet + Pkt.WriteVarInt32(a_ExpOrb.GetUniqueID()); + Pkt.WriteBEDouble(a_ExpOrb.GetPosX()); + Pkt.WriteBEDouble(a_ExpOrb.GetPosY()); + Pkt.WriteBEDouble(a_ExpOrb.GetPosZ()); + Pkt.WriteBEInt16(static_cast(a_ExpOrb.GetReward())); +} + + + + + +void cProtocol190::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3f); // Scoreboard objective packet + Pkt.WriteString(a_Name); + Pkt.WriteBEUInt8(a_Mode); + if ((a_Mode == 0) || (a_Mode == 2)) + { + Pkt.WriteString(a_DisplayName); + Pkt.WriteString("integer"); + } +} + + + + + +void cProtocol190::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x42); // Update score packet + Pkt.WriteString(a_Player); + Pkt.WriteBEUInt8(a_Mode); + Pkt.WriteString(a_Objective); + + if (a_Mode != 1) + { + Pkt.WriteVarInt32(static_cast(a_Score)); + } +} + + + + + +void cProtocol190::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x38); // Display scoreboard packet + Pkt.WriteBEUInt8(static_cast(a_Display)); + Pkt.WriteString(a_Objective); +} + + + + + +void cProtocol190::SendSetSubTitle(const cCompositeChat & a_SubTitle) +{ + SendSetRawSubTitle(a_SubTitle.CreateJsonString(false)); +} + + + + + +void cProtocol190::SendSetRawSubTitle(const AString & a_SubTitle) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(1); // Set subtitle + + Pkt.WriteString(a_SubTitle); +} + + + + + +void cProtocol190::SendSetTitle(const cCompositeChat & a_Title) +{ + SendSetRawTitle(a_Title.CreateJsonString(false)); +} + + + + + +void cProtocol190::SendSetRawTitle(const AString & a_Title) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(0); // Set title + + Pkt.WriteString(a_Title); +} + + + + + +void cProtocol190::SendSoundEffect(const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x19); // Named sound effect packet + Pkt.WriteString(a_SoundName); + Pkt.WriteVarInt32(0); // Master sound category (may want to be changed to a parameter later) + Pkt.WriteBEInt32(static_cast(a_X * 8.0)); + Pkt.WriteBEInt32(static_cast(a_Y * 8.0)); + Pkt.WriteBEInt32(static_cast(a_Z * 8.0)); + Pkt.WriteBEFloat(a_Volume); + Pkt.WriteBEUInt8(static_cast(a_Pitch * 63)); +} + + + + + +void cProtocol190::SendSoundParticleEffect(const EffectID a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x21); // Effect packet + Pkt.WriteBEInt32(static_cast(a_EffectID)); + Pkt.WritePosition64(a_SrcX, a_SrcY, a_SrcZ); + Pkt.WriteBEInt32(a_Data); + Pkt.WriteBool(false); +} + + + + + +void cProtocol190::SendSpawnFallingBlock(const cFallingBlock & a_FallingBlock) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x00); // Spawn Object packet + Pkt.WriteVarInt32(a_FallingBlock.GetUniqueID()); + // TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now. + Pkt.WriteBEUInt64(0); + Pkt.WriteBEUInt64(a_FallingBlock.GetUniqueID()); + Pkt.WriteBEUInt8(70); // Falling block + Pkt.WriteBEDouble(a_FallingBlock.GetPosX()); + Pkt.WriteBEDouble(a_FallingBlock.GetPosY()); + Pkt.WriteBEDouble(a_FallingBlock.GetPosZ()); + Pkt.WriteByteAngle(a_FallingBlock.GetYaw()); + Pkt.WriteByteAngle(a_FallingBlock.GetPitch()); + Pkt.WriteBEInt32(static_cast(a_FallingBlock.GetBlockType()) | (static_cast(a_FallingBlock.GetBlockMeta()) << 12)); + Pkt.WriteBEInt16(static_cast(a_FallingBlock.GetSpeedX() * 400)); + Pkt.WriteBEInt16(static_cast(a_FallingBlock.GetSpeedY() * 400)); + Pkt.WriteBEInt16(static_cast(a_FallingBlock.GetSpeedZ() * 400)); +} + + + + + +void cProtocol190::SendSpawnMob(const cMonster & a_Mob) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x03); // Spawn Mob packet + Pkt.WriteVarInt32(a_Mob.GetUniqueID()); + // TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now. + Pkt.WriteBEUInt64(0); + Pkt.WriteBEUInt64(a_Mob.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_Mob.GetMobType())); + Pkt.WriteBEDouble(a_Mob.GetPosX()); + Pkt.WriteBEDouble(a_Mob.GetPosY()); + Pkt.WriteBEDouble(a_Mob.GetPosZ()); + Pkt.WriteByteAngle(a_Mob.GetPitch()); + Pkt.WriteByteAngle(a_Mob.GetHeadYaw()); + Pkt.WriteByteAngle(a_Mob.GetYaw()); + Pkt.WriteBEInt16(static_cast(a_Mob.GetSpeedX() * 400)); + Pkt.WriteBEInt16(static_cast(a_Mob.GetSpeedY() * 400)); + Pkt.WriteBEInt16(static_cast(a_Mob.GetSpeedZ() * 400)); + WriteEntityMetadata(Pkt, a_Mob); + Pkt.WriteBEUInt8(0xff); // Metadata terminator +} + + + + + +void cProtocol190::SendSpawnObject(const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) +{ + ASSERT(m_State == 3); // In game mode? + double PosX = a_Entity.GetPosX(); + double PosZ = a_Entity.GetPosZ(); + double Yaw = a_Entity.GetYaw(); + if (a_ObjectType == 71) + { + FixItemFramePositions(a_ObjectData, PosX, PosZ, Yaw); + } + + cPacketizer Pkt(*this, 0x00); // Spawn Object packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now. + Pkt.WriteBEUInt64(0); + Pkt.WriteBEUInt64(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_ObjectType)); + Pkt.WriteBEDouble(PosX); + Pkt.WriteBEDouble(a_Entity.GetPosY()); + Pkt.WriteBEDouble(PosZ); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteByteAngle(Yaw); + Pkt.WriteBEInt32(a_ObjectData); + Pkt.WriteBEInt16(static_cast(a_Entity.GetSpeedX() * 400)); + Pkt.WriteBEInt16(static_cast(a_Entity.GetSpeedY() * 400)); + Pkt.WriteBEInt16(static_cast(a_Entity.GetSpeedZ() * 400)); +} + + + + + +void cProtocol190::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x00); // Spawn Object packet + Pkt.WriteVarInt32(a_Vehicle.GetUniqueID()); + // TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now. + Pkt.WriteBEUInt64(0); + Pkt.WriteBEUInt64(a_Vehicle.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_VehicleType)); + Pkt.WriteBEDouble(a_Vehicle.GetPosX()); + Pkt.WriteBEDouble(a_Vehicle.GetPosY()); + Pkt.WriteBEDouble(a_Vehicle.GetPosZ()); + Pkt.WriteByteAngle(a_Vehicle.GetPitch()); + Pkt.WriteByteAngle(a_Vehicle.GetYaw()); + Pkt.WriteBEInt32(a_VehicleSubType); + Pkt.WriteBEInt16(static_cast(a_Vehicle.GetSpeedX() * 400)); + Pkt.WriteBEInt16(static_cast(a_Vehicle.GetSpeedY() * 400)); + Pkt.WriteBEInt16(static_cast(a_Vehicle.GetSpeedZ() * 400)); +} + + + + + +void cProtocol190::SendStatistics(const cStatManager & a_Manager) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x07); // Statistics packet + Pkt.WriteVarInt32(statCount); // TODO 2014-05-11 xdot: Optimization: Send "dirty" statistics only + + size_t Count = static_cast(statCount); + for (size_t i = 0; i < Count; ++i) + { + StatValue Value = a_Manager.GetValue(static_cast(i)); + const AString & StatName = cStatInfo::GetName(static_cast(i)); + + Pkt.WriteString(StatName); + Pkt.WriteVarInt32(static_cast(Value)); + } +} + + + + + +void cProtocol190::SendTabCompletionResults(const AStringVector & a_Results) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0e); // Tab-Complete packet + Pkt.WriteVarInt32(static_cast(a_Results.size())); + + for (AStringVector::const_iterator itr = a_Results.begin(), end = a_Results.end(); itr != end; ++itr) + { + Pkt.WriteString(*itr); + } +} + + + + + +void cProtocol190::SendTeleportEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4a); // Entity teleport packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEDouble(a_Entity.GetPosX()); + Pkt.WriteBEDouble(a_Entity.GetPosY()); + Pkt.WriteBEDouble(a_Entity.GetPosZ()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol190::SendThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x02); // Spawn Global Entity packet + Pkt.WriteVarInt32(0); // EntityID = 0, always + Pkt.WriteBEUInt8(1); // Type = Thunderbolt + Pkt.WriteBEDouble(a_BlockX); + Pkt.WriteBEDouble(a_BlockY); + Pkt.WriteBEDouble(a_BlockZ); +} + + + + + +void cProtocol190::SendTitleTimes(int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(2); // Set title display times + + Pkt.WriteBEInt32(a_FadeInTicks); + Pkt.WriteBEInt32(a_DisplayTicks); + Pkt.WriteBEInt32(a_FadeOutTicks); +} + + + + + +void cProtocol190::SendTimeUpdate(Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) +{ + ASSERT(m_State == 3); // In game mode? + if (!a_DoDaylightCycle) + { + // When writing a "-" before the number the client ignores it but it will stop the client-side time expiration. + a_TimeOfDay = std::min(-a_TimeOfDay, -1LL); + } + + cPacketizer Pkt(*this, 0x44); // Time update packet + Pkt.WriteBEInt64(a_WorldAge); + Pkt.WriteBEInt64(a_TimeOfDay); +} + + + + + +void cProtocol190::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1d); // Unload chunk packet + Pkt.WriteBEInt32(a_ChunkX); + Pkt.WriteBEInt32(a_ChunkZ); +} + + + + +void cProtocol190::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x09); // Update tile entity packet + Pkt.WritePosition64(a_BlockEntity.GetPosX(), a_BlockEntity.GetPosY(), a_BlockEntity.GetPosZ()); + + Byte Action = 0; + switch (a_BlockEntity.GetBlockType()) + { + case E_BLOCK_MOB_SPAWNER: Action = 1; break; // Update mob spawner spinny mob thing + case E_BLOCK_COMMAND_BLOCK: Action = 2; break; // Update command block text + case E_BLOCK_BEACON: Action = 3; break; // Update beacon entity + case E_BLOCK_HEAD: Action = 4; break; // Update Mobhead entity + case E_BLOCK_FLOWER_POT: Action = 5; break; // Update flower pot + default: ASSERT(!"Unhandled or unimplemented BlockEntity update request!"); break; + } + Pkt.WriteBEUInt8(Action); + + WriteBlockEntity(Pkt, a_BlockEntity); +} + + + + + +void cProtocol190::SendUpdateSign(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x46); // Update sign packet + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); + + Json::StyledWriter JsonWriter; + AString Lines[] = { a_Line1, a_Line2, a_Line3, a_Line4 }; + for (size_t i = 0; i < ARRAYCOUNT(Lines); i++) + { + Json::Value RootValue; + RootValue["text"] = Lines[i]; + Pkt.WriteString(JsonWriter.write(RootValue).c_str()); + } +} + + + + + +void cProtocol190::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2f); // Use bed + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); +} + + + + + +void cProtocol190::SendWeather(eWeather a_Weather) +{ + ASSERT(m_State == 3); // In game mode? + + { + cPacketizer Pkt(*this, 0x1e); // Change Game State packet + Pkt.WriteBEUInt8((a_Weather == wSunny) ? 1 : 2); // End rain / begin rain + Pkt.WriteBEFloat(0); // Unused for weather + } + + // TODO: Fade effect, somehow +} + + + + + +void cProtocol190::SendWholeInventory(const cWindow & a_Window) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x14); // Window Items packet + Pkt.WriteBEInt8(a_Window.GetWindowID()); + Pkt.WriteBEInt16(static_cast(a_Window.GetNumSlots())); + cItems Slots; + a_Window.GetSlots(*(m_Client->GetPlayer()), Slots); + for (cItems::const_iterator itr = Slots.begin(), end = Slots.end(); itr != end; ++itr) + { + WriteItem(Pkt, *itr); + } // for itr - Slots[] +} + + + + + +void cProtocol190::SendWindowClose(const cWindow & a_Window) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x12); // Close window packet + Pkt.WriteBEInt8(a_Window.GetWindowID()); +} + + + + + +void cProtocol190::SendWindowOpen(const cWindow & a_Window) +{ + ASSERT(m_State == 3); // In game mode? + + if (a_Window.GetWindowType() < 0) + { + // Do not send this packet for player inventory windows + return; + } + + cPacketizer Pkt(*this, 0x13); // Open window packet + Pkt.WriteBEInt8(a_Window.GetWindowID()); + Pkt.WriteString(a_Window.GetWindowTypeName()); + Pkt.WriteString(Printf("{\"text\":\"%s\"}", a_Window.GetWindowTitle().c_str())); + + switch (a_Window.GetWindowType()) + { + case cWindow::wtWorkbench: + case cWindow::wtEnchantment: + case cWindow::wtAnvil: + { + Pkt.WriteBEInt8(0); + break; + } + default: + { + Pkt.WriteBEInt8(static_cast(a_Window.GetNumNonInventorySlots())); + break; + } + } + + if (a_Window.GetWindowType() == cWindow::wtAnimalChest) + { + Pkt.WriteBEInt32(0); // TODO: The animal's EntityID + } +} + + + + + +void cProtocol190::SendWindowProperty(const cWindow & a_Window, short a_Property, short a_Value) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x15); // Window Property packet + Pkt.WriteBEInt8(a_Window.GetWindowID()); + Pkt.WriteBEInt16(a_Property); + Pkt.WriteBEInt16(a_Value); +} + + + + + +bool cProtocol190::CompressPacket(const AString & a_Packet, AString & a_CompressedData) +{ + // Compress the data: + char CompressedData[MAX_COMPRESSED_PACKET_LEN]; + + uLongf CompressedSize = compressBound(static_cast(a_Packet.size())); + if (CompressedSize >= MAX_COMPRESSED_PACKET_LEN) + { + ASSERT(!"Too high packet size."); + return false; + } + + int Status = compress2( + reinterpret_cast(CompressedData), &CompressedSize, + reinterpret_cast(a_Packet.data()), static_cast(a_Packet.size()), Z_DEFAULT_COMPRESSION + ); + if (Status != Z_OK) + { + return false; + } + + AString LengthData; + cByteBuffer Buffer(20); + Buffer.WriteVarInt32(static_cast(a_Packet.size())); + Buffer.ReadAll(LengthData); + Buffer.CommitRead(); + + Buffer.WriteVarInt32(static_cast(CompressedSize + LengthData.size())); + Buffer.WriteVarInt32(static_cast(a_Packet.size())); + Buffer.ReadAll(LengthData); + Buffer.CommitRead(); + + a_CompressedData.clear(); + a_CompressedData.reserve(LengthData.size() + CompressedSize); + a_CompressedData.append(LengthData.data(), LengthData.size()); + a_CompressedData.append(CompressedData, CompressedSize); + return true; +} + + + + + +int cProtocol190::GetParticleID(const AString & a_ParticleName) +{ + static bool IsInitialized = false; + static std::map ParticleMap; + if (!IsInitialized) + { + // Initialize the ParticleMap: + ParticleMap["explode"] = 0; + ParticleMap["largeexplode"] = 1; + ParticleMap["hugeexplosion"] = 2; + ParticleMap["fireworksspark"] = 3; + ParticleMap["bubble"] = 4; + ParticleMap["splash"] = 5; + ParticleMap["wake"] = 6; + ParticleMap["suspended"] = 7; + ParticleMap["depthsuspend"] = 8; + ParticleMap["crit"] = 9; + ParticleMap["magiccrit"] = 10; + ParticleMap["smoke"] = 11; + ParticleMap["largesmoke"] = 12; + ParticleMap["spell"] = 13; + ParticleMap["instantspell"] = 14; + ParticleMap["mobspell"] = 15; + ParticleMap["mobspellambient"] = 16; + ParticleMap["witchmagic"] = 17; + ParticleMap["dripwater"] = 18; + ParticleMap["driplava"] = 19; + ParticleMap["angryvillager"] = 20; + ParticleMap["happyVillager"] = 21; + ParticleMap["townaura"] = 22; + ParticleMap["note"] = 23; + ParticleMap["portal"] = 24; + ParticleMap["enchantmenttable"] = 25; + ParticleMap["flame"] = 26; + ParticleMap["lava"] = 27; + ParticleMap["footstep"] = 28; + ParticleMap["cloud"] = 29; + ParticleMap["reddust"] = 30; + ParticleMap["snowballpoof"] = 31; + ParticleMap["snowshovel"] = 32; + ParticleMap["slime"] = 33; + ParticleMap["heart"] = 34; + ParticleMap["barrier"] = 35; + ParticleMap["iconcrack"] = 36; + ParticleMap["blockcrack"] = 37; + ParticleMap["blockdust"] = 38; + ParticleMap["droplet"] = 39; + ParticleMap["take"] = 40; + ParticleMap["mobappearance"] = 41; + } + + AString ParticleName = StrToLower(a_ParticleName); + if (ParticleMap.find(ParticleName) == ParticleMap.end()) + { + LOGWARNING("Unknown particle: %s", a_ParticleName.c_str()); + ASSERT(!"Unknown particle"); + return 0; + } + + return ParticleMap[ParticleName]; +} + + + + + +void cProtocol190::FixItemFramePositions(int a_ObjectData, double & a_PosX, double & a_PosZ, double & a_Yaw) +{ + switch (a_ObjectData) + { + case 0: + { + a_PosZ += 1; + a_Yaw = 0; + break; + } + case 1: + { + a_PosX -= 1; + a_Yaw = 90; + break; + } + case 2: + { + a_PosZ -= 1; + a_Yaw = 180; + break; + } + case 3: + { + a_PosX += 1; + a_Yaw = 270; + break; + } + } +} + + + + + +void cProtocol190::AddReceivedData(const char * a_Data, size_t a_Size) +{ + // Write the incoming data into the comm log file: + if (g_ShouldLogCommIn && m_CommLogFile.IsOpen()) + { + if (m_ReceivedData.GetReadableSpace() > 0) + { + AString AllData; + size_t OldReadableSpace = m_ReceivedData.GetReadableSpace(); + m_ReceivedData.ReadAll(AllData); + m_ReceivedData.ResetRead(); + m_ReceivedData.SkipRead(m_ReceivedData.GetReadableSpace() - OldReadableSpace); + ASSERT(m_ReceivedData.GetReadableSpace() == OldReadableSpace); + AString Hex; + CreateHexDump(Hex, AllData.data(), AllData.size(), 16); + m_CommLogFile.Printf("Incoming data, " SIZE_T_FMT " (0x" SIZE_T_FMT_HEX ") unparsed bytes already present in buffer:\n%s\n", + AllData.size(), AllData.size(), Hex.c_str() + ); + } + AString Hex; + CreateHexDump(Hex, a_Data, a_Size, 16); + m_CommLogFile.Printf("Incoming data: %u (0x%x) bytes: \n%s\n", + static_cast(a_Size), static_cast(a_Size), Hex.c_str() + ); + m_CommLogFile.Flush(); + } + + if (!m_ReceivedData.Write(a_Data, a_Size)) + { + // Too much data in the incoming queue, report to caller: + m_Client->PacketBufferFull(); + return; + } + + // Handle all complete packets: + for (;;) + { + UInt32 PacketLen; + if (!m_ReceivedData.ReadVarInt(PacketLen)) + { + // Not enough data + m_ReceivedData.ResetRead(); + break; + } + if (!m_ReceivedData.CanReadBytes(PacketLen)) + { + // The full packet hasn't been received yet + m_ReceivedData.ResetRead(); + break; + } + + // Check packet for compression: + UInt32 UncompressedSize = 0; + AString UncompressedData; + if (m_State == 3) + { + UInt32 NumBytesRead = static_cast(m_ReceivedData.GetReadableSpace()); + + if (!m_ReceivedData.ReadVarInt(UncompressedSize)) + { + m_Client->Kick("Compression packet incomplete"); + return; + } + + NumBytesRead -= static_cast(m_ReceivedData.GetReadableSpace()); // How many bytes has the UncompressedSize taken up? + ASSERT(PacketLen > NumBytesRead); + PacketLen -= NumBytesRead; + + if (UncompressedSize > 0) + { + // Decompress the data: + AString CompressedData; + VERIFY(m_ReceivedData.ReadString(CompressedData, PacketLen)); + if (InflateString(CompressedData.data(), PacketLen, UncompressedData) != Z_OK) + { + m_Client->Kick("Compression failure"); + return; + } + PacketLen = static_cast(UncompressedData.size()); + if (PacketLen != UncompressedSize) + { + m_Client->Kick("Wrong uncompressed packet size given"); + return; + } + } + } + + // Move the packet payload to a separate cByteBuffer, bb: + cByteBuffer bb(PacketLen + 1); + if (UncompressedSize == 0) + { + // No compression was used, move directly + VERIFY(m_ReceivedData.ReadToByteBuffer(bb, static_cast(PacketLen))); + } + else + { + // Compression was used, move the uncompressed data: + VERIFY(bb.Write(UncompressedData.data(), UncompressedData.size())); + } + m_ReceivedData.CommitRead(); + + UInt32 PacketType; + if (!bb.ReadVarInt(PacketType)) + { + // Not enough data + break; + } + + // Write one NUL extra, so that we can detect over-reads + bb.Write("\0", 1); + + // Log the packet info into the comm log file: + if (g_ShouldLogCommIn && m_CommLogFile.IsOpen()) + { + AString PacketData; + bb.ReadAll(PacketData); + bb.ResetRead(); + bb.ReadVarInt(PacketType); // We have already read the packet type once, it will be there again + ASSERT(PacketData.size() > 0); // We have written an extra NUL, so there had to be at least one byte read + PacketData.resize(PacketData.size() - 1); + AString PacketDataHex; + CreateHexDump(PacketDataHex, PacketData.data(), PacketData.size(), 16); + m_CommLogFile.Printf("Next incoming packet is type %u (0x%x), length %u (0x%x) at state %d. Payload:\n%s\n", + PacketType, PacketType, PacketLen, PacketLen, m_State, PacketDataHex.c_str() + ); + } + + if (!HandlePacket(bb, PacketType)) + { + // Unknown packet, already been reported, but without the length. Log the length here: + LOGWARNING("Protocol 1.9: Unhandled packet: type 0x%x, state %d, length %u", PacketType, m_State, PacketLen); + + #ifdef _DEBUG + // Dump the packet contents into the log: + bb.ResetRead(); + AString Packet; + bb.ReadAll(Packet); + Packet.resize(Packet.size() - 1); // Drop the final NUL pushed there for over-read detection + AString Out; + CreateHexDump(Out, Packet.data(), Packet.size(), 24); + LOGD("Packet contents:\n%s", Out.c_str()); + #endif // _DEBUG + + // Put a message in the comm log: + if (g_ShouldLogCommIn && m_CommLogFile.IsOpen()) + { + m_CommLogFile.Printf("^^^^^^ Unhandled packet ^^^^^^\n\n\n"); + } + + return; + } + + // The packet should have 1 byte left in the buffer - the NUL we had added + if (bb.GetReadableSpace() != 1) + { + // Read more or less than packet length, report as error + LOGWARNING("Protocol 1.9: Wrong number of bytes read for packet 0x%x, state %d. Read " SIZE_T_FMT " bytes, packet contained %u bytes", + PacketType, m_State, bb.GetUsedSpace() - bb.GetReadableSpace(), PacketLen + ); + + // Put a message in the comm log: + if (g_ShouldLogCommIn && m_CommLogFile.IsOpen()) + { + m_CommLogFile.Printf("^^^^^^ Wrong number of bytes read for this packet (exp %d left, got " SIZE_T_FMT " left) ^^^^^^\n\n\n", + 1, bb.GetReadableSpace() + ); + m_CommLogFile.Flush(); + } + + ASSERT(!"Read wrong number of bytes!"); + m_Client->PacketError(PacketType); + } + } // for (ever) + + // Log any leftover bytes into the logfile: + if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0) && m_CommLogFile.IsOpen()) + { + AString AllData; + size_t OldReadableSpace = m_ReceivedData.GetReadableSpace(); + m_ReceivedData.ReadAll(AllData); + m_ReceivedData.ResetRead(); + m_ReceivedData.SkipRead(m_ReceivedData.GetReadableSpace() - OldReadableSpace); + ASSERT(m_ReceivedData.GetReadableSpace() == OldReadableSpace); + AString Hex; + CreateHexDump(Hex, AllData.data(), AllData.size(), 16); + m_CommLogFile.Printf("Protocol 1.9: There are " SIZE_T_FMT " (0x" SIZE_T_FMT_HEX ") bytes of non-parse-able data left in the buffer:\n%s", + m_ReceivedData.GetReadableSpace(), m_ReceivedData.GetReadableSpace(), Hex.c_str() + ); + m_CommLogFile.Flush(); + } +} + + + + +bool cProtocol190::HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) +{ + switch (m_State) + { + case 1: + { + // Status + switch (a_PacketType) + { + case 0x00: HandlePacketStatusRequest(a_ByteBuffer); return true; + case 0x01: HandlePacketStatusPing (a_ByteBuffer); return true; + } + break; + } + + case 2: + { + // Login + switch (a_PacketType) + { + case 0x00: HandlePacketLoginStart (a_ByteBuffer); return true; + case 0x01: HandlePacketLoginEncryptionResponse(a_ByteBuffer); return true; + } + break; + } + + case 3: + { + // Game + switch (a_PacketType) + { + case 0x00: HandleConfirmTeleport (a_ByteBuffer); return true; + case 0x01: HandlePacketTabComplete (a_ByteBuffer); return true; + case 0x02: HandlePacketChatMessage (a_ByteBuffer); return true; + case 0x03: HandlePacketClientStatus (a_ByteBuffer); return true; + case 0x04: HandlePacketClientSettings (a_ByteBuffer); return true; + case 0x05: break; // Confirm transaction - not used in MCS + case 0x06: HandlePacketEnchantItem (a_ByteBuffer); return true; + case 0x07: HandlePacketWindowClick (a_ByteBuffer); return true; + case 0x08: HandlePacketWindowClose (a_ByteBuffer); return true; + case 0x09: HandlePacketPluginMessage (a_ByteBuffer); return true; + case 0x0a: HandlePacketUseEntity (a_ByteBuffer); return true; + case 0x0b: HandlePacketKeepAlive (a_ByteBuffer); return true; + case 0x0c: HandlePacketPlayerPos (a_ByteBuffer); return true; + case 0x0d: HandlePacketPlayerPosLook (a_ByteBuffer); return true; + case 0x0e: HandlePacketPlayerLook (a_ByteBuffer); return true; + case 0x0f: HandlePacketPlayer (a_ByteBuffer); return true; + case 0x10: break; // Vehicle move - not yet implemented + case 0x11: break; // Steer boat - not yet implemented + case 0x12: HandlePacketPlayerAbilities (a_ByteBuffer); return true; + case 0x13: HandlePacketBlockDig (a_ByteBuffer); return true; + case 0x14: HandlePacketEntityAction (a_ByteBuffer); return true; + case 0x15: HandlePacketSteerVehicle (a_ByteBuffer); return true; + case 0x16: break; // Resource pack status - not yet implemented + case 0x17: HandlePacketSlotSelect (a_ByteBuffer); return true; + case 0x18: HandlePacketCreativeInventoryAction(a_ByteBuffer); return true; + case 0x19: HandlePacketUpdateSign (a_ByteBuffer); return true; + case 0x1a: HandlePacketAnimation (a_ByteBuffer); return true; + case 0x1b: break; // Spectate? + case 0x1c: HandlePacketBlockPlace (a_ByteBuffer); return true; + case 0x1d: HandlePacketUseItem (a_ByteBuffer); return true; + } + break; + } + default: + { + // Received a packet in an unknown state, report: + LOGWARNING("Received a packet in an unknown protocol state %d. Ignoring further packets.", m_State); + + // Cannot kick the client - we don't know this state and thus the packet number for the kick packet + + // Switch to a state when all further packets are silently ignored: + m_State = 255; + return false; + } + case 255: + { + // This is the state used for "not processing packets anymore" when we receive a bad packet from a client. + // Do not output anything (the caller will do that for us), just return failure + return false; + } + } // switch (m_State) + + // Unknown packet type, report to the ClientHandle: + m_Client->PacketUnknown(a_PacketType); + return false; +} + + + + + +void cProtocol190::HandlePacketStatusPing(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Timestamp); + + cPacketizer Pkt(*this, 0x01); // Ping packet + Pkt.WriteBEInt64(Timestamp); +} + + + + + +void cProtocol190::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"] = "Cuberite 1.9"; + Version["protocol"] = 107; + + // 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 cProtocol190::HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer) +{ + UInt32 EncKeyLength, EncNonceLength; + if (!a_ByteBuffer.ReadVarInt(EncKeyLength)) + { + return; + } + AString EncKey; + if (!a_ByteBuffer.ReadString(EncKey, EncKeyLength)) + { + return; + } + if (!a_ByteBuffer.ReadVarInt(EncNonceLength)) + { + return; + } + 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(); + UInt32 DecryptedNonce[MAX_ENC_LEN / sizeof(Int32)]; + int res = rsaDecryptor.Decrypt(reinterpret_cast(EncNonce.data()), EncNonce.size(), reinterpret_cast(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]) != static_cast(reinterpret_cast(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(reinterpret_cast(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()); +} + + + + + +void cProtocol190::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.WriteVarInt32(static_cast(PubKeyDer.size())); + Pkt.WriteBuf(PubKeyDer.data(), PubKeyDer.size()); + Pkt.WriteVarInt32(4); + Pkt.WriteBEInt32(static_cast(reinterpret_cast(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 cProtocol190::HandlePacketAnimation(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Hand); + + m_Client->HandleAnimation(0); // Packet exists solely for arm-swing notification +} + + + + + +void cProtocol190::HandlePacketBlockDig(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Status); + + int BlockX, BlockY, BlockZ; + if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) + { + return; + } + + HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Face); + m_Client->HandleLeftClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), Status); +} + + + + + +void cProtocol190::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) +{ + int BlockX, BlockY, BlockZ; + if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) + { + return; + } + + HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Face); + HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Hand); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorX); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorY); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorZ); + m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), CursorX, CursorY, CursorZ, m_Client->GetPlayer()->GetEquippedItem()); +} + + + + + +void cProtocol190::HandlePacketChatMessage(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Message); + m_Client->HandleChat(Message); +} + + + + + +void cProtocol190::HandlePacketClientSettings(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Locale); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, ViewDistance); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, ChatFlags); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, ChatColors); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, SkinFlags); + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, MainHand); + + m_Client->SetLocale(Locale); + m_Client->SetViewDistance(ViewDistance); + // TODO: Handle other values +} + + + + + +void cProtocol190::HandlePacketClientStatus(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, ActionID); + switch (ActionID) + { + case 0: + { + // Respawn + m_Client->HandleRespawn(); + break; + } + case 1: + { + // Request stats + const cStatManager & Manager = m_Client->GetPlayer()->GetStatManager(); + SendStatistics(Manager); + + break; + } + case 2: + { + // Open Inventory achievement + m_Client->GetPlayer()->AwardAchievement(achOpenInv); + break; + } + } +} + + + + + +void cProtocol190::HandleConfirmTeleport(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt32, UInt32, TeleportID); + // We don't actually validate that this packet is sent or anything yet, but it still needs to be read. +} + + + + + +void cProtocol190::HandlePacketCreativeInventoryAction(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, SlotNum); + cItem Item; + if (!ReadItem(a_ByteBuffer, Item)) + { + return; + } + m_Client->HandleCreativeInventory(SlotNum, Item, (SlotNum == -1) ? caLeftClickOutside : caLeftClick); +} + + + + + +void cProtocol190::HandlePacketEntityAction(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, PlayerID); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Action); + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, JumpBoost); + + switch (Action) + { + case 0: m_Client->HandleEntityCrouch(PlayerID, true); break; // Crouch + case 1: m_Client->HandleEntityCrouch(PlayerID, false); break; // Uncrouch + case 2: m_Client->HandleEntityLeaveBed(PlayerID); break; // Leave Bed + case 3: m_Client->HandleEntitySprinting(PlayerID, true); break; // Start sprinting + case 4: m_Client->HandleEntitySprinting(PlayerID, false); break; // Stop sprinting + } +} + + + + + +void cProtocol190::HandlePacketKeepAlive(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, KeepAliveID); + m_Client->HandleKeepAlive(KeepAliveID); +} + + + + + +void cProtocol190::HandlePacketPlayer(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); + // TODO: m_Client->HandlePlayerOnGround(IsOnGround); +} + + + + + +void cProtocol190::HandlePacketPlayerAbilities(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Flags); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, FlyingSpeed); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, WalkingSpeed); + + // COnvert the bitfield into individual boolean flags: + bool IsFlying = false, CanFly = false; + if ((Flags & 2) != 0) + { + IsFlying = true; + } + if ((Flags & 4) != 0) + { + CanFly = true; + } + + m_Client->HandlePlayerAbilities(CanFly, IsFlying, FlyingSpeed, WalkingSpeed); +} + + + + + +void cProtocol190::HandlePacketPlayerLook(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Yaw); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Pitch); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); + m_Client->HandlePlayerLook(Yaw, Pitch, IsOnGround); +} + + + + + +void cProtocol190::HandlePacketPlayerPos(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosX); + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosY); + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosZ); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); + m_Client->HandlePlayerPos(PosX, PosY, PosZ, PosY + (m_Client->GetPlayer()->IsCrouched() ? 1.54 : 1.62), IsOnGround); +} + + + + + +void cProtocol190::HandlePacketPlayerPosLook(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosX); + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosY); + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosZ); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Yaw); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Pitch); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); + m_Client->HandlePlayerMoveLook(PosX, PosY, PosZ, PosY + 1.62, Yaw, Pitch, IsOnGround); +} + + + + + +void cProtocol190::HandlePacketPluginMessage(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Channel); + + // If the plugin channel is recognized vanilla, handle it directly: + if (Channel.substr(0, 3) == "MC|") + { + HandleVanillaPluginMessage(a_ByteBuffer, Channel); + + // Skip any unread data (vanilla sometimes sends garbage at the end of a packet; #1692): + if (a_ByteBuffer.GetReadableSpace() > 1) + { + LOGD("Protocol 1.8: Skipping garbage data at the end of a vanilla PluginMessage packet, %u bytes", + static_cast(a_ByteBuffer.GetReadableSpace() - 1) + ); + a_ByteBuffer.SkipRead(a_ByteBuffer.GetReadableSpace() - 1); + } + + return; + } + + // Read the plugin message and relay to clienthandle: + AString Data; + VERIFY(a_ByteBuffer.ReadString(Data, a_ByteBuffer.GetReadableSpace() - 1)); // Always succeeds + m_Client->HandlePluginMessage(Channel, Data); +} + + + + + +void cProtocol190::HandlePacketSlotSelect(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, SlotNum); + m_Client->HandleSlotSelected(SlotNum); +} + + + + + +void cProtocol190::HandlePacketSteerVehicle(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Sideways); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Forward); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Flags); + + if ((Flags & 0x2) != 0) + { + m_Client->HandleUnmount(); + } + else if ((Flags & 0x1) != 0) + { + // jump + } + else + { + m_Client->HandleSteerVehicle(Forward, Sideways); + } +} + + + + + +void cProtocol190::HandlePacketTabComplete(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Text); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, AssumeCommand); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, HasPosition); + + if (HasPosition) + { + HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Position); + } + + m_Client->HandleTabCompletion(Text); +} + + + + + +void cProtocol190::HandlePacketUpdateSign(cByteBuffer & a_ByteBuffer) +{ + int BlockX, BlockY, BlockZ; + if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) + { + return; + } + + AString Lines[4]; + for (int i = 0; i < 4; i++) + { + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Line); + Lines[i] = Line; + } + + m_Client->HandleUpdateSign(BlockX, BlockY, BlockZ, Lines[0], Lines[1], Lines[2], Lines[3]); +} + + + + + +void cProtocol190::HandlePacketUseEntity(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, EntityID); + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, Type); + + switch (Type) + { + case 0: + { + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, Hand) + m_Client->HandleUseEntity(EntityID, false); + break; + } + case 1: + { + m_Client->HandleUseEntity(EntityID, true); + break; + } + case 2: + { + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, TargetX); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, TargetY); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, TargetZ); + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, Hand); + + // TODO: Do anything + break; + } + default: + { + ASSERT(!"Unhandled use entity type!"); + return; + } + } +} + + + + + +void cProtocol190::HandlePacketUseItem(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt64, Hand); + + // Didn't click a block - emulate old values used with place block of -1, -1, -1 (and BLOCK_FACE_NONE). + m_Client->HandleRightClick(-1, 255, -1, BLOCK_FACE_NONE, 0, 0, 0, m_Client->GetPlayer()->GetEquippedItem()); +} + + + + + +void cProtocol190::HandlePacketEnchantItem(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Enchantment); + + m_Client->HandleEnchantItem(WindowID, Enchantment); +} + + + + + +void cProtocol190::HandlePacketWindowClick(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); + HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, SlotNum); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Button); + HANDLE_READ(a_ByteBuffer, ReadBEUInt16, UInt16, TransactionID); + HANDLE_READ(a_ByteBuffer, ReadVarInt32, UInt32, Mode); + cItem Item; + ReadItem(a_ByteBuffer, Item); + + // Convert Button, Mode, SlotNum and HeldItem into eClickAction: + eClickAction Action; + switch ((Mode << 8) | Button) + { + case 0x0000: Action = (SlotNum != SLOT_NUM_OUTSIDE) ? caLeftClick : caLeftClickOutside; break; + case 0x0001: Action = (SlotNum != SLOT_NUM_OUTSIDE) ? caRightClick : caRightClickOutside; break; + case 0x0100: Action = caShiftLeftClick; break; + case 0x0101: Action = caShiftRightClick; break; + case 0x0200: Action = caNumber1; break; + case 0x0201: Action = caNumber2; break; + case 0x0202: Action = caNumber3; break; + case 0x0203: Action = caNumber4; break; + case 0x0204: Action = caNumber5; break; + case 0x0205: Action = caNumber6; break; + case 0x0206: Action = caNumber7; break; + case 0x0207: Action = caNumber8; break; + case 0x0208: Action = caNumber9; break; + case 0x0302: Action = caMiddleClick; break; + case 0x0400: Action = (SlotNum == SLOT_NUM_OUTSIDE) ? caLeftClickOutsideHoldNothing : caDropKey; break; + case 0x0401: Action = (SlotNum == SLOT_NUM_OUTSIDE) ? caRightClickOutsideHoldNothing : caCtrlDropKey; break; + case 0x0500: Action = (SlotNum == SLOT_NUM_OUTSIDE) ? caLeftPaintBegin : caUnknown; break; + case 0x0501: Action = (SlotNum != SLOT_NUM_OUTSIDE) ? caLeftPaintProgress : caUnknown; break; + case 0x0502: Action = (SlotNum == SLOT_NUM_OUTSIDE) ? caLeftPaintEnd : caUnknown; break; + case 0x0504: Action = (SlotNum == SLOT_NUM_OUTSIDE) ? caRightPaintBegin : caUnknown; break; + case 0x0505: Action = (SlotNum != SLOT_NUM_OUTSIDE) ? caRightPaintProgress : caUnknown; break; + case 0x0506: Action = (SlotNum == SLOT_NUM_OUTSIDE) ? caRightPaintEnd : caUnknown; break; + case 0x0600: Action = caDblClick; break; + default: + { + LOGWARNING("Unhandled window click mode / button combination: %d (0x%x)", (Mode << 8) | Button, (Mode << 8) | Button); + Action = caUnknown; + break; + } + } + + m_Client->HandleWindowClick(WindowID, SlotNum, Action, Item); +} + + + + + +void cProtocol190::HandlePacketWindowClose(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); + m_Client->HandleWindowClose(WindowID); +} + + + + + +void cProtocol190::HandleVanillaPluginMessage(cByteBuffer & a_ByteBuffer, const AString & a_Channel) +{ + if (a_Channel == "MC|AdvCdm") + { + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Mode) + switch (Mode) + { + case 0x00: + { + HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, BlockX); + HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, BlockY); + HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, BlockZ); + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Command); + m_Client->HandleCommandBlockBlockChange(BlockX, BlockY, BlockZ, Command); + break; + } + + default: + { + m_Client->SendChat(Printf("Failure setting command block command; unhandled mode %u (0x%02x)", Mode, Mode), mtFailure); + LOG("Unhandled MC|AdvCdm packet mode."); + return; + } + } // switch (Mode) + return; + } + else if (a_Channel == "MC|Brand") + { + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Brand); + m_Client->SetClientBrand(Brand); + // Send back our brand, including the length: + SendPluginMessage("MC|Brand", "\x08Cuberite"); + return; + } + else if (a_Channel == "MC|Beacon") + { + HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, Effect1); + HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, Effect2); + m_Client->HandleBeaconSelection(Effect1, Effect2); + return; + } + else if (a_Channel == "MC|ItemName") + { + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, ItemName); + m_Client->HandleAnvilItemName(ItemName); + return; + } + else if (a_Channel == "MC|TrSel") + { + HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, SlotNum); + m_Client->HandleNPCTrade(SlotNum); + return; + } + LOG("Unhandled vanilla plugin channel: \"%s\".", a_Channel.c_str()); + + // Read the payload and send it through to the clienthandle: + AString Message; + VERIFY(a_ByteBuffer.ReadString(Message, a_ByteBuffer.GetReadableSpace() - 1)); + m_Client->HandlePluginMessage(a_Channel, Message); +} + + + + + +void cProtocol190::SendData(const char * a_Data, size_t a_Size) +{ + if (m_IsEncrypted) + { + Byte Encrypted[8192]; // Larger buffer, we may be sending lots of data (chunks) + while (a_Size > 0) + { + size_t NumBytes = (a_Size > sizeof(Encrypted)) ? sizeof(Encrypted) : a_Size; + m_Encryptor.ProcessData(Encrypted, reinterpret_cast(const_cast(a_Data)), NumBytes); + m_Client->SendData(reinterpret_cast(Encrypted), NumBytes); + a_Size -= NumBytes; + a_Data += NumBytes; + } + } + else + { + m_Client->SendData(a_Data, a_Size); + } +} + + + + + +bool cProtocol190::ReadItem(cByteBuffer & a_ByteBuffer, cItem & a_Item, size_t a_KeepRemainingBytes) +{ + HANDLE_PACKET_READ(a_ByteBuffer, ReadBEInt16, Int16, ItemType); + if (ItemType == -1) + { + // The item is empty, no more data follows + a_Item.Empty(); + return true; + } + a_Item.m_ItemType = ItemType; + + HANDLE_PACKET_READ(a_ByteBuffer, ReadBEInt8, Int8, ItemCount); + HANDLE_PACKET_READ(a_ByteBuffer, ReadBEInt16, Int16, ItemDamage); + a_Item.m_ItemCount = ItemCount; + a_Item.m_ItemDamage = ItemDamage; + if (ItemCount <= 0) + { + a_Item.Empty(); + } + + AString Metadata; + if (!a_ByteBuffer.ReadString(Metadata, a_ByteBuffer.GetReadableSpace() - a_KeepRemainingBytes - 1) || (Metadata.size() == 0) || (Metadata[0] == 0)) + { + // No metadata + return true; + } + + ParseItemMetadata(a_Item, Metadata); + return true; +} + + + + + +void cProtocol190::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata) +{ + // Parse into NBT: + cParsedNBT NBT(a_Metadata.data(), a_Metadata.size()); + if (!NBT.IsValid()) + { + AString HexDump; + CreateHexDump(HexDump, a_Metadata.data(), std::max(a_Metadata.size(), 1024), 16); + LOGWARNING("Cannot parse NBT item metadata: (" SIZE_T_FMT " bytes)\n%s", a_Metadata.size(), HexDump.c_str()); + return; + } + + // Load enchantments and custom display names from the NBT data: + for (int tag = NBT.GetFirstChild(NBT.GetRoot()); tag >= 0; tag = NBT.GetNextSibling(tag)) + { + AString TagName = NBT.GetName(tag); + switch (NBT.GetType(tag)) + { + case TAG_List: + { + if ((TagName == "ench") || (TagName == "StoredEnchantments")) // Enchantments tags + { + EnchantmentSerializer::ParseFromNBT(a_Item.m_Enchantments, NBT, tag); + } + break; + } + case TAG_Compound: + { + if (TagName == "display") // Custom name and lore tag + { + for (int displaytag = NBT.GetFirstChild(tag); displaytag >= 0; displaytag = NBT.GetNextSibling(displaytag)) + { + if ((NBT.GetType(displaytag) == TAG_String) && (NBT.GetName(displaytag) == "Name")) // Custon name tag + { + a_Item.m_CustomName = NBT.GetString(displaytag); + } + else if ((NBT.GetType(displaytag) == TAG_List) && (NBT.GetName(displaytag) == "Lore")) // Lore tag + { + AString Lore; + + for (int loretag = NBT.GetFirstChild(displaytag); loretag >= 0; loretag = NBT.GetNextSibling(loretag)) // Loop through array of strings + { + AppendPrintf(Lore, "%s`", NBT.GetString(loretag).c_str()); // Append the lore with a grave accent / backtick, used internally by MCS to display a new line in the client; don't forget to c_str ;) + } + + a_Item.m_Lore = Lore; + } + else if ((NBT.GetType(displaytag) == TAG_Int) && (NBT.GetName(displaytag) == "color")) + { + a_Item.m_ItemColor.m_Color = static_cast(NBT.GetInt(displaytag)); + } + } + } + else if ((TagName == "Fireworks") || (TagName == "Explosion")) + { + cFireworkItem::ParseFromNBT(a_Item.m_FireworkItem, NBT, tag, static_cast(a_Item.m_ItemType)); + } + else if (TagName == "EntityTag") + { + for (int entitytag = NBT.GetFirstChild(tag); entitytag >= 0; entitytag = NBT.GetNextSibling(entitytag)) + { + if ((NBT.GetType(entitytag) == TAG_String) && (NBT.GetName(entitytag) == "id")) + { + eMonsterType MonsterType = cMonster::StringToMobType(NBT.GetString(entitytag)); + // No special method here to convert to the numeric damage value; just cast to the given ID + a_Item.m_ItemDamage = static_cast(MonsterType); + } + } + } + break; + } + case TAG_Int: + { + if (TagName == "RepairCost") + { + a_Item.m_RepairCost = NBT.GetInt(tag); + } + break; + } + case TAG_String: + { + if (TagName == "Potion") + { + AString PotionEffect = NBT.GetString(tag); + if (PotionEffect.find("minecraft:") == AString::npos) + { + LOGD("Unknown or missing domain on potion effect name %s!", PotionEffect.c_str()); + continue; + } + + if (PotionEffect.find("water") != AString::npos) + { + a_Item.m_ItemDamage = 0; + // Water bottles shouldn't have other bits set on them; exit early. + continue; + } + if (PotionEffect.find("empty") != AString::npos) + { + a_Item.m_ItemDamage = 0; + } + else if (PotionEffect.find("mundane") != AString::npos) + { + a_Item.m_ItemDamage = 0; + } + else if (PotionEffect.find("thick") != AString::npos) + { + a_Item.m_ItemDamage = 20; + } + else if (PotionEffect.find("awkward") != AString::npos) + { + a_Item.m_ItemDamage = 10; + } + else if (PotionEffect.find("regeneration") != AString::npos) + { + a_Item.m_ItemDamage = 1; + } + else if (PotionEffect.find("swiftness") != AString::npos) + { + a_Item.m_ItemDamage = 2; + } + else if (PotionEffect.find("fire_resistance") != AString::npos) + { + a_Item.m_ItemDamage = 3; + } + else if (PotionEffect.find("poison") != AString::npos) + { + a_Item.m_ItemDamage = 4; + } + else if (PotionEffect.find("healing") != AString::npos) + { + a_Item.m_ItemDamage = 5; + } + else if (PotionEffect.find("night_vision") != AString::npos) + { + a_Item.m_ItemDamage = 6; + } + else if (PotionEffect.find("weakness") != AString::npos) + { + a_Item.m_ItemDamage = 8; + } + else if (PotionEffect.find("strength") != AString::npos) + { + a_Item.m_ItemDamage = 9; + } + else if (PotionEffect.find("slowness") != AString::npos) + { + a_Item.m_ItemDamage = 10; + } + else if (PotionEffect.find("leaping") != AString::npos) + { + a_Item.m_ItemDamage = 11; + } + else if (PotionEffect.find("harming") != AString::npos) + { + a_Item.m_ItemDamage = 12; + } + else if (PotionEffect.find("water_breathing") != AString::npos) + { + a_Item.m_ItemDamage = 13; + } + else if (PotionEffect.find("invisibility") != AString::npos) + { + a_Item.m_ItemDamage = 14; + } + else + { + // Note: luck potions are not handled and will reach this location + LOGD("Unknown potion type for effect name %s!", PotionEffect.c_str()); + continue; + } + + if (PotionEffect.find("strong") != AString::npos) + { + a_Item.m_ItemDamage |= 0x20; + } + if (PotionEffect.find("long") != AString::npos) + { + a_Item.m_ItemDamage |= 0x40; + } + + // Ugly special case with the changed splash potion ID in 1.9 + if ((a_Item.m_ItemType == 438) || (a_Item.m_ItemType == 441)) + { + // Splash or lingering potions - change the ID to the normal one and mark as splash potions + a_Item.m_ItemType = E_ITEM_POTION; + a_Item.m_ItemDamage |= 0x4000; // Is splash potion + } + else + { + a_Item.m_ItemDamage |= 0x2000; // Is drinkable + } + } + break; + } + default: LOGD("Unimplemented NBT data when parsing!"); break; + } + } +} + + + + + +void cProtocol190::StartEncryption(const Byte * a_Key) +{ + m_Encryptor.Init(a_Key, a_Key); + m_Decryptor.Init(a_Key, a_Key); + m_IsEncrypted = true; + + // Prepare the m_AuthServerID: + cSha1Checksum Checksum; + cServer * Server = cRoot::Get()->GetServer(); + const AString & ServerID = Server->GetServerID(); + Checksum.Update(reinterpret_cast(ServerID.c_str()), ServerID.length()); + Checksum.Update(a_Key, 16); + Checksum.Update(reinterpret_cast(Server->GetPublicKeyDER().data()), Server->GetPublicKeyDER().size()); + Byte Digest[20]; + Checksum.Finalize(Digest); + cSha1Checksum::DigestToJava(Digest, m_AuthServerID); +} + + + + + +eBlockFace cProtocol190::FaceIntToBlockFace(UInt32 a_BlockFace) +{ + // Normalize the blockface values returned from the protocol + // Anything known gets mapped 1:1, everything else returns BLOCK_FACE_NONE + switch (a_BlockFace) + { + case BLOCK_FACE_XM: return BLOCK_FACE_XM; + case BLOCK_FACE_XP: return BLOCK_FACE_XP; + case BLOCK_FACE_YM: return BLOCK_FACE_YM; + case BLOCK_FACE_YP: return BLOCK_FACE_YP; + case BLOCK_FACE_ZM: return BLOCK_FACE_ZM; + case BLOCK_FACE_ZP: return BLOCK_FACE_ZP; + default: return BLOCK_FACE_NONE; + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol190::cPacketizer: + +void cProtocol190::SendPacket(cPacketizer & a_Pkt) +{ + UInt32 PacketLen = static_cast(m_OutPacketBuffer.GetUsedSpace()); + AString PacketData, CompressedPacket; + m_OutPacketBuffer.ReadAll(PacketData); + m_OutPacketBuffer.CommitRead(); + + if ((m_State == 3) && (PacketLen >= 256)) + { + // Compress the packet payload: + if (!cProtocol190::CompressPacket(PacketData, CompressedPacket)) + { + return; + } + } + else if (m_State == 3) + { + // The packet is not compressed, indicate this in the packet header: + m_OutPacketLenBuffer.WriteVarInt32(PacketLen + 1); + m_OutPacketLenBuffer.WriteVarInt32(0); + AString LengthData; + m_OutPacketLenBuffer.ReadAll(LengthData); + SendData(LengthData.data(), LengthData.size()); + } + else + { + // Compression doesn't apply to this state, send raw data: + m_OutPacketLenBuffer.WriteVarInt32(PacketLen); + AString LengthData; + m_OutPacketLenBuffer.ReadAll(LengthData); + SendData(LengthData.data(), LengthData.size()); + } + + // Send the packet's payload, either direct or compressed: + if (CompressedPacket.empty()) + { + m_OutPacketLenBuffer.CommitRead(); + SendData(PacketData.data(), PacketData.size()); + } + else + { + SendData(CompressedPacket.data(), CompressedPacket.size()); + } + + // Log the comm into logfile: + if (g_ShouldLogCommOut && m_CommLogFile.IsOpen()) + { + AString Hex; + ASSERT(PacketData.size() > 0); + CreateHexDump(Hex, PacketData.data(), PacketData.size(), 16); + m_CommLogFile.Printf("Outgoing packet: type %d (0x%x), length %u (0x%x), state %d. Payload (incl. type):\n%s\n", + a_Pkt.GetPacketType(), a_Pkt.GetPacketType(), PacketLen, PacketLen, m_State, Hex.c_str() + ); + } +} + + + + + +void cProtocol190::WriteItem(cPacketizer & a_Pkt, const cItem & a_Item) +{ + short ItemType = a_Item.m_ItemType; + ASSERT(ItemType >= -1); // Check validity of packets in debug runtime + if (ItemType <= 0) + { + // Fix, to make sure no invalid values are sent. + ItemType = -1; + } + + if (a_Item.IsEmpty()) + { + a_Pkt.WriteBEInt16(-1); + return; + } + + if ((ItemType == E_ITEM_POTION) && ((a_Item.m_ItemDamage & 0x4000) != 0)) + { + // Ugly special case for splash potion ids which changed in 1.9; this can be removed when the new 1.9 ids are implemented + a_Pkt.WriteBEInt16(438); // minecraft:splash_potion + } + else + { + // Normal item + a_Pkt.WriteBEInt16(ItemType); + } + a_Pkt.WriteBEInt8(a_Item.m_ItemCount); + if ((ItemType == E_ITEM_POTION) || (ItemType == E_ITEM_SPAWN_EGG)) + { + // These items lost their metadata; if it is sent they don't render correctly. + a_Pkt.WriteBEInt16(0); + } + else + { + a_Pkt.WriteBEInt16(a_Item.m_ItemDamage); + } + + if (a_Item.m_Enchantments.IsEmpty() && a_Item.IsBothNameAndLoreEmpty() && (ItemType != E_ITEM_FIREWORK_ROCKET) && (ItemType != E_ITEM_FIREWORK_STAR) && !a_Item.m_ItemColor.IsValid() && (ItemType != E_ITEM_POTION) && (ItemType != E_ITEM_SPAWN_EGG)) + { + a_Pkt.WriteBEInt8(0); + return; + } + + + // Send the enchantments and custom names: + cFastNBTWriter Writer; + if (a_Item.m_RepairCost != 0) + { + Writer.AddInt("RepairCost", a_Item.m_RepairCost); + } + if (!a_Item.m_Enchantments.IsEmpty()) + { + const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench"; + EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments, Writer, TagName); + } + if (!a_Item.IsBothNameAndLoreEmpty() || a_Item.m_ItemColor.IsValid()) + { + Writer.BeginCompound("display"); + if (a_Item.m_ItemColor.IsValid()) + { + Writer.AddInt("color", static_cast(a_Item.m_ItemColor.m_Color)); + } + + if (!a_Item.IsCustomNameEmpty()) + { + Writer.AddString("Name", a_Item.m_CustomName.c_str()); + } + if (!a_Item.IsLoreEmpty()) + { + Writer.BeginList("Lore", TAG_String); + + AStringVector Decls = StringSplit(a_Item.m_Lore, "`"); + for (AStringVector::const_iterator itr = Decls.begin(), end = Decls.end(); itr != end; ++itr) + { + if (itr->empty()) + { + // The decl is empty (two `s), ignore + continue; + } + Writer.AddString("", itr->c_str()); + } + + Writer.EndList(); + } + Writer.EndCompound(); + } + if ((a_Item.m_ItemType == E_ITEM_FIREWORK_ROCKET) || (a_Item.m_ItemType == E_ITEM_FIREWORK_STAR)) + { + cFireworkItem::WriteToNBTCompound(a_Item.m_FireworkItem, Writer, static_cast(a_Item.m_ItemType)); + } + if (a_Item.m_ItemType == E_ITEM_POTION) + { + // 1.9 potions use a different format. In the future (when only 1.9+ is supported) this should be its own class + AString PotionID = "empty"; // Fallback of "Uncraftable potion" for unhandled cases + + cEntityEffect::eType Type = cEntityEffect::GetPotionEffectType(a_Item.m_ItemDamage); + if (Type != cEntityEffect::effNoEffect) + { + switch (Type) + { + case cEntityEffect::effRegeneration: PotionID = "regeneration"; break; + case cEntityEffect::effSpeed: PotionID = "swiftness"; break; + case cEntityEffect::effFireResistance: PotionID = "fire_resistance"; break; + case cEntityEffect::effPoison: PotionID = "poison"; break; + case cEntityEffect::effInstantHealth: PotionID = "healing"; break; + case cEntityEffect::effNightVision: PotionID = "night_vision"; break; + case cEntityEffect::effWeakness: PotionID = "weakness"; break; + case cEntityEffect::effStrength: PotionID = "strength"; break; + case cEntityEffect::effSlowness: PotionID = "slowness"; break; + case cEntityEffect::effJumpBoost: PotionID = "leaping"; break; + case cEntityEffect::effInstantDamage: PotionID = "harming"; break; + case cEntityEffect::effWaterBreathing: PotionID = "water_breathing"; break; + case cEntityEffect::effInvisibility: PotionID = "invisibility"; break; + } + if (cEntityEffect::GetPotionEffectIntensity(a_Item.m_ItemDamage) == 1) + { + PotionID = "strong_" + PotionID; + } + else if (a_Item.m_ItemDamage & 0x40) + { + // Extended potion bit + PotionID = "long_" + PotionID; + } + } + else + { + // Empty potions: Water bottles and other base ones + if (a_Item.m_ItemDamage == 0) + { + // No other bits set; thus it's a water bottle + PotionID = "water"; + } + else + { + switch (a_Item.m_ItemDamage & 0x3f) + { + case 0x00: PotionID = "mundane"; break; + case 0x10: PotionID = "awkward"; break; + case 0x20: PotionID = "thick"; break; + } + // Default cases will use "empty" from before. + } + } + + PotionID = "minecraft:" + PotionID; + + Writer.AddString("Potion", PotionID.c_str()); + } + if (a_Item.m_ItemType == E_ITEM_SPAWN_EGG) + { + // Convert entity ID to the name. + eMonsterType MonsterType = cItemSpawnEggHandler::ItemDamageToMonsterType(a_Item.m_ItemDamage); + if (MonsterType != eMonsterType::mtInvalidType) + { + Writer.BeginCompound("EntityTag"); + Writer.AddString("id", cMonster::MobTypeToVanillaName(MonsterType)); + Writer.EndCompound(); + } + } + + Writer.Finish(); + + AString Result = Writer.GetResult(); + if (Result.size() == 0) + { + a_Pkt.WriteBEInt8(0); + return; + } + a_Pkt.WriteBuf(Result.data(), Result.size()); +} + + + + + +void cProtocol190::WriteBlockEntity(cPacketizer & a_Pkt, const cBlockEntity & a_BlockEntity) +{ + cFastNBTWriter Writer; + + switch (a_BlockEntity.GetBlockType()) + { + case E_BLOCK_BEACON: + { + auto & BeaconEntity = reinterpret_cast(a_BlockEntity); + Writer.AddInt("x", BeaconEntity.GetPosX()); + Writer.AddInt("y", BeaconEntity.GetPosY()); + Writer.AddInt("z", BeaconEntity.GetPosZ()); + Writer.AddInt("Primary", BeaconEntity.GetPrimaryEffect()); + Writer.AddInt("Secondary", BeaconEntity.GetSecondaryEffect()); + Writer.AddInt("Levels", BeaconEntity.GetBeaconLevel()); + Writer.AddString("id", "Beacon"); // "Tile Entity ID" - MC wiki; vanilla server always seems to send this though + break; + } + + case E_BLOCK_COMMAND_BLOCK: + { + auto & CommandBlockEntity = reinterpret_cast(a_BlockEntity); + Writer.AddByte("TrackOutput", 1); // Neither I nor the MC wiki has any idea about this + Writer.AddInt("SuccessCount", CommandBlockEntity.GetResult()); + Writer.AddInt("x", CommandBlockEntity.GetPosX()); + Writer.AddInt("y", CommandBlockEntity.GetPosY()); + Writer.AddInt("z", CommandBlockEntity.GetPosZ()); + Writer.AddString("Command", CommandBlockEntity.GetCommand().c_str()); + // You can set custom names for windows in Vanilla + // For a command block, this would be the 'name' prepended to anything it outputs into global chat + // MCS doesn't have this, so just leave it @ '@'. (geddit?) + Writer.AddString("CustomName", "@"); + Writer.AddString("id", "Control"); // "Tile Entity ID" - MC wiki; vanilla server always seems to send this though + if (!CommandBlockEntity.GetLastOutput().empty()) + { + Writer.AddString("LastOutput", Printf("{\"text\":\"%s\"}", CommandBlockEntity.GetLastOutput().c_str())); + } + break; + } + + case E_BLOCK_HEAD: + { + auto & MobHeadEntity = reinterpret_cast(a_BlockEntity); + Writer.AddInt("x", MobHeadEntity.GetPosX()); + Writer.AddInt("y", MobHeadEntity.GetPosY()); + Writer.AddInt("z", MobHeadEntity.GetPosZ()); + Writer.AddByte("SkullType", MobHeadEntity.GetType() & 0xFF); + Writer.AddByte("Rot", MobHeadEntity.GetRotation() & 0xFF); + Writer.AddString("id", "Skull"); // "Tile Entity ID" - MC wiki; vanilla server always seems to send this though + + // The new Block Entity format for a Mob Head. See: http://minecraft.gamepedia.com/Head#Block_entity + Writer.BeginCompound("Owner"); + Writer.AddString("Id", MobHeadEntity.GetOwnerUUID()); + Writer.AddString("Name", MobHeadEntity.GetOwnerName()); + Writer.BeginCompound("Properties"); + Writer.BeginList("textures", TAG_Compound); + Writer.BeginCompound(""); + Writer.AddString("Signature", MobHeadEntity.GetOwnerTextureSignature()); + Writer.AddString("Value", MobHeadEntity.GetOwnerTexture()); + Writer.EndCompound(); + Writer.EndList(); + Writer.EndCompound(); + Writer.EndCompound(); + break; + } + + case E_BLOCK_FLOWER_POT: + { + auto & FlowerPotEntity = reinterpret_cast(a_BlockEntity); + Writer.AddInt("x", FlowerPotEntity.GetPosX()); + Writer.AddInt("y", FlowerPotEntity.GetPosY()); + Writer.AddInt("z", FlowerPotEntity.GetPosZ()); + Writer.AddInt("Item", static_cast(FlowerPotEntity.GetItem().m_ItemType)); + Writer.AddInt("Data", static_cast(FlowerPotEntity.GetItem().m_ItemDamage)); + Writer.AddString("id", "FlowerPot"); // "Tile Entity ID" - MC wiki; vanilla server always seems to send this though + break; + } + + case E_BLOCK_MOB_SPAWNER: + { + auto & MobSpawnerEntity = reinterpret_cast(a_BlockEntity); + Writer.AddInt("x", MobSpawnerEntity.GetPosX()); + Writer.AddInt("y", MobSpawnerEntity.GetPosY()); + Writer.AddInt("z", MobSpawnerEntity.GetPosZ()); + Writer.AddString("EntityId", cMonster::MobTypeToVanillaName(MobSpawnerEntity.GetEntity())); + Writer.AddShort("Delay", MobSpawnerEntity.GetSpawnDelay()); + Writer.AddString("id", "MobSpawner"); + break; + } + + default: + { + break; + } + } + + Writer.Finish(); + a_Pkt.WriteBuf(Writer.GetResult().data(), Writer.GetResult().size()); +} + + + + + +void cProtocol190::WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) +{ + // Common metadata: + Int8 Flags = 0; + if (a_Entity.IsOnFire()) + { + Flags |= 0x01; + } + if (a_Entity.IsCrouched()) + { + Flags |= 0x02; + } + if (a_Entity.IsSprinting()) + { + Flags |= 0x08; + } + if (a_Entity.IsRclking()) + { + Flags |= 0x10; + } + if (a_Entity.IsInvisible()) + { + Flags |= 0x20; + } + a_Pkt.WriteBEUInt8(0); // Index 0 + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); // Type + a_Pkt.WriteBEInt8(Flags); + + switch (a_Entity.GetEntityType()) + { + case cEntity::etPlayer: break; // TODO? + case cEntity::etPickup: + { + a_Pkt.WriteBEUInt8(5); // Index 5: Item + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, reinterpret_cast(a_Entity).GetItem()); + break; + } + case cEntity::etMinecart: + { + a_Pkt.WriteBEUInt8(5); // Index 5: Shaking power + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + + // The following expression makes Minecarts shake more with less health or higher damage taken + // It gets half the maximum health, and takes it away from the current health minus the half health: + /* + Health: 5 | 3 - (5 - 3) = 1 (shake power) + Health: 3 | 3 - (3 - 3) = 3 + Health: 1 | 3 - (1 - 3) = 5 + */ + auto & Minecart = reinterpret_cast(a_Entity); + a_Pkt.WriteVarInt32((((a_Entity.GetMaxHealth() / 2) - (a_Entity.GetHealth() - (a_Entity.GetMaxHealth() / 2))) * Minecart.LastDamage()) * 4); + + a_Pkt.WriteBEUInt8(6); // Index 6: Shaking direction (doesn't seem to effect anything) + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(1); + + a_Pkt.WriteBEUInt8(7); // Index 7: Shake multiplier / damage taken + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast(Minecart.LastDamage() + 10)); + + if (Minecart.GetPayload() == cMinecart::mpNone) + { + auto & RideableMinecart = reinterpret_cast(Minecart); + const cItem & MinecartContent = RideableMinecart.GetContent(); + if (!MinecartContent.IsEmpty()) + { + a_Pkt.WriteBEUInt8(8); // Index 8: Block ID and damage + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + int Content = MinecartContent.m_ItemType; + Content |= MinecartContent.m_ItemDamage << 8; + a_Pkt.WriteVarInt32(Content); + + a_Pkt.WriteBEUInt8(9); // Index 9: Block ID and damage + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(RideableMinecart.GetBlockHeight()); + + a_Pkt.WriteBEUInt8(10); // Index 10: Show block + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(true); + } + } + else if (Minecart.GetPayload() == cMinecart::mpFurnace) + { + a_Pkt.WriteBEUInt8(11); // Index 11: Is powered + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(reinterpret_cast(Minecart).IsFueled()); + } + break; + } // case etMinecart + + case cEntity::etProjectile: + { + auto & Projectile = reinterpret_cast(a_Entity); + switch (Projectile.GetProjectileKind()) + { + case cProjectileEntity::pkArrow: + { + a_Pkt.WriteBEUInt8(5); // Index 5: Is critical + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(reinterpret_cast(Projectile).IsCritical() ? 1 : 0); + break; + } + case cProjectileEntity::pkFirework: + { + a_Pkt.WriteBEUInt8(5); // Index 5: Firework item used for this firework + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, reinterpret_cast(Projectile).GetItem()); + break; + } + case cProjectileEntity::pkSplashPotion: + { + a_Pkt.WriteBEUInt8(5); // Index 5: Potion item which was thrown + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, reinterpret_cast(Projectile).GetItem()); + } + default: + { + break; + } + } + break; + } // case etProjectile + + case cEntity::etMonster: + { + WriteMobMetadata(a_Pkt, reinterpret_cast(a_Entity)); + break; + } + + case cEntity::etItemFrame: + { + auto & Frame = reinterpret_cast(a_Entity); + a_Pkt.WriteBEUInt8(5); // Index 5: Item + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, Frame.GetItem()); + a_Pkt.WriteBEUInt8(6); // Index 6: Rotation + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Frame.GetItemRotation()); + break; + } // case etItemFrame + + default: + { + break; + } + } +} + + + + + +void cProtocol190::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) +{ + // Living Enitiy Metadata + if (a_Mob.HasCustomName()) + { + // TODO: As of 1.9 _all_ entities can have custom names; should this be moved up? + a_Pkt.WriteBEUInt8(2); // Index 2: Custom name + a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); + a_Pkt.WriteString(a_Mob.GetCustomName()); + + a_Pkt.WriteBEUInt8(3); // Index 3: Custom name always visible + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(a_Mob.IsCustomNameAlwaysVisible()); + } + + a_Pkt.WriteBEUInt8(6); // Index 6: Health + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); + + switch (a_Mob.GetMobType()) + { + case mtBat: + { + auto & Bat = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Bat flags - currently only hanging + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(Bat.IsHanging() ? 1 : 0); + break; + } // case mtBat + + case mtCreeper: + { + auto & Creeper = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: State (idle or "blowing") + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Creeper.IsBlowing() ? 1 : -1); + + a_Pkt.WriteBEUInt8(12); // Index 12: Is charged + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Creeper.IsCharged()); + + a_Pkt.WriteBEUInt8(13); // Index 13: Is ignited + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Creeper.IsBurnedWithFlintAndSteel()); + break; + } // case mtCreeper + + case mtEnderman: + { + auto & Enderman = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Carried block + a_Pkt.WriteBEUInt8(METADATA_TYPE_BLOCKID); + UInt32 Carried = 0; + Carried |= Enderman.GetCarriedBlock() << 4; + Carried |= Enderman.GetCarriedMeta(); + a_Pkt.WriteVarInt32(Carried); + + a_Pkt.WriteBEUInt8(12); // Index 12: Is screaming + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Enderman.IsScreaming()); + break; + } // case mtEnderman + + case mtGhast: + { + auto & Ghast = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Is attacking + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Ghast.IsCharging()); + break; + } // case mtGhast + + case mtHorse: + { + auto & Horse = reinterpret_cast(a_Mob); + Int8 Flags = 0; + if (Horse.IsTame()) + { + Flags |= 0x02; + } + if (Horse.IsSaddled()) + { + Flags |= 0x04; + } + if (Horse.IsChested()) + { + Flags |= 0x08; + } + if (Horse.IsEating()) + { + Flags |= 0x20; + } + if (Horse.IsRearing()) + { + Flags |= 0x40; + } + if (Horse.IsMthOpen()) + { + Flags |= 0x80; + } + a_Pkt.WriteBEUInt8(12); // Index 12: flags + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(Flags); + + a_Pkt.WriteBEUInt8(13); // Index 13: Variant / type + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Horse.GetHorseType()); + + a_Pkt.WriteBEUInt8(14); // Index 14: Color / style + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + int Appearance = 0; + Appearance = Horse.GetHorseColor(); + Appearance |= Horse.GetHorseStyle() << 8; + a_Pkt.WriteVarInt32(Appearance); + + a_Pkt.WriteBEUInt8(16); // Index 16: Armor + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Horse.GetHorseArmour()); + + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Horse.IsBaby()); + break; + } // case mtHorse + + case mtMagmaCube: + { + auto & MagmaCube = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Size + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(MagmaCube.GetSize()); + break; + } // case mtMagmaCube + + case mtOcelot: + { + auto & Ocelot = reinterpret_cast(a_Mob); + + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Ocelot.IsBaby()); + break; + } // case mtOcelot + + case mtCow: + { + auto & Cow = reinterpret_cast(a_Mob); + + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Cow.IsBaby()); + break; + } // case mtCow + + case mtChicken: + { + auto & Chicken = reinterpret_cast(a_Mob); + + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Chicken.IsBaby()); + break; + } // case mtChicken + + case mtPig: + { + auto & Pig = reinterpret_cast(a_Mob); + + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Pig.IsBaby()); + + a_Pkt.WriteBEUInt8(12); // Index 12: Is saddled + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Pig.IsSaddled()); + break; + } // case mtPig + + case mtSheep: + { + auto & Sheep = reinterpret_cast(a_Mob); + + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Sheep.IsBaby()); + + a_Pkt.WriteBEUInt8(12); // Index 12: sheared, color + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + Int8 SheepMetadata = 0; + SheepMetadata = static_cast(Sheep.GetFurColor()); + if (Sheep.IsSheared()) + { + SheepMetadata |= 0x10; + } + a_Pkt.WriteBEInt8(SheepMetadata); + break; + } // case mtSheep + + case mtRabbit: + { + auto & Rabbit = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Rabbit.IsBaby()); + + a_Pkt.WriteBEUInt8(12); // Index 12: Type + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Rabbit.GetRabbitTypeAsNumber()); + break; + } // case mtRabbit + + case mtSkeleton: + { + auto & Skeleton = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Type + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Skeleton.IsWither() ? 1 : 0); + break; + } // case mtSkeleton + + case mtSlime: + { + auto & Slime = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Size + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Slime.GetSize()); + break; + } // case mtSlime + + case mtVillager: + { + auto & Villager = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Villager.IsBaby()); + + a_Pkt.WriteBEUInt8(12); // Index 12: Type + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Villager.GetVilType()); + break; + } // case mtVillager + + case mtWitch: + { + auto & Witch = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Is angry + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Witch.IsAngry()); + break; + } // case mtWitch + + case mtWither: + { + auto & Wither = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(14); // Index 14: Invulnerable ticks + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Wither.GetWitherInvulnerableTicks()); + + // TODO: Use boss bar packet for health + break; + } // case mtWither + + case mtWolf: + { + auto & Wolf = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Wolf.IsBaby()); + + Int8 WolfStatus = 0; + if (Wolf.IsSitting()) + { + WolfStatus |= 0x1; + } + if (Wolf.IsAngry()) + { + WolfStatus |= 0x2; + } + if (Wolf.IsTame()) + { + WolfStatus |= 0x4; + } + a_Pkt.WriteBEUInt8(12); // Index 12: status + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(WolfStatus); + + a_Pkt.WriteBEUInt8(14); // Index 14: Health + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); + + a_Pkt.WriteBEUInt8(15); // Index 15: Is begging + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Wolf.IsBegging()); + + a_Pkt.WriteBEUInt8(16); // Index 16: Collar color + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Wolf.GetCollarColor()); + break; + } // case mtWolf + + case mtZombie: + { + auto & Zombie = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Zombie.IsBaby()); + + a_Pkt.WriteBEUInt8(12); // Index 12: Is a villager + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Zombie.IsVillagerZombie() ? 1 : 0); // TODO: This actually encodes the zombie villager profession, but that isn't implemented yet. + + a_Pkt.WriteBEUInt8(13); // Index 13: Is converting + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Zombie.IsConverting()); + break; + } // case mtZombie + + case mtZombiePigman: + { + auto & ZombiePigman = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(11); // Index 11: Is baby + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(ZombiePigman.IsBaby()); + break; + } // case mtZombiePigman + } // switch (a_Mob.GetType()) +} + + + + + +void cProtocol190::WriteEntityProperties(cPacketizer & a_Pkt, const cEntity & a_Entity) +{ + if (!a_Entity.IsMob()) + { + // No properties for anything else than mobs + a_Pkt.WriteBEInt32(0); + return; + } + + // const cMonster & Mob = (const cMonster &)a_Entity; + + // TODO: Send properties and modifiers based on the mob type + + a_Pkt.WriteBEInt32(0); // NumProperties +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol191: + +cProtocol191::cProtocol191(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol191::SendLogin(const cPlayer & a_Player, const cWorld & a_World) +{ + // Send the Join Game packet: + { + cServer * Server = cRoot::Get()->GetServer(); + cPacketizer Pkt(*this, 0x23); // Join Game packet + Pkt.WriteBEUInt32(a_Player.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_Player.GetEffectiveGameMode()) | (Server->IsHardcore() ? 0x08 : 0)); // Hardcore flag bit 4 + Pkt.WriteBEInt32(static_cast(a_World.GetDimension())); + Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal) + Pkt.WriteBEUInt8(static_cast(Clamp(Server->GetMaxPlayers(), 0, 255))); + 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, 0x43); // Spawn Position packet + Pkt.WritePosition64(FloorC(a_World.GetSpawnX()), FloorC(a_World.GetSpawnY()), FloorC(a_World.GetSpawnZ())); + } + + // Send the server difficulty: + { + cPacketizer Pkt(*this, 0x0d); // Server difficulty packet + Pkt.WriteBEInt8(1); + } + + // Send player abilities: + SendPlayerAbilities(); +} + + + + + +void cProtocol191::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"] = "Cuberite 1.9.1"; + Version["protocol"] = 108; + + // 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); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol192: + +cProtocol192::cProtocol192(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol192::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"] = "Cuberite 1.9.2"; + Version["protocol"] = 109; + + // 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); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol194: + +cProtocol194::cProtocol194(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol194::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"] = "Cuberite 1.9.4"; + Version["protocol"] = 110; + + // 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 cProtocol194::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x48); // Collect Item packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteVarInt32(a_Player.GetUniqueID()); +} + + + + + +void cProtocol194::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) +{ + ASSERT(m_State == 3); // In game mode? + + // Serialize first, before creating the Packetizer (the packetizer locks a CS) + // This contains the flags and bitmasks, too + const AString & ChunkData = a_Serializer.Serialize(cChunkDataSerializer::RELEASE_1_9_4, a_ChunkX, a_ChunkZ); + + cCSLock Lock(m_CSPacket); + SendData(ChunkData.data(), ChunkData.size()); +} + + + + + +void cProtocol194::SendEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4b); // Entity Effect packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_EffectID)); + Pkt.WriteBEUInt8(static_cast(a_Amplifier)); + Pkt.WriteVarInt32(static_cast(a_Duration)); + Pkt.WriteBool(false); // Hide particles +} + + + + + +void cProtocol194::SendEntityProperties(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + + cPacketizer Pkt(*this, 0x4a); // Entity Properties packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + WriteEntityProperties(Pkt, a_Entity); +} + + + + + +void cProtocol194::SendPlayerMaxSpeed(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4a); // Entity Properties + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteVarInt32(Player->GetUniqueID()); + Pkt.WriteBEInt32(1); // Count + Pkt.WriteString("generic.movementSpeed"); + // The default game speed is 0.1, multiply that value by the relative speed: + Pkt.WriteBEDouble(0.1 * Player->GetNormalMaxSpeed()); + if (Player->IsSprinting()) + { + Pkt.WriteVarInt32(1); // Modifier count + Pkt.WriteBEUInt64(0x662a6b8dda3e4c1c); + Pkt.WriteBEUInt64(0x881396ea6097278d); // UUID of the modifier + Pkt.WriteBEDouble(Player->GetSprintingMaxSpeed() - Player->GetNormalMaxSpeed()); + Pkt.WriteBEUInt8(2); + } + else + { + Pkt.WriteVarInt32(0); // Modifier count + } +} + + + + + +void cProtocol194::SendTeleportEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x49); // Entity teleport packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEDouble(a_Entity.GetPosX()); + Pkt.WriteBEDouble(a_Entity.GetPosY()); + Pkt.WriteBEDouble(a_Entity.GetPosZ()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol194::SendUpdateSign(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) +{ + ASSERT(m_State == 3); // In game mode? + + // 1.9.4 removed the update sign packet and now uses Update Block Entity + cPacketizer Pkt(*this, 0x09); // Update tile entity packet + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); + Pkt.WriteBEUInt8(9); // Action 9 - update sign + + cFastNBTWriter Writer; + Writer.AddInt("x", a_BlockX); + Writer.AddInt("y", a_BlockY); + Writer.AddInt("z", a_BlockZ); + Writer.AddString("id", "Sign"); + + Json::StyledWriter JsonWriter; + Json::Value Line1; + Line1["text"] = a_Line1; + Writer.AddString("Text1", JsonWriter.write(Line1)); + Json::Value Line2; + Line2["text"] = a_Line2; + Writer.AddString("Text2", JsonWriter.write(Line2)); + Json::Value Line3; + Line3["text"] = a_Line3; + Writer.AddString("Text3", JsonWriter.write(Line3)); + Json::Value Line4; + Line4["text"] = a_Line4; + Writer.AddString("Text4", JsonWriter.write(Line4)); + + Writer.Finish(); + Pkt.WriteBuf(Writer.GetResult().data(), Writer.GetResult().size()); +} + + + + + diff --git a/src/Protocol/Protocol19x.h b/src/Protocol/Protocol19x.h new file mode 100644 index 000000000..e40e3aac2 --- /dev/null +++ b/src/Protocol/Protocol19x.h @@ -0,0 +1,360 @@ + +// Protocol19x.h + +/* +Declares the 1.9.x protocol classes: + - cProtocol190 + - release 1.9.0 protocol (#107) + - cProtocol191 + - release 1.9.1 protocol (#108) + - cProtocol192 + - release 1.9.2 protocol (#109) + - cProtocol194 + - release 1.9.4 protocol (#110) +(others may be added later in the future for the 1.9 release series) +*/ + + + + + +#pragma once + +#include "Protocol.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 + +#include "PolarSSL++/AesCfb128Decryptor.h" +#include "PolarSSL++/AesCfb128Encryptor.h" + + + + + +// fwd: +namespace Json +{ + class Value; +} + + + + + +class cProtocol190 : + public cProtocol +{ + typedef cProtocol super; + +public: + + cProtocol190(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + /** Called when client sends some data: */ + virtual void DataReceived(const char * a_Data, size_t a_Size) override; + + /** Sending stuff to clients (alphabetically sorted): */ + virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle) override; + virtual void SendBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) override; + virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; + 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, eChatType a_Type) override; + virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; + virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; + virtual void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player) override; + virtual void SendDestroyEntity (const cEntity & a_Entity) override; + virtual void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) override; + virtual void SendDisconnect (const AString & a_Reason) override; + virtual void SendEditSign (int a_BlockX, int a_BlockY, int a_BlockZ) override; ///< Request the client to open up the sign editor for the sign (1.6+) + virtual void SendEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) override; + virtual void SendEntityEquipment (const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) override; + virtual void SendEntityHeadLook (const cEntity & a_Entity) override; + virtual void SendEntityLook (const cEntity & a_Entity) override; + virtual void SendEntityMetadata (const cEntity & a_Entity) override; + virtual void SendEntityProperties (const cEntity & a_Entity) override; + virtual void SendEntityRelMove (const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) override; + virtual void SendEntityRelMoveLook (const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) override; + virtual void SendEntityStatus (const cEntity & a_Entity, char a_Status) override; + virtual void SendEntityVelocity (const cEntity & a_Entity) override; + virtual void SendExplosion (double a_BlockX, double a_BlockY, double a_BlockZ, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d & a_PlayerMotion) override; + virtual void SendGameMode (eGameMode a_GameMode) override; + virtual void SendHealth (void) override; + virtual void SendHideTitle (void) override; + virtual void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item) override; + virtual void SendKeepAlive (UInt32 a_PingID) override; + virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override; + virtual void SendLoginSuccess (void) override; + virtual void SendMapData (const cMap & a_Map, int a_DataStartX, int a_DataStartY) override; + virtual void SendPaintingSpawn (const cPainting & a_Painting) override; + virtual void SendPickupSpawn (const cPickup & a_Pickup) override; + virtual void SendPlayerAbilities (void) override; + virtual void SendEntityAnimation (const cEntity & a_Entity, char a_Animation) override; + virtual void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount) override; + virtual void SendParticleEffect (const AString & a_ParticleName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array a_Data) override; + virtual void SendPlayerListAddPlayer (const cPlayer & a_Player) override; + virtual void SendPlayerListRemovePlayer (const cPlayer & a_Player) override; + virtual void SendPlayerListUpdateGameMode (const cPlayer & a_Player) override; + virtual void SendPlayerListUpdatePing (const cPlayer & a_Player) override; + virtual void SendPlayerListUpdateDisplayName(const cPlayer & a_Player, const AString & a_CustomName) override; + virtual void SendPlayerMaxSpeed (void) override; + virtual void SendPlayerMoveLook (void) override; + virtual void SendPlayerPosition (void) override; + virtual void SendPlayerSpawn (const cPlayer & a_Player) override; + virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override; + virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override; + virtual void SendResetTitle (void) override; + virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override; + virtual void SendSoundEffect (const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) override; + virtual void SendExperience (void) override; + virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) override; + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override; + virtual void SendSetSubTitle (const cCompositeChat & a_SubTitle) override; + virtual void SendSetRawSubTitle (const AString & a_SubTitle) override; + virtual void SendSetTitle (const cCompositeChat & a_Title) override; + virtual void SendSetRawTitle (const AString & a_Title) override; + virtual void SendSoundParticleEffect (const EffectID a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) override; + virtual void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock) override; + virtual void SendSpawnMob (const cMonster & a_Mob) override; + virtual void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) override; + virtual void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) override; + virtual void SendStatistics (const cStatManager & a_Manager) override; + virtual void SendTabCompletionResults (const AStringVector & a_Results) override; + virtual void SendTeleportEntity (const cEntity & a_Entity) override; + virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override; + virtual void SendTitleTimes (int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) override; + virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) override; + virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override; + virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override; + virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; + virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) override; + virtual void SendWeather (eWeather a_Weather) override; + virtual void SendWholeInventory (const cWindow & a_Window) override; + virtual void SendWindowClose (const cWindow & a_Window) override; + virtual void SendWindowOpen (const cWindow & a_Window) override; + virtual void SendWindowProperty (const cWindow & a_Window, short a_Property, short a_Value) override; + + virtual AString GetAuthServerID(void) override { return m_AuthServerID; } + + /** Compress the packet. a_Packet must be without packet length. + a_Compressed will be set to the compressed packet includes packet length and data length. + If compression fails, the function returns false. */ + static bool CompressPacket(const AString & a_Packet, AString & a_Compressed); + + /** The 1.8 protocol use a particle id instead of a string. This function converts the name to the id. If the name is incorrect, it returns 0. */ + static int GetParticleID(const AString & a_ParticleName); + + /** Minecraft 1.8 use other locations to spawn the item frame. This function converts the 1.7 positions to 1.8 positions. */ + static void FixItemFramePositions(int a_ObjectData, double & a_PosX, double & a_PosZ, double & a_Yaw); + +protected: + + AString m_ServerAddress; + + UInt16 m_ServerPort; + + AString m_AuthServerID; + + /** State of the protocol. 1 = status, 2 = login, 3 = game */ + UInt32 m_State; + + /** Buffer for the received data */ + cByteBuffer m_ReceivedData; + + bool m_IsEncrypted; + + cAesCfb128Decryptor m_Decryptor; + cAesCfb128Encryptor m_Encryptor; + + /** The logfile where the comm is logged, when g_ShouldLogComm is true */ + cFile m_CommLogFile; + + /** The dimension that was last sent to a player in a Respawn or Login packet. + Used to avoid Respawning into the same dimension, which confuses the client. */ + eDimension m_LastSentDimension; + + + /** Adds the received (unencrypted) data to m_ReceivedData, parses complete packets */ + void AddReceivedData(const char * a_Data, size_t a_Size); + + /** Reads and handles the packet. The packet length and type have already been read. + Returns true if the packet was understood, false if it was an unknown packet + */ + bool HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType); + + // Packet handlers while in the Status state (m_State == 1): + virtual 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); + + // Packet handlers while in the Game state (m_State == 3): + void HandlePacketAnimation (cByteBuffer & a_ByteBuffer); + void HandlePacketBlockDig (cByteBuffer & a_ByteBuffer); + void HandlePacketBlockPlace (cByteBuffer & a_ByteBuffer); + void HandlePacketChatMessage (cByteBuffer & a_ByteBuffer); + void HandlePacketClientSettings (cByteBuffer & a_ByteBuffer); + void HandlePacketClientStatus (cByteBuffer & a_ByteBuffer); + void HandleConfirmTeleport (cByteBuffer & a_ByteBuffer); + void HandlePacketCreativeInventoryAction(cByteBuffer & a_ByteBuffer); + void HandlePacketEntityAction (cByteBuffer & a_ByteBuffer); + void HandlePacketKeepAlive (cByteBuffer & a_ByteBuffer); + void HandlePacketPlayer (cByteBuffer & a_ByteBuffer); + void HandlePacketPlayerAbilities (cByteBuffer & a_ByteBuffer); + void HandlePacketPlayerLook (cByteBuffer & a_ByteBuffer); + void HandlePacketPlayerPos (cByteBuffer & a_ByteBuffer); + void HandlePacketPlayerPosLook (cByteBuffer & a_ByteBuffer); + void HandlePacketPluginMessage (cByteBuffer & a_ByteBuffer); + void HandlePacketSlotSelect (cByteBuffer & a_ByteBuffer); + void HandlePacketSteerVehicle (cByteBuffer & a_ByteBuffer); + void HandlePacketTabComplete (cByteBuffer & a_ByteBuffer); + void HandlePacketUpdateSign (cByteBuffer & a_ByteBuffer); + void HandlePacketUseEntity (cByteBuffer & a_ByteBuffer); + void HandlePacketUseItem (cByteBuffer & a_ByteBuffer); + void HandlePacketEnchantItem (cByteBuffer & a_ByteBuffer); + void HandlePacketWindowClick (cByteBuffer & a_ByteBuffer); + void HandlePacketWindowClose (cByteBuffer & a_ByteBuffer); + + /** Parses Vanilla plugin messages into specific ClientHandle calls. + The message payload is still in the bytebuffer, the handler reads it specifically for each handled channel */ + void HandleVanillaPluginMessage(cByteBuffer & a_ByteBuffer, const AString & a_Channel); + + + /** Sends the data to the client, encrypting them if needed. */ + virtual void SendData(const char * a_Data, size_t a_Size) override; + + /** Sends the packet to the client. Called by the cPacketizer's destructor. */ + virtual void SendPacket(cPacketizer & a_Packet) override; + + void SendCompass(const cWorld & a_World); + + /** Reads an item out of the received data, sets a_Item to the values read. + Returns false if not enough received data. + a_KeepRemainingBytes tells the function to keep that many bytes at the end of the buffer. */ + virtual bool ReadItem(cByteBuffer & a_ByteBuffer, cItem & a_Item, size_t a_KeepRemainingBytes = 0); + + /** Parses item metadata as read by ReadItem(), into the item enchantments. */ + void ParseItemMetadata(cItem & a_Item, const AString & a_Metadata); + + void StartEncryption(const Byte * a_Key); + + /** Converts the BlockFace received by the protocol into eBlockFace constants. + If the received value doesn't match any of our eBlockFace constants, BLOCK_FACE_NONE is returned. */ + eBlockFace FaceIntToBlockFace(UInt32 a_FaceInt); + + /** Writes the item data into a packet. */ + void WriteItem(cPacketizer & a_Pkt, const cItem & a_Item); + + /** Writes the metadata for the specified entity, not including the terminating 0xff. */ + void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity); + + /** Writes the mob-specific metadata for the specified mob */ + void WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob); + + /** Writes the entity properties for the specified entity, including the Count field. */ + void WriteEntityProperties(cPacketizer & a_Pkt, const cEntity & a_Entity); + + /** Writes the block entity data for the specified block entity into the packet. */ + void WriteBlockEntity(cPacketizer & a_Pkt, const cBlockEntity & a_BlockEntity); + + /** Types used within metadata */ + enum eMetadataType + { + METADATA_TYPE_BYTE = 0, + METADATA_TYPE_VARINT = 1, + METADATA_TYPE_FLOAT = 2, + METADATA_TYPE_STRING = 3, + METADATA_TYPE_CHAT = 4, + METADATA_TYPE_ITEM = 5, + METADATA_TYPE_BOOL = 6, + METADATA_TYPE_ROTATION = 7, + METADATA_TYPE_POSITION = 8, + METADATA_TYPE_OPTIONAL_POSITION = 9, + METADATA_TYPE_DIRECTION = 10, + METADATA_TYPE_OPTIONAL_UUID = 11, + METADATA_TYPE_BLOCKID = 12 + } ; +} ; + + + + + +/** The version 108 protocol, used by 1.9.1. Uses an int rather than a byte for dimension in join game. */ +class cProtocol191 : + public cProtocol190 +{ + typedef cProtocol190 super; + +public: + cProtocol191(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + // cProtocol190 overrides: + virtual void SendLogin(const cPlayer & a_Player, const cWorld & a_World) override; + virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; + +} ; + + + + + +/** The version 109 protocol, used by 1.9.2. Same as 1.9.1, except the server list ping version number changed with the protocol number. */ +class cProtocol192 : + public cProtocol191 +{ + typedef cProtocol191 super; + +public: + cProtocol192(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + // cProtocol190 overrides: + virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; + +} ; + + + + + +/** The version 110 protocol, used by 1.9.3 and 1.9.4. */ +class cProtocol194 : + public cProtocol192 +{ + typedef cProtocol192 super; + +public: + cProtocol194(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + // cProtocol190 overrides: + virtual void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player) override; + virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; + virtual void SendEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) override; + virtual void SendEntityProperties(const cEntity & a_Entity) override; + virtual void SendPlayerMaxSpeed (void) override; + virtual void SendTeleportEntity (const cEntity & a_Entity) override; + virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; + + virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; + +} ; + + + + diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index fff55b6e9..3977183e9 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -9,6 +9,8 @@ #include "ProtocolRecognizer.h" #include "Protocol17x.h" #include "Protocol18x.h" +#include "Protocol19x.h" +#include "Packetizer.h" #include "../ClientHandle.h" #include "../Root.h" #include "../Server.h" @@ -23,7 +25,8 @@ cProtocolRecognizer::cProtocolRecognizer(cClientHandle * a_Client) : super(a_Client), m_Protocol(nullptr), - m_Buffer(8192) // We need a larger buffer to support BungeeCord - it sends one huge packet at the start + m_Buffer(8192), // We need a larger buffer to support BungeeCord - it sends one huge packet at the start + m_InPingForUnrecognizedVersion(false) { } @@ -48,6 +51,10 @@ AString cProtocolRecognizer::GetVersionTextFromInt(int a_ProtocolVersion) 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"; + case PROTO_VERSION_1_9_0: return "1.9"; + case PROTO_VERSION_1_9_1: return "1.9.1"; + case PROTO_VERSION_1_9_2: return "1.9.2"; + case PROTO_VERSION_1_9_4: return "1.9.4"; } ASSERT(!"Unknown protocol version"); return Printf("Unknown protocol (%d)", a_ProtocolVersion); @@ -67,6 +74,33 @@ void cProtocolRecognizer::DataReceived(const char * a_Data, size_t a_Size) return; } + if (m_InPingForUnrecognizedVersion) + { + // We already know the verison; handle it here. + UInt32 PacketLen; + UInt32 PacketID; + if (!m_Buffer.ReadVarInt32(PacketLen)) + { + return; + } + if (!m_Buffer.ReadVarInt32(PacketID)) + { + return; + } + ASSERT(PacketID == 0x01); // Ping packet + ASSERT(PacketLen == 9); // Payload of the packet ID and a UInt64 + + Int64 Data; + if (!m_Buffer.ReadBEInt64(Data)) + { + return; + } + + cPacketizer Pkt(*this, 0x01); // Pong packet + Pkt.WriteBEInt64(Data); + return; + } + if (!TryRecognizeProtocol()) { return; @@ -88,7 +122,7 @@ void cProtocolRecognizer::DataReceived(const char * a_Data, size_t a_Size) -void cProtocolRecognizer::SendAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle) +void cProtocolRecognizer::SendAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { ASSERT(m_Protocol != nullptr); m_Protocol->SendAttachEntity(a_Entity, a_Vehicle); @@ -188,6 +222,16 @@ void cProtocolRecognizer::SendDestroyEntity(const cEntity & a_Entity) +void cProtocolRecognizer::SendDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + ASSERT(m_Protocol != nullptr); + m_Protocol->SendDetachEntity(a_Entity, a_PreviousVehicle); +} + + + + + void cProtocolRecognizer::SendDisconnect(const AString & a_Reason) { if (m_Protocol != nullptr) @@ -196,14 +240,9 @@ void cProtocolRecognizer::SendDisconnect(const AString & a_Reason) } else { - // This is used when the client sends a server-ping, respond with the default packet: - static const int Packet = 0xff; // PACKET_DISCONNECT - SendData(reinterpret_cast(&Packet), 1); // WriteByte() - - auto UTF16 = UTF8ToRawBEUTF16(a_Reason); - static const u_short Size = htons(static_cast(UTF16.size())); - SendData(reinterpret_cast(&Size), 2); // WriteShort() - SendData(reinterpret_cast(UTF16.data()), UTF16.size() * sizeof(char16_t)); // WriteString() + AString Message = Printf("{\"text\":\"%s\"}", EscapeString(a_Reason).c_str()); + cPacketizer Pkt(*this, 0x00); // Disconnect packet (in login state) + Pkt.WriteString(Message); } } @@ -967,88 +1006,159 @@ bool cProtocolRecognizer::TryRecognizeLengthedProtocol(UInt32 a_PacketLengthRema return false; } m_Client->SetProtocolVersion(ProtocolVersion); + AString ServerAddress; + UInt16 ServerPort; + UInt32 NextState; + if (!m_Buffer.ReadVarUTF8String(ServerAddress)) + { + return false; + } + if (!m_Buffer.ReadBEUInt16(ServerPort)) + { + return false; + } + if (!m_Buffer.ReadVarInt(NextState)) + { + return false; + } + m_Buffer.CommitRead(); switch (ProtocolVersion) { case PROTO_VERSION_1_7_2: { - AString ServerAddress; - UInt16 ServerPort; - UInt32 NextState; - if (!m_Buffer.ReadVarUTF8String(ServerAddress)) - { - break; - } - if (!m_Buffer.ReadBEUInt16(ServerPort)) - { - break; - } - if (!m_Buffer.ReadVarInt(NextState)) - { - break; - } - m_Buffer.CommitRead(); m_Protocol = new cProtocol172(m_Client, ServerAddress, ServerPort, NextState); return true; } case PROTO_VERSION_1_7_6: { - AString ServerAddress; - UInt16 ServerPort; - UInt32 NextState; - if (!m_Buffer.ReadVarUTF8String(ServerAddress)) - { - break; - } - if (!m_Buffer.ReadBEUInt16(ServerPort)) - { - break; - } - if (!m_Buffer.ReadVarInt(NextState)) - { - break; - } - m_Buffer.CommitRead(); m_Protocol = new cProtocol176(m_Client, ServerAddress, ServerPort, NextState); return true; } case PROTO_VERSION_1_8_0: { - AString ServerAddress; - UInt16 ServerPort; - UInt32 NextState; - if (!m_Buffer.ReadVarUTF8String(ServerAddress)) - { - break; - } - if (!m_Buffer.ReadBEUInt16(ServerPort)) + m_Buffer.CommitRead(); + m_Protocol = new cProtocol180(m_Client, ServerAddress, ServerPort, NextState); + return true; + } + case PROTO_VERSION_1_9_0: + { + m_Protocol = new cProtocol190(m_Client, ServerAddress, ServerPort, NextState); + return true; + } + case PROTO_VERSION_1_9_1: + { + m_Protocol = new cProtocol191(m_Client, ServerAddress, ServerPort, NextState); + return true; + } + case PROTO_VERSION_1_9_2: + { + m_Protocol = new cProtocol192(m_Client, ServerAddress, ServerPort, NextState); + return true; + } + case PROTO_VERSION_1_9_4: + { + m_Protocol = new cProtocol194(m_Client, ServerAddress, ServerPort, NextState); + return true; + } + default: + { + LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))", + m_Client->GetIPString().c_str(), ProtocolVersion, ProtocolVersion + ); + if (NextState != 1) { - break; + m_Client->Kick(Printf("Unsupported protocol version %u, please use one of these versions:\n" MCS_CLIENT_VERSIONS, ProtocolVersion)); + return false; } - if (!m_Buffer.ReadVarInt(NextState)) + else { - break; + m_InPingForUnrecognizedVersion = true; + + UInt32 PacketLen; + UInt32 PacketID; + if (!m_Buffer.ReadVarInt32(PacketLen)) + { + return false; + } + if (!m_Buffer.ReadVarInt32(PacketID)) + { + return false; + } + ASSERT(PacketID == 0x00); // Request packet + ASSERT(PacketLen == 1); // No payload except for packet ID + SendPingStatusResponse(); } - m_Buffer.CommitRead(); - m_Protocol = new cProtocol180(m_Client, ServerAddress, ServerPort, NextState); - return true; + return false; } } - LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))", - m_Client->GetIPString().c_str(), ProtocolVersion, ProtocolVersion - ); - m_Client->Kick("Unsupported protocol version"); - return false; } + void cProtocolRecognizer::SendPacket(cPacketizer & a_Pkt) { - // This function should never be called - it needs to exists so that cProtocolRecognizer can be instantiated, - // but the actual sending is done by the internal m_Protocol itself. - LOGWARNING("%s: This function shouldn't ever be called.", __FUNCTION__); - ASSERT(!"Function not to be called"); + // Writes out the packet normally. + UInt32 PacketLen = static_cast(m_OutPacketBuffer.GetUsedSpace()); + AString PacketData, CompressedPacket; + m_OutPacketBuffer.ReadAll(PacketData); + m_OutPacketBuffer.CommitRead(); + + // Compression doesn't apply to this state, send raw data: + m_OutPacketLenBuffer.WriteVarInt32(PacketLen); + AString LengthData; + m_OutPacketLenBuffer.ReadAll(LengthData); + SendData(LengthData.data(), LengthData.size()); + + // Send the packet's payload + m_OutPacketLenBuffer.CommitRead(); + SendData(PacketData.data(), PacketData.size()); +} + + + + + +void cProtocolRecognizer::SendPingStatusResponse(void) +{ + 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"] = "Cuberite " MCS_CLIENT_VERSIONS; + Version["protocol"] = 0; // Force client to think this is an invalid version (no other good default) + + // 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); } diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 89dfaec38..ea04b1a92 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -18,8 +18,8 @@ // Adjust these if a new protocol is added or an old one is removed: -#define MCS_CLIENT_VERSIONS "1.7.x, 1.8.x" -#define MCS_PROTOCOL_VERSIONS "4, 5, 47" +#define MCS_CLIENT_VERSIONS "1.7.x, 1.8.x, 1.9.x" +#define MCS_PROTOCOL_VERSIONS "4, 5, 47, 107, 108, 109, 110" @@ -36,6 +36,10 @@ public: PROTO_VERSION_1_7_2 = 4, PROTO_VERSION_1_7_6 = 5, PROTO_VERSION_1_8_0 = 47, + PROTO_VERSION_1_9_0 = 107, + PROTO_VERSION_1_9_1 = 108, + PROTO_VERSION_1_9_2 = 109, + PROTO_VERSION_1_9_4 = 110, } ; cProtocolRecognizer(cClientHandle * a_Client); @@ -48,7 +52,7 @@ public: virtual void DataReceived(const char * a_Data, size_t a_Size) override; /** Sending stuff to clients (alphabetically sorted): */ - virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle) override; + virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle) override; virtual void SendBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) override; virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; @@ -58,6 +62,7 @@ public: virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; virtual void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player) override; virtual void SendDestroyEntity (const cEntity & a_Entity) override; + virtual void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) override; virtual void SendDisconnect (const AString & a_Reason) override; virtual void SendEditSign (int a_BlockX, int a_BlockY, int a_BlockZ) override; ///< Request the client to open up the sign editor for the sign (1.6+) virtual void SendEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) override; @@ -133,6 +138,8 @@ public: virtual void SendData(const char * a_Data, size_t a_Size) override; + void SendPingStatusResponse(void); + protected: /** The recognized protocol */ cProtocol * m_Protocol; @@ -140,6 +147,9 @@ protected: /** Buffer for the incoming data until we recognize the protocol */ cByteBuffer m_Buffer; + /** Is a server list ping for an unrecognized version currently occuring? */ + bool m_InPingForUnrecognizedVersion; + /** Tries to recognize protocol based on m_Buffer contents; returns true if recognized */ bool TryRecognizeProtocol(void); diff --git a/src/World.cpp b/src/World.cpp index e42e55825..d05f90365 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -2263,9 +2263,9 @@ bool cWorld::TryGetHeight(int a_BlockX, int a_BlockZ, int & a_Height) -void cWorld::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle) +void cWorld::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { - return m_ChunkMap->BroadcastAttachEntity(a_Entity, a_Vehicle); + m_ChunkMap->BroadcastAttachEntity(a_Entity, a_Vehicle); } @@ -2353,6 +2353,15 @@ void cWorld::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHandl +void cWorld::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + m_ChunkMap->BroadcastDetachEntity(a_Entity, a_PreviousVehicle); +} + + + + + void cWorld::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude) { m_ChunkMap->BroadcastEntityEffect(a_Entity, a_EffectID, a_Amplifier, a_Duration, a_Exclude); diff --git a/src/World.h b/src/World.h index bf5dd3d1e..eafe7b9b6 100644 --- a/src/World.h +++ b/src/World.h @@ -173,7 +173,7 @@ public: // Broadcast respective packets to all clients of the chunk where the event is taking place // (Please keep these alpha-sorted) - void BroadcastAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle); + void BroadcastAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle); void BroadcastBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, Byte a_Byte1, Byte a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude = nullptr); // tolua_export void BroadcastBlockBreakAnimation(UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage, const cClientHandle * a_Exclude = nullptr); void BroadcastBlockEntity (int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = nullptr); ///< If there is a block entity at the specified coods, sends it to all clients except a_Exclude @@ -191,6 +191,7 @@ public: void BroadcastCollectEntity (const cEntity & a_Pickup, const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); void BroadcastDestroyEntity (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); + void BroadcastDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle); void BroadcastEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityEquipment (const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityHeadLook (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); -- cgit v1.2.3