From 0e3b3be7662f03b7487408f70548eaca1188da3d Mon Sep 17 00:00:00 2001 From: Mattes D Date: Thu, 15 Dec 2016 20:21:43 +0100 Subject: Initial support for the 1.11 protocol. --- src/Protocol/CMakeLists.txt | 20 +- src/Protocol/ChunkDataSerializer.cpp | 10 +- src/Protocol/Protocol.h | 2 +- src/Protocol/Protocol110x.cpp | 884 ------- src/Protocol/Protocol110x.h | 34 - src/Protocol/Protocol18x.cpp | 3629 ---------------------------- src/Protocol/Protocol18x.h | 268 --- src/Protocol/Protocol19x.cpp | 4411 ---------------------------------- src/Protocol/Protocol19x.h | 360 --- src/Protocol/ProtocolRecognizer.cpp | 36 +- src/Protocol/ProtocolRecognizer.h | 7 +- src/Protocol/Protocol_1_10.cpp | 883 +++++++ src/Protocol/Protocol_1_10.h | 37 + src/Protocol/Protocol_1_11.cpp | 189 ++ src/Protocol/Protocol_1_11.h | 41 + src/Protocol/Protocol_1_8.cpp | 3623 ++++++++++++++++++++++++++++ src/Protocol/Protocol_1_8.h | 267 ++ src/Protocol/Protocol_1_9.cpp | 4406 +++++++++++++++++++++++++++++++++ src/Protocol/Protocol_1_9.h | 358 +++ 19 files changed, 9846 insertions(+), 9619 deletions(-) delete mode 100644 src/Protocol/Protocol110x.cpp delete mode 100644 src/Protocol/Protocol110x.h delete mode 100644 src/Protocol/Protocol18x.cpp delete mode 100644 src/Protocol/Protocol18x.h delete mode 100644 src/Protocol/Protocol19x.cpp delete mode 100644 src/Protocol/Protocol19x.h create mode 100644 src/Protocol/Protocol_1_10.cpp create mode 100644 src/Protocol/Protocol_1_10.h create mode 100644 src/Protocol/Protocol_1_11.cpp create mode 100644 src/Protocol/Protocol_1_11.h create mode 100644 src/Protocol/Protocol_1_8.cpp create mode 100644 src/Protocol/Protocol_1_8.h create mode 100644 src/Protocol/Protocol_1_9.cpp create mode 100644 src/Protocol/Protocol_1_9.h (limited to 'src/Protocol') diff --git a/src/Protocol/CMakeLists.txt b/src/Protocol/CMakeLists.txt index 13afb76f4..e8e485ddf 100644 --- a/src/Protocol/CMakeLists.txt +++ b/src/Protocol/CMakeLists.txt @@ -7,9 +7,10 @@ SET (SRCS ChunkDataSerializer.cpp MojangAPI.cpp Packetizer.cpp - Protocol18x.cpp - Protocol19x.cpp - Protocol110x.cpp + Protocol_1_8.cpp + Protocol_1_9.cpp + Protocol_1_10.cpp + Protocol_1_11.cpp ProtocolRecognizer.cpp ) @@ -19,16 +20,17 @@ SET (HDRS MojangAPI.h Packetizer.h Protocol.h - Protocol18x.h - Protocol19x.h - Protocol110x.h + Protocol_1_8.h + Protocol_1_9.h + Protocol_1_10.h + Protocol_1_11.h ProtocolRecognizer.h ) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(Protocol19x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum -Wno-error=switch") - set_source_files_properties(Protocol18x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum -Wno-error=switch") - set_source_files_properties(Protocol110x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch") + set_source_files_properties(Protocol_1_9.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum -Wno-error=switch") + set_source_files_properties(Protocol_1_8.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum -Wno-error=switch") + set_source_files_properties(Protocol_1_10.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch") endif() if (NOT MSVC) diff --git a/src/Protocol/ChunkDataSerializer.cpp b/src/Protocol/ChunkDataSerializer.cpp index c7fd504a1..bbedb3c59 100644 --- a/src/Protocol/ChunkDataSerializer.cpp +++ b/src/Protocol/ChunkDataSerializer.cpp @@ -9,8 +9,8 @@ #include "ChunkDataSerializer.h" #include "zlib/zlib.h" #include "ByteBuffer.h" -#include "Protocol18x.h" -#include "Protocol19x.h" +#include "Protocol_1_8.h" +#include "Protocol_1_9.h" @@ -111,7 +111,7 @@ void cChunkDataSerializer::Serialize47(AString & a_Data, int a_ChunkX, int a_Chu cByteBuffer Buffer(20); if (PacketData.size() >= 256) { - if (!cProtocol180::CompressPacket(PacketData, a_Data)) + if (!cProtocol_1_8_0::CompressPacket(PacketData, a_Data)) { ASSERT(!"Packet compression failed."); a_Data.clear(); @@ -254,7 +254,7 @@ void cChunkDataSerializer::Serialize107(AString & a_Data, int a_ChunkX, int a_Ch cByteBuffer Buffer(20); if (PacketData.size() >= 256) { - if (!cProtocol190::CompressPacket(PacketData, a_Data)) + if (!cProtocol_1_9_0::CompressPacket(PacketData, a_Data)) { ASSERT(!"Packet compression failed."); a_Data.clear(); @@ -400,7 +400,7 @@ void cChunkDataSerializer::Serialize110(AString & a_Data, int a_ChunkX, int a_Ch cByteBuffer Buffer(20); if (PacketData.size() >= 256) { - if (!cProtocol190::CompressPacket(PacketData, a_Data)) + if (!cProtocol_1_9_0::CompressPacket(PacketData, a_Data)) { ASSERT(!"Packet compression failed."); a_Data.clear(); diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 137a47acc..d20c37a29 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -74,7 +74,7 @@ public: virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) = 0; virtual void SendChatRaw (const AString & a_MessageRaw, eChatType a_Type) = 0; 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 SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) = 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; diff --git a/src/Protocol/Protocol110x.cpp b/src/Protocol/Protocol110x.cpp deleted file mode 100644 index 48ea0c6ba..000000000 --- a/src/Protocol/Protocol110x.cpp +++ /dev/null @@ -1,884 +0,0 @@ - -// Protocol110x.cpp - -/* -Implements the 1.10.x protocol classes: - - cProtocol1100 - - release 1.10.0 protocol (#210) -(others may be added later in the future for the 1.10 release series) -*/ - -#include "Globals.h" -#include "Protocol110x.h" -#include "Packetizer.h" - -#include "json/json.h" - -#include "../Root.h" -#include "../Server.h" - -#include "../Entities/Boat.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 "../Mobs/IncludeAllMonsters.h" - -#include "Bindings/PluginManager.h" - - - - - -// The disabled error is intended, since the Metadata have overlapping indexes -// based on the type of the Entity. -// -// IMPORTANT: The enum is used to automate the sequential counting of the -// Metadata indexes. Adding a new enum value causes the following values to -// increase their index. Therefore the ordering of the enum values is VERY important! -#ifdef __clang__ - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wduplicate-enum" -#endif - -namespace Metadata -{ - enum Metadata_Index - { - // Entity - ENTITY_FLAGS, - ENTITY_AIR, - ENTITY_CUSTOM_NAME, - ENTITY_CUSTOM_NAME_VISIBLE, - ENTITY_SILENT, - ENTITY_NO_GRAVITY, - _ENTITY_NEXT, // Used by descendants - - // Potion - POTION_THROWN = _ENTITY_NEXT, - - // FallingBlock - FALLING_BLOCK_POSITION = _ENTITY_NEXT, - - // AreaEffectCloud - AREA_EFFECT_CLOUD_RADIUS = _ENTITY_NEXT, - AREA_EFFECT_CLOUD_COLOR, - AREA_EFFECT_CLOUD_SINGLE_POINT_EFFECT, - AREA_EFFECT_CLOUD_PARTICLE_ID, - AREA_EFFECT_CLOUD_PARTICLE_PARAMETER1, - AREA_EFFECT_CLOUD_PARTICLE_PARAMETER2, - - // Arrow - ARROW_CRITICAL = _ENTITY_NEXT, - _ARROW_NEXT, - - // TippedArrow - TIPPED_ARROW_COLOR = _ARROW_NEXT, - - // Boat - BOAT_LAST_HIT_TIME = _ENTITY_NEXT, - BOAT_FORWARD_DIRECTION, - BOAT_DAMAGE_TAKEN, - BOAT_TYPE, - BOAT_RIGHT_PADDLE_TURNING, - BOAT_LEFT_PADDLE_TURNING, - - // EnderCrystal - ENDER_CRYSTAL_BEAM_TARGET = _ENTITY_NEXT, - ENDER_CRYSTAL_SHOW_BOTTOM, - - // Fireball - _FIREBALL_NEXT = _ENTITY_NEXT, - - // WitherSkull - WITHER_SKULL_INVULNERABLE = _FIREBALL_NEXT, - - // Fireworks - FIREWORK_INFO = _ENTITY_NEXT, - - // Hanging - _HANGING_NEXT = _ENTITY_NEXT, - - // ItemFrame - ITEM_FRAME_ITEM = _HANGING_NEXT, - ITEM_FRAME_ROTATION, - - // Item - ITEM_ITEM = _ENTITY_NEXT, - - // Living - LIVING_ACTIVE_HAND = _ENTITY_NEXT, - LIVING_HEALTH, - LIVING_POTION_EFFECT_COLOR, - LIVING_POTION_EFFECT_AMBIENT, - LIVING_NUMBER_OF_ARROWS, - _LIVING_NEXT, - - // Player - PLAYER_ADDITIONAL_HEARTHS = _LIVING_NEXT, - PLAYER_SCORE, - PLAYER_DISPLAYED_SKIN_PARTS, - PLAYER_MAIN_HAND, - - // ArmorStand - ARMOR_STAND_STATUS = _LIVING_NEXT, - ARMOR_STAND_HEAD_ROTATION, - ARMOR_STAND_BODY_ROTATION, - ARMOR_STAND_LEFT_ARM_ROTATION, - ARMOR_STAND_RIGHT_ARM_ROTATION, - ARMOR_STAND_LEFT_LEG_ROTATION, - ARMOR_STAND_RIGHT_LEG_ROTATION, - - // Insentient - INSENTIENT_STATUS = _LIVING_NEXT, - _INSENTIENT_NEXT, - - // Ambient - _AMBIENT_NEXT = _INSENTIENT_NEXT, - - // Bat - BAT_HANGING = _AMBIENT_NEXT, - - // Creature - _CREATURE_NEXT = _INSENTIENT_NEXT, - - // Ageable - AGEABLE_BABY = _CREATURE_NEXT, - _AGEABLE_NEXT, - - // PolarBear - POLAR_BEAR_STANDING = _AGEABLE_NEXT, - - // Animal - _ANIMAL_NEXT = _AGEABLE_NEXT, - - // Horse - HORSE_STATUS = _ANIMAL_NEXT, - HORSE_TYPE, - HORSE_VARIANT, - HORSE_OWNER, - HORSE_ARMOR, - - // Pig - PIG_HAS_SADDLE = _ANIMAL_NEXT, - - // Rabbit - RABBIT_TYPE = _ANIMAL_NEXT, - - // Sheep - SHEEP_STATUS = _ANIMAL_NEXT, - - // TameableAnimal - TAMEABLE_ANIMAL_STATUS = _ANIMAL_NEXT, - TAMEABLE_ANIMAL_OWNER, - _TAMEABLE_NEXT, - - // Ocelot - OCELOT_TYPE = _TAMEABLE_NEXT, - - // Wolf - WOLF_DAMAGE_TAKEN = _TAMEABLE_NEXT, - WOLF_BEGGING, - WOLF_COLLAR_COLOR, - - // Villager - VILLAGER_PROFESSION = _AGEABLE_NEXT, - - // Golem - _GOLEM_NEXT = _CREATURE_NEXT, - - // IronGolem - IRON_GOLEM_PLAYER_CREATED = _GOLEM_NEXT, - - // Shulker - SHULKER_FACING_DIRECTION = _GOLEM_NEXT, - SHULKER_ATTACHMENT_FALLING_BLOCK_POSITION, - SHULKER_SHIELD_HEIGHT, - - // Monster - _MONSTER_NEXT = _CREATURE_NEXT, - - // Blaze - BLAZE_ON_FIRE = _MONSTER_NEXT, - - // Creeper - CREEPER_STATE = _MONSTER_NEXT, - CREEPER_POWERED, - CREEPER_IGNITED, - - // Guardian - GUARDIAN_STATUS = _MONSTER_NEXT, - GUARDIAN_TARGET, - - // Skeleton - SKELETON_TYPE = _MONSTER_NEXT, - SKELETON_ARMS_SWINGING, - - // Spider - SPIDER_CLIMBING = _MONSTER_NEXT, - - // Witch - WITCH_AGGRESIVE = _MONSTER_NEXT, - - // Wither - WITHER_FIRST_HEAD_TARGET = _MONSTER_NEXT, - WITHER_SECOND_HEAD_TARGET, - WITHER_THIRD_HEAD_TARGET, - WITHER_INVULNERABLE_TIMER, - - // Zombie - ZOMBIE_IS_BABY = _MONSTER_NEXT, - ZOMBIE_TYPE, - ZOMBIE_CONVERTING, - ZOMBIE_HANDS_RISED_UP, - - // Enderman - ENDERMAN_CARRIED_BLOCK = _MONSTER_NEXT, - ENDERMAN_SCREAMING, - - // EnderDragon - ENDER_DRAGON_DRAGON_PHASE = _INSENTIENT_NEXT, - - // Flying - _FLYING_NEXT = _INSENTIENT_NEXT, - - // Ghast - GHAST_ATTACKING = _FLYING_NEXT, - - // Slime - SLIME_SIZE = _INSENTIENT_NEXT, - - // Minecart - MINECART_SHAKING_POWER = _ENTITY_NEXT, - MINECART_SHAKING_DIRECTION, - MINECART_SHAKING_MULTIPLIER, - MINECART_BLOCK_ID_META, - MINECART_BLOCK_Y, - MINECART_SHOW_BLOCK, - _MINECART_NEXT, - - // MinecartCommandBlock - MINECART_COMMAND_BLOCK_COMMAND = _MINECART_NEXT, - MINECART_COMMAND_BLOCK_LAST_OUTPUT, - - // MinecartFurnace - MINECART_FURNACE_POWERED = _MINECART_NEXT, - - // TNTPrimed - TNT_PRIMED_FUSE_TIME = _ENTITY_NEXT, - }; -} - -#ifdef __clang__ - #pragma clang diagnostic pop // Restore ignored clang errors -#endif - - - - -cProtocol1100::cProtocol1100(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : - super(a_Client, a_ServerAddress, a_ServerPort, a_State) -{ -} - - - - - -void cProtocol1100::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(FloorC(a_X * 8.0)); - Pkt.WriteBEInt32(FloorC(a_Y * 8.0)); - Pkt.WriteBEInt32(FloorC(a_Z * 8.0)); - Pkt.WriteBEFloat(a_Volume); - Pkt.WriteBEFloat(a_Pitch); -} - - - - - -void cProtocol1100::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.10"; - Version["protocol"] = 210; - - // 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 cProtocol1100::WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) -{ - using namespace Metadata; - - // 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(ENTITY_FLAGS); // Index - a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); // Type - a_Pkt.WriteBEInt8(Flags); - - switch (a_Entity.GetEntityType()) - { - case cEntity::etPlayer: - { - auto & Player = reinterpret_cast(a_Entity); - - // TODO Set player custom name to their name. - // Then it's possible to move the custom name of mobs to the entities - // and to remove the "special" player custom name. - a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME); - a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); - a_Pkt.WriteString(Player.GetName()); - - a_Pkt.WriteBEUInt8(LIVING_HEALTH); - a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); - a_Pkt.WriteBEFloat(static_cast(Player.GetHealth())); - break; - } - case cEntity::etPickup: - { - a_Pkt.WriteBEUInt8(ITEM_ITEM); - a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); - WriteItem(a_Pkt, reinterpret_cast(a_Entity).GetItem()); - break; - } - case cEntity::etMinecart: - { - a_Pkt.WriteBEUInt8(MINECART_SHAKING_POWER); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - - // The following expression makes Minecarts shake more with less health or higher damage taken - auto & Minecart = reinterpret_cast(a_Entity); - auto maxHealth = a_Entity.GetMaxHealth(); - auto curHealth = a_Entity.GetHealth(); - a_Pkt.WriteVarInt32(static_cast((maxHealth - curHealth) * Minecart.LastDamage() * 4)); - - a_Pkt.WriteBEUInt8(MINECART_SHAKING_DIRECTION); // (doesn't seem to effect anything) - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(1); - - a_Pkt.WriteBEUInt8(MINECART_SHAKING_MULTIPLIER); // or 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(MINECART_BLOCK_ID_META); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - int Content = MinecartContent.m_ItemType; - Content |= MinecartContent.m_ItemDamage << 8; - a_Pkt.WriteVarInt32(static_cast(Content)); - - a_Pkt.WriteBEUInt8(MINECART_BLOCK_Y); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(RideableMinecart.GetBlockHeight())); - - a_Pkt.WriteBEUInt8(MINECART_SHOW_BLOCK); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(true); - } - } - else if (Minecart.GetPayload() == cMinecart::mpFurnace) - { - a_Pkt.WriteBEUInt8(MINECART_FURNACE_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(ARROW_CRITICAL); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); - a_Pkt.WriteBEInt8(reinterpret_cast(Projectile).IsCritical() ? 1 : 0); - break; - } - case cProjectileEntity::pkFirework: - { - a_Pkt.WriteBEUInt8(FIREWORK_INFO); // 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(POTION_THROWN); // 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::etBoat: - { - auto & Boat = reinterpret_cast(a_Entity); - - a_Pkt.WriteBEInt8(BOAT_LAST_HIT_TIME); - a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteBEInt32(Boat.GetLastDamage()); - - a_Pkt.WriteBEInt8(BOAT_FORWARD_DIRECTION); - a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteBEInt32(Boat.GetForwardDirection()); - - a_Pkt.WriteBEInt8(BOAT_DAMAGE_TAKEN); - a_Pkt.WriteBEInt8(METADATA_TYPE_FLOAT); - a_Pkt.WriteBEFloat(Boat.GetDamageTaken()); - - a_Pkt.WriteBEInt8(BOAT_TYPE); - a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteBEInt32(Boat.GetType()); - - a_Pkt.WriteBEInt8(BOAT_RIGHT_PADDLE_TURNING); - a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Boat.IsRightPaddleUsed()); - - a_Pkt.WriteBEInt8(BOAT_LEFT_PADDLE_TURNING); - a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Boat.IsLeftPaddleUsed()); - - break; - } // case etBoat - - case cEntity::etItemFrame: - { - auto & Frame = reinterpret_cast(a_Entity); - a_Pkt.WriteBEUInt8(ITEM_FRAME_ITEM); - a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); - WriteItem(a_Pkt, Frame.GetItem()); - a_Pkt.WriteBEUInt8(ITEM_FRAME_ROTATION); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(Frame.GetItemRotation()); - break; - } // case etItemFrame - - default: - { - break; - } - } -} - - - - - -void cProtocol1100::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) -{ - using namespace Metadata; - - // 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(ENTITY_CUSTOM_NAME); - a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); - a_Pkt.WriteString(a_Mob.GetCustomName()); - - a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME_VISIBLE); // Custom name always visible - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(a_Mob.IsCustomNameAlwaysVisible()); - } - - a_Pkt.WriteBEUInt8(LIVING_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(BAT_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(CREEPER_STATE); // (idle or "blowing") - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(Creeper.IsBlowing() ? 1 : static_cast(-1)); - - a_Pkt.WriteBEUInt8(CREEPER_POWERED); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Creeper.IsCharged()); - - a_Pkt.WriteBEUInt8(CREEPER_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(ENDERMAN_CARRIED_BLOCK); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BLOCKID); - UInt32 Carried = 0; - Carried |= static_cast(Enderman.GetCarriedBlock() << 4); - Carried |= Enderman.GetCarriedMeta(); - a_Pkt.WriteVarInt32(Carried); - - a_Pkt.WriteBEUInt8(ENDERMAN_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(GHAST_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(HORSE_STATUS); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); - a_Pkt.WriteBEInt8(Flags); - - a_Pkt.WriteBEUInt8(HORSE_TYPE); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(Horse.GetHorseType())); - - a_Pkt.WriteBEUInt8(HORSE_VARIANT); // Color / style - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - int Appearance = 0; - Appearance = Horse.GetHorseColor(); - Appearance |= Horse.GetHorseStyle() << 8; - a_Pkt.WriteVarInt32(static_cast(Appearance)); - - a_Pkt.WriteBEUInt8(HORSE_ARMOR); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(Horse.GetHorseArmour())); - - a_Pkt.WriteBEUInt8(AGEABLE_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(SLIME_SIZE); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(MagmaCube.GetSize())); - break; - } // case mtMagmaCube - - case mtOcelot: - { - auto & Ocelot = reinterpret_cast(a_Mob); - - a_Pkt.WriteBEUInt8(AGEABLE_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(AGEABLE_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(AGEABLE_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(AGEABLE_BABY); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Pig.IsBaby()); - - a_Pkt.WriteBEUInt8(PIG_HAS_SADDLE); - 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(AGEABLE_BABY); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Sheep.IsBaby()); - - a_Pkt.WriteBEUInt8(SHEEP_STATUS); - 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(AGEABLE_BABY); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Rabbit.IsBaby()); - - a_Pkt.WriteBEUInt8(RABBIT_TYPE); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(Rabbit.GetRabbitType())); - break; - } // case mtRabbit - - case mtSkeleton: - { - auto & Skeleton = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(SKELETON_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(SLIME_SIZE); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(Slime.GetSize())); - break; - } // case mtSlime - - case mtVillager: - { - auto & Villager = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(AGEABLE_BABY); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Villager.IsBaby()); - - a_Pkt.WriteBEUInt8(VILLAGER_PROFESSION); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(Villager.GetVilType())); - break; - } // case mtVillager - - case mtWitch: - { - auto & Witch = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(WITCH_AGGRESIVE); - 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(WITHER_INVULNERABLE_TIMER); - 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(AGEABLE_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(TAMEABLE_ANIMAL_STATUS); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); - a_Pkt.WriteBEInt8(WolfStatus); - - a_Pkt.WriteBEUInt8(WOLF_DAMAGE_TAKEN); - a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); - a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); // TODO Not use the current health - - a_Pkt.WriteBEUInt8(WOLF_BEGGING); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Wolf.IsBegging()); - - a_Pkt.WriteBEUInt8(WOLF_COLLAR_COLOR); - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(Wolf.GetCollarColor())); - break; - } // case mtWolf - - case mtZombie: - { - auto & Zombie = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(AGEABLE_BABY); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Zombie.IsBaby()); - - a_Pkt.WriteBEUInt8(ZOMBIE_TYPE); - 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(ZOMBIE_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(AGEABLE_BABY); - a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(ZombiePigman.IsBaby()); - break; - } // case mtZombiePigman - } // switch (a_Mob.GetType()) -} diff --git a/src/Protocol/Protocol110x.h b/src/Protocol/Protocol110x.h deleted file mode 100644 index 64ef8acb5..000000000 --- a/src/Protocol/Protocol110x.h +++ /dev/null @@ -1,34 +0,0 @@ - -// Protocol110x.h - -/* -Declares the 1.10.x protocol classes: - - cProtocol1100 - - release 1.10.0 protocol (#210) -(others may be added later in the future for the 1.10 release series) -*/ - - - - - -#pragma once - -#include "Protocol19x.h" - -class cProtocol1100 : - public cProtocol194 -{ - typedef cProtocol194 super; - -public: - cProtocol1100(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); - - 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 HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; - -protected: - virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) override; - virtual void WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) override; -}; diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp deleted file mode 100644 index 0679219a9..000000000 --- a/src/Protocol/Protocol18x.cpp +++ /dev/null @@ -1,3629 +0,0 @@ - -// Protocol18x.cpp - -/* -Implements the 1.8.x protocol classes: - - cProtocol180 - - release 1.8.0 protocol (#47) -(others may be added later in the future for the 1.8 release series) -*/ - -#include "Globals.h" -#include "json/json.h" -#include "Protocol18x.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 "../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; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cProtocol180: - -cProtocol180::cProtocol180(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) -{ - - // 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 cProtocol180::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 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.GetUniqueID()); - Pkt.WriteBool(false); -} - - - - - -void cProtocol180::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, 0x24); // Block Action packet - Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); - Pkt.WriteBEInt8(a_Byte1); - Pkt.WriteBEInt8(a_Byte2); - Pkt.WriteVarInt32(a_BlockType); -} - - - - - -void cProtocol180::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, 0x25); // Block Break Animation packet - Pkt.WriteVarInt32(a_EntityID); - Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); - Pkt.WriteBEInt8(a_Stage); -} - - - - - -void cProtocol180::SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x23); // Block Change packet - Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); - Pkt.WriteVarInt32((static_cast(a_BlockType) << 4) | (static_cast(a_BlockMeta) & 15)); -} - - - - - -void cProtocol180::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x22); // Multi Block Change packet - Pkt.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 cProtocol180::SendCameraSetTo(const cEntity & a_Entity) -{ - cPacketizer Pkt(*this, 0x43); // Camera Packet (Attach the camera of a player at another entity in spectator mode) - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); -} - - - - - -void cProtocol180::SendChat(const AString & a_Message, eChatType a_Type) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x02); // Chat Message packet - Pkt.WriteString(Printf("{\"text\":\"%s\"}", EscapeString(a_Message).c_str())); - Pkt.WriteBEInt8(a_Type); -} - - - - - -void cProtocol180::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, 0x02); - Pkt.WriteString(a_Message.CreateJsonString(a_ShouldUseChatPrefixes)); - Pkt.WriteBEInt8(a_Type); -} - - - - - -void cProtocol180::SendChatRaw(const AString & a_MessageRaw, eChatType a_Type) -{ - ASSERT(m_State == 3); // In game mode? - - // Send the json string to the client: - cPacketizer Pkt(*this, 0x02); - Pkt.WriteString(a_MessageRaw); - Pkt.WriteBEInt8(a_Type); -} - - - - - -void cProtocol180::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_8_0, a_ChunkX, a_ChunkZ); - - cCSLock Lock(m_CSPacket); - SendData(ChunkData.data(), ChunkData.size()); -} - - - - - -void cProtocol180::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x0d); // Collect Item packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteVarInt32(a_Player.GetUniqueID()); -} - - - - - -void cProtocol180::SendDestroyEntity(const cEntity & a_Entity) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x13); // Destroy Entities packet - Pkt.WriteVarInt32(1); - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); -} - - - - - -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) - { - 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, 0x40); - Pkt.WriteString(Printf("{\"text\":\"%s\"}", EscapeString(a_Reason).c_str())); - break; - } - } -} - - - - - -void cProtocol180::SendEditSign(int a_BlockX, int a_BlockY, int a_BlockZ) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x36); // Sign Editor Open packet - Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); -} - - - - - -void cProtocol180::SendEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x1D); // 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 cProtocol180::SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x04); // Entity Equipment packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEInt16(a_SlotNum); - WriteItem(Pkt, a_Item); -} - - - - - -void cProtocol180::SendEntityHeadLook(const cEntity & a_Entity) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x19); // Entity Head Look packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteByteAngle(a_Entity.GetHeadYaw()); -} - - - - - -void cProtocol180::SendEntityLook(const cEntity & a_Entity) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x16); // Entity Look packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteByteAngle(a_Entity.GetYaw()); - Pkt.WriteByteAngle(a_Entity.GetPitch()); - Pkt.WriteBool(a_Entity.IsOnGround()); -} - - - - - -void cProtocol180::SendEntityMetadata(const cEntity & a_Entity) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x1c); // Entity Metadata packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - WriteEntityMetadata(Pkt, a_Entity); - Pkt.WriteBEUInt8(0x7f); // The termination byte -} - - - - - -void cProtocol180::SendEntityProperties(const cEntity & a_Entity) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x20); // Entity Properties packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - WriteEntityProperties(Pkt, a_Entity); -} - - - - - -void cProtocol180::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x15); // Entity Relative Move packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEInt8(a_RelX); - Pkt.WriteBEInt8(a_RelY); - Pkt.WriteBEInt8(a_RelZ); - Pkt.WriteBool(a_Entity.IsOnGround()); -} - - - - - -void cProtocol180::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x17); // Entity Look And Relative Move packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEInt8(a_RelX); - Pkt.WriteBEInt8(a_RelY); - Pkt.WriteBEInt8(a_RelZ); - Pkt.WriteByteAngle(a_Entity.GetYaw()); - Pkt.WriteByteAngle(a_Entity.GetPitch()); - Pkt.WriteBool(a_Entity.IsOnGround()); -} - - - - - -void cProtocol180::SendEntityStatus(const cEntity & a_Entity, char a_Status) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x1a); // Entity Status packet - Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEInt8(a_Status); -} - - - - - -void cProtocol180::SendEntityVelocity(const cEntity & a_Entity) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x12); // 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 cProtocol180::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, 0x27); // 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 cProtocol180::SendGameMode(eGameMode a_GameMode) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x2b); // 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 cProtocol180::SendHealth(void) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x06); // 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 cProtocol180::SendHideTitle(void) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x45); // Title packet - Pkt.WriteVarInt32(3); // Hide title -} - - - - - -void cProtocol180::SendInventorySlot(char a_WindowID, short a_SlotNum, const cItem & a_Item) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x2f); // Set Slot packet - Pkt.WriteBEInt8(a_WindowID); - Pkt.WriteBEInt16(a_SlotNum); - WriteItem(Pkt, a_Item); -} - - - - - -void cProtocol180::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, 0x00); // Keep Alive packet - Pkt.WriteVarInt32(a_PingID); -} - - - - - -void cProtocol180::SendLogin(const cPlayer & a_Player, const cWorld & a_World) -{ - // Send the Join Game packet: - { - cServer * Server = cRoot::Get()->GetServer(); - cPacketizer Pkt(*this, 0x01); // Join Game packet - Pkt.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? - } - - // Send the spawn position: - { - cPacketizer Pkt(*this, 0x05); // Spawn Position packet - Pkt.WritePosition64(FloorC(a_World.GetSpawnX()), FloorC(a_World.GetSpawnY()), FloorC(a_World.GetSpawnZ())); - } - - // Send the server difficulty: - { - cPacketizer Pkt(*this, 0x41); - Pkt.WriteBEInt8(1); - } - - // Send player abilities: - SendPlayerAbilities(); -} - - - - -void cProtocol180::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 cProtocol180::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, 0x10); // Spawn Painting packet - Pkt.WriteVarInt32(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 cProtocol180::SendMapData(const cMap & a_Map, int a_DataStartX, int a_DataStartY) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x34); - Pkt.WriteVarInt32(a_Map.GetID()); - Pkt.WriteBEUInt8(static_cast(a_Map.GetScale())); - - 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 cProtocol180::SendPickupSpawn(const cPickup & a_Pickup) -{ - ASSERT(m_State == 3); // In game mode? - - { - cPacketizer Pkt(*this, 0x0e); // Spawn Object packet - Pkt.WriteVarInt32(a_Pickup.GetUniqueID()); - Pkt.WriteBEUInt8(2); // Type = Pickup - Pkt.WriteFPInt(a_Pickup.GetPosX()); - Pkt.WriteFPInt(a_Pickup.GetPosY()); - Pkt.WriteFPInt(a_Pickup.GetPosZ()); - Pkt.WriteByteAngle(a_Pickup.GetYaw()); - Pkt.WriteByteAngle(a_Pickup.GetPitch()); - Pkt.WriteBEInt32(0); // No object data - } - - { - cPacketizer Pkt(*this, 0x1c); // Entity Metadata packet - Pkt.WriteVarInt32(a_Pickup.GetUniqueID()); - Pkt.WriteBEUInt8((0x05 << 5) | 10); // Slot type + index 10 - WriteItem(Pkt, a_Pickup.GetItem()); - Pkt.WriteBEUInt8(0x7f); // End of metadata - } -} - - - - - -void cProtocol180::SendPlayerAbilities(void) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x39); // 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 cProtocol180::SendEntityAnimation(const cEntity & a_Entity, char a_Animation) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x0b); // Animation packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEInt8(a_Animation); -} - - - - - -void cProtocol180::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, 0x2A); - 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 cProtocol180::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, 0x2A); - 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 cProtocol180::SendPlayerListAddPlayer(const cPlayer & a_Player) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x38); // 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 cProtocol180::SendPlayerListRemovePlayer(const cPlayer & a_Player) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x38); // Playerlist Item packet - Pkt.WriteVarInt32(4); - Pkt.WriteVarInt32(1); - Pkt.WriteUUID(a_Player.GetUUID()); -} - - - - - -void cProtocol180::SendPlayerListUpdateGameMode(const cPlayer & a_Player) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x38); // Playerlist Item packet - Pkt.WriteVarInt32(1); - Pkt.WriteVarInt32(1); - Pkt.WriteUUID(a_Player.GetUUID()); - Pkt.WriteVarInt32(static_cast(a_Player.GetGameMode())); -} - - - - - -void cProtocol180::SendPlayerListUpdatePing(const cPlayer & a_Player) -{ - ASSERT(m_State == 3); // In game mode? - - auto ClientHandle = a_Player.GetClientHandlePtr(); - if (ClientHandle != nullptr) - { - cPacketizer Pkt(*this, 0x38); // Playerlist Item packet - Pkt.WriteVarInt32(2); - Pkt.WriteVarInt32(1); - Pkt.WriteUUID(a_Player.GetUUID()); - Pkt.WriteVarInt32(static_cast(ClientHandle->GetPing())); - } -} - - - - - -void cProtocol180::SendPlayerListUpdateDisplayName(const cPlayer & a_Player, const AString & a_CustomName) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x38); // 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 cProtocol180::SendPlayerMaxSpeed(void) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x20); // 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 cProtocol180::SendPlayerMoveLook(void) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x08); // 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); -} - - - - - -void cProtocol180::SendPlayerPosition(void) -{ - // There is no dedicated packet for this, send the whole thing: - SendPlayerMoveLook(); -} - - - - - -void cProtocol180::SendPlayerSpawn(const cPlayer & a_Player) -{ - // Called to spawn another player for the client - cPacketizer Pkt(*this, 0x0c); // Spawn Player packet - Pkt.WriteVarInt32(a_Player.GetUniqueID()); - Pkt.WriteUUID(cMojangAPI::MakeUUIDShort(a_Player.GetUUID())); - Pkt.WriteFPInt(a_Player.GetPosX()); - Pkt.WriteFPInt(a_Player.GetPosY() + 0.001); // The "+ 0.001" is there because otherwise the player falls through the block they were standing on. - Pkt.WriteFPInt(a_Player.GetPosZ()); - Pkt.WriteByteAngle(a_Player.GetYaw()); - Pkt.WriteByteAngle(a_Player.GetPitch()); - short ItemType = a_Player.GetEquippedItem().IsEmpty() ? 0 : a_Player.GetEquippedItem().m_ItemType; - Pkt.WriteBEInt16(ItemType); - Pkt.WriteBEUInt8((3 << 5) | 6); // Metadata: float + index 6 - Pkt.WriteBEFloat(static_cast(a_Player.GetHealth())); - Pkt.WriteBEUInt8((4 << 5 | (2 & 0x1F)) & 0xFF); - Pkt.WriteString(a_Player.GetName()); - Pkt.WriteBEUInt8(0x7f); // Metadata: end -} - - - - - -void cProtocol180::SendPluginMessage(const AString & a_Channel, const AString & a_Message) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x3f); - Pkt.WriteString(a_Channel); - Pkt.WriteBuf(a_Message.data(), a_Message.size()); -} - - - - - -void cProtocol180::SendRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x1e); - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEUInt8(static_cast(a_EffectID)); -} - - - - - -void cProtocol180::SendResetTitle(void) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x45); // Title packet - Pkt.WriteVarInt32(4); // Reset title -} - - - - - -void cProtocol180::SendRespawn(eDimension a_Dimension) -{ - - cPacketizer Pkt(*this, 0x07); // 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"); -} - - - - - -void cProtocol180::SendExperience(void) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x1f); // Experience Packet - cPlayer * Player = m_Client->GetPlayer(); - Pkt.WriteBEFloat(Player->GetXpPercentage()); - Pkt.WriteVarInt32(static_cast(Player->GetXpLevel())); - Pkt.WriteVarInt32(static_cast(Player->GetCurrentXp())); -} - - - - - -void cProtocol180::SendExperienceOrb(const cExpOrb & a_ExpOrb) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x11); - Pkt.WriteVarInt32(a_ExpOrb.GetUniqueID()); - Pkt.WriteFPInt(a_ExpOrb.GetPosX()); - Pkt.WriteFPInt(a_ExpOrb.GetPosY()); - Pkt.WriteFPInt(a_ExpOrb.GetPosZ()); - Pkt.WriteBEInt16(static_cast(a_ExpOrb.GetReward())); -} - - - - - -void cProtocol180::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x3b); - Pkt.WriteString(a_Name); - Pkt.WriteBEUInt8(a_Mode); - if ((a_Mode == 0) || (a_Mode == 2)) - { - Pkt.WriteString(a_DisplayName); - Pkt.WriteString("integer"); - } -} - - - - - -void cProtocol180::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, 0x3c); - Pkt.WriteString(a_Player); - Pkt.WriteBEUInt8(a_Mode); - Pkt.WriteString(a_Objective); - - if (a_Mode != 1) - { - Pkt.WriteVarInt32(static_cast(a_Score)); - } -} - - - - - -void cProtocol180::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x3d); - Pkt.WriteBEUInt8(static_cast(a_Display)); - Pkt.WriteString(a_Objective); -} - - - - - -void cProtocol180::SendSetSubTitle(const cCompositeChat & a_SubTitle) -{ - SendSetRawSubTitle(a_SubTitle.CreateJsonString(false)); -} - - - - - -void cProtocol180::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 cProtocol180::SendSetTitle(const cCompositeChat & a_Title) -{ - SendSetRawTitle(a_Title.CreateJsonString(false)); -} - - - - - -void cProtocol180::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 cProtocol180::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, 0x29); // Sound Effect packet - Pkt.WriteString(a_SoundName); - 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 cProtocol180::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, 0x28); // Effect packet - Pkt.WriteBEInt32(static_cast(a_EffectID)); - Pkt.WritePosition64(a_SrcX, a_SrcY, a_SrcZ); - Pkt.WriteBEInt32(a_Data); - Pkt.WriteBool(false); -} - - - - - -void cProtocol180::SendSpawnFallingBlock(const cFallingBlock & a_FallingBlock) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x0e); // Spawn Object packet - Pkt.WriteVarInt32(a_FallingBlock.GetUniqueID()); - Pkt.WriteBEUInt8(70); // Falling block - Pkt.WriteFPInt(a_FallingBlock.GetPosX()); - Pkt.WriteFPInt(a_FallingBlock.GetPosY()); - Pkt.WriteFPInt(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 cProtocol180::SendSpawnMob(const cMonster & a_Mob) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x0f); // Spawn Mob packet - Pkt.WriteVarInt32(a_Mob.GetUniqueID()); - Pkt.WriteBEUInt8(static_cast(a_Mob.GetMobType())); - Pkt.WriteFPInt(a_Mob.GetPosX()); - Pkt.WriteFPInt(a_Mob.GetPosY()); - Pkt.WriteFPInt(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(0x7f); // Metadata terminator -} - - - - - -void cProtocol180::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, 0xe); // Spawn Object packet - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteBEUInt8(static_cast(a_ObjectType)); - Pkt.WriteFPInt(PosX); - Pkt.WriteFPInt(a_Entity.GetPosY()); - Pkt.WriteFPInt(PosZ); - Pkt.WriteByteAngle(a_Entity.GetPitch()); - Pkt.WriteByteAngle(Yaw); - Pkt.WriteBEInt32(a_ObjectData); - if (a_ObjectData != 0) - { - 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 cProtocol180::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0xe); // Spawn Object packet - Pkt.WriteVarInt32(a_Vehicle.GetUniqueID()); - Pkt.WriteBEUInt8(static_cast(a_VehicleType)); - Pkt.WriteFPInt(a_Vehicle.GetPosX()); - Pkt.WriteFPInt(a_Vehicle.GetPosY()); - Pkt.WriteFPInt(a_Vehicle.GetPosZ()); - Pkt.WriteByteAngle(a_Vehicle.GetPitch()); - Pkt.WriteByteAngle(a_Vehicle.GetYaw()); - Pkt.WriteBEInt32(a_VehicleSubType); - if (a_VehicleSubType != 0) - { - 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 cProtocol180::SendStatistics(const cStatManager & a_Manager) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x37); - 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 cProtocol180::SendTabCompletionResults(const AStringVector & a_Results) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x3a); // 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 cProtocol180::SendTeleportEntity(const cEntity & a_Entity) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x18); - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WriteFPInt(a_Entity.GetPosX()); - Pkt.WriteFPInt(a_Entity.GetPosY()); - Pkt.WriteFPInt(a_Entity.GetPosZ()); - Pkt.WriteByteAngle(a_Entity.GetYaw()); - Pkt.WriteByteAngle(a_Entity.GetPitch()); - Pkt.WriteBool(a_Entity.IsOnGround()); -} - - - - - -void cProtocol180::SendThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x2c); // Spawn Global Entity packet - Pkt.WriteVarInt32(0); // EntityID = 0, always - Pkt.WriteBEUInt8(1); // Type = Thunderbolt - Pkt.WriteFPInt(a_BlockX); - Pkt.WriteFPInt(a_BlockY); - Pkt.WriteFPInt(a_BlockZ); -} - - - - - -void cProtocol180::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 cProtocol180::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, 0x03); - Pkt.WriteBEInt64(a_WorldAge); - Pkt.WriteBEInt64(a_TimeOfDay); -} - - - - - -void cProtocol180::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x21); // Chunk Data packet - Pkt.WriteBEInt32(a_ChunkX); - Pkt.WriteBEInt32(a_ChunkZ); - Pkt.WriteBool(true); - Pkt.WriteBEInt16(0); // Primary bitmap - Pkt.WriteVarInt32(0); // Data size -} - - - - -void cProtocol180::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x35); // 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 cProtocol180::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, 0x33); - 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 cProtocol180::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x0a); - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); - Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); -} - - - - - -void cProtocol180::SendWeather(eWeather a_Weather) -{ - ASSERT(m_State == 3); // In game mode? - - { - cPacketizer Pkt(*this, 0x2b); // 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 cProtocol180::SendWholeInventory(const cWindow & a_Window) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x30); // 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 cProtocol180::SendWindowClose(const cWindow & a_Window) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x2e); - Pkt.WriteBEInt8(a_Window.GetWindowID()); -} - - - - - -void cProtocol180::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, 0x2d); - 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 cProtocol180::SendWindowProperty(const cWindow & a_Window, short a_Property, short a_Value) -{ - ASSERT(m_State == 3); // In game mode? - - cPacketizer Pkt(*this, 0x31); // Window Property packet - Pkt.WriteBEInt8(a_Window.GetWindowID()); - Pkt.WriteBEInt16(a_Property); - Pkt.WriteBEInt16(a_Value); -} - - - - - -bool cProtocol180::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 cProtocol180::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 cProtocol180::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 cProtocol180::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("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.8: 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("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 cProtocol180::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: HandlePacketKeepAlive (a_ByteBuffer); return true; - case 0x01: HandlePacketChatMessage (a_ByteBuffer); return true; - case 0x02: HandlePacketUseEntity (a_ByteBuffer); return true; - case 0x03: HandlePacketPlayer (a_ByteBuffer); return true; - case 0x04: HandlePacketPlayerPos (a_ByteBuffer); return true; - case 0x05: HandlePacketPlayerLook (a_ByteBuffer); return true; - case 0x06: HandlePacketPlayerPosLook (a_ByteBuffer); return true; - case 0x07: HandlePacketBlockDig (a_ByteBuffer); return true; - case 0x08: HandlePacketBlockPlace (a_ByteBuffer); return true; - case 0x09: HandlePacketSlotSelect (a_ByteBuffer); return true; - case 0x0a: HandlePacketAnimation (a_ByteBuffer); return true; - case 0x0b: HandlePacketEntityAction (a_ByteBuffer); return true; - case 0x0c: HandlePacketSteerVehicle (a_ByteBuffer); return true; - case 0x0d: HandlePacketWindowClose (a_ByteBuffer); return true; - case 0x0e: HandlePacketWindowClick (a_ByteBuffer); return true; - case 0x0f: // Confirm transaction - not used in MCS - case 0x10: HandlePacketCreativeInventoryAction(a_ByteBuffer); return true; - case 0x11: HandlePacketEnchantItem (a_ByteBuffer); return true; - case 0x12: HandlePacketUpdateSign (a_ByteBuffer); return true; - case 0x13: HandlePacketPlayerAbilities (a_ByteBuffer); return true; - case 0x14: HandlePacketTabComplete (a_ByteBuffer); return true; - case 0x15: HandlePacketClientSettings (a_ByteBuffer); return true; - case 0x16: HandlePacketClientStatus (a_ByteBuffer); return true; - case 0x17: HandlePacketPluginMessage (a_ByteBuffer); return true; - case 0x18: HandlePacketSpectate (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 cProtocol180::HandlePacketStatusPing(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Timestamp); - - cPacketizer Pkt(*this, 0x01); // Ping packet - Pkt.WriteBEInt64(Timestamp); -} - - - - - -void cProtocol180::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) -{ - cServer * Server = cRoot::Get()->GetServer(); - AString ServerDescription = Server->GetDescription(); - int NumPlayers = Server->GetNumPlayers(); - int MaxPlayers = Server->GetMaxPlayers(); - AString Favicon = Server->GetFaviconData(); - cRoot::Get()->GetPluginManager()->CallHookServerPing(*m_Client, ServerDescription, NumPlayers, MaxPlayers, Favicon); - - // Version: - Json::Value Version; - Version["name"] = "Cuberite 1.8"; - Version["protocol"] = 47; - - // Players: - Json::Value Players; - Players["online"] = NumPlayers; - Players["max"] = MaxPlayers; - // TODO: Add "sample" - - // Description: - Json::Value Description; - Description["text"] = ServerDescription.c_str(); - - // Create the response: - Json::Value ResponseValue; - ResponseValue["version"] = Version; - ResponseValue["players"] = Players; - ResponseValue["description"] = Description; - if (!Favicon.empty()) - { - ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); - } - - Json::StyledWriter Writer; - AString Response = Writer.write(ResponseValue); - - cPacketizer Pkt(*this, 0x00); // Response packet - Pkt.WriteString(Response); -} - - - - - -void cProtocol180::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 cProtocol180::HandlePacketLoginStart(cByteBuffer & a_ByteBuffer) -{ - AString Username; - if (!a_ByteBuffer.ReadVarUTF8String(Username)) - { - m_Client->Kick("Bad username"); - return; - } - - if (!m_Client->HandleHandshake(Username)) - { - // The client is not welcome here, they have been sent a Kick packet already - return; - } - - cServer * Server = cRoot::Get()->GetServer(); - // If auth is required, then send the encryption request: - if (Server->ShouldAuthenticate()) - { - cPacketizer Pkt(*this, 0x01); - Pkt.WriteString(Server->GetServerID()); - const AString & PubKeyDer = Server->GetPublicKeyDER(); - Pkt.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 cProtocol180::HandlePacketAnimation(cByteBuffer & a_ByteBuffer) -{ - m_Client->HandleAnimation(0); // Packet exists solely for arm-swing notification -} - - - - - -void cProtocol180::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, ReadBEInt8, Int8, Face); - m_Client->HandleLeftClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), Status); -} - - - - - -void cProtocol180::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) -{ - int BlockX, BlockY, BlockZ; - if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) - { - return; - } - - HANDLE_READ(a_ByteBuffer, ReadBEInt8, Int8, Face); - - cItem Item; - ReadItem(a_ByteBuffer, Item, 3); - - 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 cProtocol180::HandlePacketChatMessage(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Message); - m_Client->HandleChat(Message); -} - - - - - -void cProtocol180::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); - - m_Client->SetLocale(Locale); - m_Client->SetViewDistance(ViewDistance); - // TODO: Handle other values -} - - - - - -void cProtocol180::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 cProtocol180::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 cProtocol180::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 cProtocol180::HandlePacketKeepAlive(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, KeepAliveID); - m_Client->HandleKeepAlive(KeepAliveID); -} - - - - - -void cProtocol180::HandlePacketPlayer(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - // TODO: m_Client->HandlePlayerOnGround(IsOnGround); -} - - - - - -void cProtocol180::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 cProtocol180::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 cProtocol180::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 cProtocol180::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 cProtocol180::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 cProtocol180::HandlePacketSlotSelect(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, SlotNum); - m_Client->HandleSlotSelected(SlotNum); -} - - - - - -void cProtocol180::HandlePacketSpectate(cByteBuffer &a_ByteBuffer) -{ - AString playerUUID; - if (!a_ByteBuffer.ReadUUID(playerUUID)) - { - return; - } - - m_Client->HandleSpectate(playerUUID); -} - - - - - -void cProtocol180::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 cProtocol180::HandlePacketTabComplete(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Text); - HANDLE_READ(a_ByteBuffer, ReadBool, bool, HasPosition); - - if (HasPosition) - { - HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Position); - } - - m_Client->HandleTabCompletion(Text); -} - - - - - -void cProtocol180::HandlePacketUpdateSign(cByteBuffer & a_ByteBuffer) -{ - int BlockX, BlockY, BlockZ; - if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) - { - return; - } - - AString Lines[4]; - Json::Value root; - Json::Reader reader; - for (int i = 0; i < 4; i++) - { - HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Line); - if (reader.parse(Line, root, false)) - { - Lines[i] = root.asString(); - } - } - - m_Client->HandleUpdateSign(BlockX, BlockY, BlockZ, Lines[0], Lines[1], Lines[2], Lines[3]); -} - - - - - -void cProtocol180::HandlePacketUseEntity(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, EntityID); - HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, Type); - - switch (Type) - { - case 0: - { - 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); - - // TODO: Do anything - break; - } - default: - { - ASSERT(!"Unhandled use entity type!"); - return; - } - } -} - - - - - -void cProtocol180::HandlePacketEnchantItem(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); - HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Enchantment); - - m_Client->HandleEnchantItem(WindowID, Enchantment); -} - - - - - -void cProtocol180::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, ReadBEUInt8, UInt8, 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 cProtocol180::HandlePacketWindowClose(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); - m_Client->HandleWindowClose(WindowID); -} - - - - - -void cProtocol180::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", "\x08""Cuberite"); - 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 cProtocol180::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 cProtocol180::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 cProtocol180::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)); - } - break; - } - case TAG_Int: - { - if (TagName == "RepairCost") - { - a_Item.m_RepairCost = NBT.GetInt(tag); - } - } - default: LOGD("Unimplemented NBT data when parsing!"); break; - } - } -} - - - - - -void cProtocol180::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 cProtocol180::FaceIntToBlockFace(Int8 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; - } -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cProtocol180::cPacketizer: - -void cProtocol180::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 (!cProtocol180::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 cProtocol180::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; - } - - a_Pkt.WriteBEInt16(ItemType); - a_Pkt.WriteBEInt8(a_Item.m_ItemCount); - a_Pkt.WriteBEInt16(a_Item.m_ItemDamage); - - if (a_Item.m_Enchantments.IsEmpty() && a_Item.IsBothNameAndLoreEmpty() && (a_Item.m_ItemType != E_ITEM_FIREWORK_ROCKET) && (a_Item.m_ItemType != E_ITEM_FIREWORK_STAR) && !a_Item.m_ItemColor.IsValid()) - { - 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)); - } - Writer.Finish(); - - AString Result = Writer.GetResult(); - if (Result.size() == 0) - { - a_Pkt.WriteBEInt8(0); - return; - } - a_Pkt.WriteBuf(Result.data(), Result.size()); -} - - - - - -void cProtocol180::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 cProtocol180::WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) -{ - // Common metadata: - Byte 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); // Byte(0) + index 0 - a_Pkt.WriteBEUInt8(Flags); - - switch (a_Entity.GetEntityType()) - { - case cEntity::etPlayer: break; // TODO? - case cEntity::etPickup: - { - a_Pkt.WriteBEUInt8((5 << 5) | 10); // Slot(5) + index 10 - WriteItem(a_Pkt, reinterpret_cast(a_Entity).GetItem()); - break; - } - case cEntity::etMinecart: - { - a_Pkt.WriteBEUInt8(0x51); - - // 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.WriteBEInt32((((a_Entity.GetMaxHealth() / 2) - (a_Entity.GetHealth() - (a_Entity.GetMaxHealth() / 2))) * Minecart.LastDamage()) * 4); - a_Pkt.WriteBEUInt8(0x52); - a_Pkt.WriteBEInt32(1); // Shaking direction, doesn't seem to affect anything - a_Pkt.WriteBEUInt8(0x73); - a_Pkt.WriteBEFloat(static_cast(Minecart.LastDamage() + 10)); // Damage taken / shake effect multiplyer - - if (Minecart.GetPayload() == cMinecart::mpNone) - { - auto & RideableMinecart = reinterpret_cast(Minecart); - const cItem & MinecartContent = RideableMinecart.GetContent(); - if (!MinecartContent.IsEmpty()) - { - a_Pkt.WriteBEUInt8(0x54); - int Content = MinecartContent.m_ItemType; - Content |= MinecartContent.m_ItemDamage << 8; - a_Pkt.WriteBEInt32(Content); - a_Pkt.WriteBEUInt8(0x55); - a_Pkt.WriteBEInt32(RideableMinecart.GetBlockHeight()); - a_Pkt.WriteBEUInt8(0x56); - a_Pkt.WriteBEUInt8(1); - } - } - else if (Minecart.GetPayload() == cMinecart::mpFurnace) - { - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(reinterpret_cast(Minecart).IsFueled() ? 1 : 0); - } - break; - } // case etMinecart - - case cEntity::etProjectile: - { - auto & Projectile = reinterpret_cast(a_Entity); - switch (Projectile.GetProjectileKind()) - { - case cProjectileEntity::pkArrow: - { - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(reinterpret_cast(Projectile).IsCritical() ? 1 : 0); - break; - } - case cProjectileEntity::pkFirework: - { - a_Pkt.WriteBEUInt8(0xa8); - WriteItem(a_Pkt, reinterpret_cast(Projectile).GetItem()); - break; - } - 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(0xa8); - WriteItem(a_Pkt, Frame.GetItem()); - a_Pkt.WriteBEUInt8(0x09); - a_Pkt.WriteBEUInt8(Frame.GetItemRotation()); - break; - } // case etItemFrame - - default: - { - break; - } - } -} - - - - - -void cProtocol180::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) -{ - // Living Enitiy Metadata - if (a_Mob.HasCustomName()) - { - a_Pkt.WriteBEUInt8(0x82); - a_Pkt.WriteString(a_Mob.GetCustomName()); - - a_Pkt.WriteBEUInt8(0x03); - a_Pkt.WriteBool(a_Mob.IsCustomNameAlwaysVisible()); - } - - a_Pkt.WriteBEUInt8(0x66); - a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); - - switch (a_Mob.GetMobType()) - { - case mtBat: - { - auto & Bat = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(Bat.IsHanging() ? 1 : 0); - break; - } // case mtBat - - case mtCreeper: - { - auto & Creeper = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(Creeper.IsBlowing() ? 1 : 255); - a_Pkt.WriteBEUInt8(0x11); - a_Pkt.WriteBEUInt8(Creeper.IsCharged() ? 1 : 0); - break; - } // case mtCreeper - - case mtEnderman: - { - auto & Enderman = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x30); - a_Pkt.WriteBEInt16(static_cast(Enderman.GetCarriedBlock())); - a_Pkt.WriteBEUInt8(0x11); - a_Pkt.WriteBEUInt8(static_cast(Enderman.GetCarriedMeta())); - a_Pkt.WriteBEUInt8(0x12); - a_Pkt.WriteBEUInt8(Enderman.IsScreaming() ? 1 : 0); - break; - } // case mtEnderman - - case mtGhast: - { - auto & Ghast = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(Ghast.IsCharging()); - break; - } // case mtGhast - - case mtHorse: - { - auto & Horse = reinterpret_cast(a_Mob); - int 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(0x50); // Int at index 16 - a_Pkt.WriteBEInt32(Flags); - a_Pkt.WriteBEUInt8(0x13); // Byte at index 19 - a_Pkt.WriteBEUInt8(static_cast(Horse.GetHorseType())); - a_Pkt.WriteBEUInt8(0x54); // Int at index 20 - int Appearance = 0; - Appearance = Horse.GetHorseColor(); - Appearance |= Horse.GetHorseStyle() << 8; - a_Pkt.WriteBEInt32(Appearance); - a_Pkt.WriteBEUInt8(0x56); // Int at index 22 - a_Pkt.WriteBEInt32(Horse.GetHorseArmour()); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Horse.IsBaby() ? -1 : (Horse.IsInLoveCooldown() ? 1 : 0)); - break; - } // case mtHorse - - case mtMagmaCube: - { - auto & MagmaCube = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(static_cast(MagmaCube.GetSize())); - break; - } // case mtMagmaCube - - case mtOcelot: - { - auto & Ocelot = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Ocelot.IsBaby() ? -1 : (Ocelot.IsInLoveCooldown() ? 1 : 0)); - break; - } // case mtOcelot - - case mtCow: - { - auto & Cow = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Cow.IsBaby() ? -1 : (Cow.IsInLoveCooldown() ? 1 : 0)); - break; - } // case mtCow - - case mtChicken: - { - auto & Chicken = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Chicken.IsBaby() ? -1 : (Chicken.IsInLoveCooldown() ? 1 : 0)); - break; - } // case mtChicken - - case mtPig: - { - auto & Pig = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Pig.IsBaby() ? -1 : (Pig.IsInLoveCooldown() ? 1 : 0)); - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(Pig.IsSaddled() ? 1 : 0); - break; - } // case mtPig - - case mtSheep: - { - auto & Sheep = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Sheep.IsBaby() ? -1 : (Sheep.IsInLoveCooldown() ? 1 : 0)); - - a_Pkt.WriteBEUInt8(0x10); - Byte SheepMetadata = 0; - SheepMetadata = static_cast(Sheep.GetFurColor()); - if (Sheep.IsSheared()) - { - SheepMetadata |= 0x10; - } - a_Pkt.WriteBEUInt8(SheepMetadata); - break; - } // case mtSheep - - case mtRabbit: - { - auto & Rabbit = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x12); - a_Pkt.WriteBEUInt8(static_cast(Rabbit.GetRabbitType())); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Rabbit.IsBaby() ? -1 : (Rabbit.IsInLoveCooldown() ? 1 : 0)); - break; - } // case mtRabbit - - case mtSkeleton: - { - auto & Skeleton = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x0d); - a_Pkt.WriteBEUInt8(Skeleton.IsWither() ? 1 : 0); - break; - } // case mtSkeleton - - case mtSlime: - { - auto & Slime = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(static_cast(Slime.GetSize())); - break; - } // case mtSlime - - case mtVillager: - { - auto & Villager = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x50); - a_Pkt.WriteBEInt32(Villager.GetVilType()); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Villager.IsBaby() ? -1 : 0); - break; - } // case mtVillager - - case mtWitch: - { - auto & Witch = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x15); - a_Pkt.WriteBEUInt8(Witch.IsAngry() ? 1 : 0); - break; - } // case mtWitch - - case mtWither: - { - auto & Wither = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x54); // Int at index 20 - a_Pkt.WriteBEInt32(static_cast(Wither.GetWitherInvulnerableTicks())); - a_Pkt.WriteBEUInt8(0x66); // Float at index 6 - a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); - break; - } // case mtWither - - case mtWolf: - { - auto & Wolf = reinterpret_cast(a_Mob); - Byte WolfStatus = 0; - if (Wolf.IsSitting()) - { - WolfStatus |= 0x1; - } - if (Wolf.IsAngry()) - { - WolfStatus |= 0x2; - } - if (Wolf.IsTame()) - { - WolfStatus |= 0x4; - } - a_Pkt.WriteBEUInt8(0x10); - a_Pkt.WriteBEUInt8(WolfStatus); - - a_Pkt.WriteBEUInt8(0x72); - a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); - a_Pkt.WriteBEUInt8(0x13); - a_Pkt.WriteBEUInt8(Wolf.IsBegging() ? 1 : 0); - a_Pkt.WriteBEUInt8(0x14); - a_Pkt.WriteBEUInt8(static_cast(Wolf.GetCollarColor())); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Wolf.IsBaby() ? -1 : 0); - break; - } // case mtWolf - - case mtZombie: - { - auto & Zombie = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Zombie.IsBaby() ? 1 : -1); - a_Pkt.WriteBEUInt8(0x0d); - a_Pkt.WriteBEUInt8(Zombie.IsVillagerZombie() ? 1 : 0); - a_Pkt.WriteBEUInt8(0x0e); - a_Pkt.WriteBEUInt8(Zombie.IsConverting() ? 1 : 0); - break; - } // case mtZombie - - case mtZombiePigman: - { - auto & ZombiePigman = reinterpret_cast(a_Mob); - a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(ZombiePigman.IsBaby() ? 1 : -1); - break; - } // case mtZombiePigman - } // switch (a_Mob.GetType()) -} - - - - - -void cProtocol180::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 -} - - - diff --git a/src/Protocol/Protocol18x.h b/src/Protocol/Protocol18x.h deleted file mode 100644 index 71bc95ef8..000000000 --- a/src/Protocol/Protocol18x.h +++ /dev/null @@ -1,268 +0,0 @@ - -// Protocol18x.h - -/* -Declares the 1.8.x protocol classes: - - cProtocol180 - - release 1.8.0 protocol (#47) -(others may be added later in the future for the 1.8 release series) -*/ - - - - - -#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 cProtocol180 : - public cProtocol -{ - typedef cProtocol super; - -public: - - cProtocol180(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 SendCameraSetTo (const cEntity & a_Entity) 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 SendChatRaw (const AString & a_MessageRaw, eChatType a_Type) 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) 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; - - /** 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): - void HandlePacketStatusPing(cByteBuffer & a_ByteBuffer); - 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 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 HandlePacketSpectate (cByteBuffer & a_ByteBuffer); - void HandlePacketSteerVehicle (cByteBuffer & a_ByteBuffer); - void HandlePacketTabComplete (cByteBuffer & a_ByteBuffer); - void HandlePacketUpdateSign (cByteBuffer & a_ByteBuffer); - void HandlePacketUseEntity (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(Int8 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 0x7f. */ - 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); -} ; - - - diff --git a/src/Protocol/Protocol19x.cpp b/src/Protocol/Protocol19x.cpp deleted file mode 100644 index 5f3b9ec27..000000000 --- a/src/Protocol/Protocol19x.cpp +++ /dev/null @@ -1,4411 +0,0 @@ - -// 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/Boat.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) -{ - - // 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::SendCameraSetTo(const cEntity & a_Entity) -{ - cPacketizer Pkt(*this, 0x36); // Camera Packet (Attach the camera of a player at another entity in spectator mode) - Pkt.WriteVarInt32(a_Entity.GetUniqueID()); -} - - - - - -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::SendChatRaw(const AString & a_MessageRaw, eChatType a_Type) -{ - ASSERT(m_State == 3); // In game mode? - - // Send the json string to the client: - cPacketizer Pkt(*this, 0x0f); // Chat Message packet - Pkt.WriteString(a_MessageRaw); - 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(static_cast(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? - } - - // 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? - - { // TODO Use SendSpawnObject - 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); - } - - SendEntityMetadata(a_Pickup); -} - - - - - -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()); - WriteEntityMetadata(Pkt, a_Player); - 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) -{ - 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"); -} - - - - - -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: HandlePacketVehicleMove (a_ByteBuffer); return true; - case 0x11: HandlePacketBoatSteer (a_ByteBuffer); return true; - 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: HandlePacketSpectate (a_ByteBuffer); return true; - 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::HandlePacketBoatSteer(cByteBuffer & a_ByteBuffer) -{ - HANDLE_READ(a_ByteBuffer, ReadBool, bool, RightPaddle); - HANDLE_READ(a_ByteBuffer, ReadBool, bool, LeftPaddle); - - // Get the players vehicle - cPlayer * Player = m_Client->GetPlayer(); - cEntity * Vehicle = Player->GetAttached(); - - if (Vehicle) - { - if (Vehicle->GetEntityType() == cEntity::etBoat) - { - auto * Boat = reinterpret_cast(Vehicle); - Boat->UpdatePaddles(RightPaddle, LeftPaddle); - } - } -} - - - - - -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::HandlePacketSpectate(cByteBuffer & a_ByteBuffer) -{ - AString playerUUID; - if (!a_ByteBuffer.ReadUUID(playerUUID)) - { - return; - } - - m_Client->HandleSpectate(playerUUID); -} - - - - - -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) - { - // TODO: Handle vehicle jump (for animals) - } - 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::HandlePacketVehicleMove(cByteBuffer & a_ByteBuffer) -{ - // This handles updating the vehicles location server side - HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, xPos); - HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, yPos); - HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, zPos); - HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, yaw); - HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, pitch); - - // Get the players vehicle - cEntity * Vehicle = m_Client->GetPlayer()->GetAttached(); - - if (Vehicle) - { - Vehicle->SetPosX(xPos); - Vehicle->SetPosY(yPos); - Vehicle->SetPosZ(zPos); - Vehicle->SetYaw(yaw); - Vehicle->SetPitch(pitch); - } -} - - - - - -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", "\x08""Cuberite"); - 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(Int32 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: - { - auto & Player = reinterpret_cast(a_Entity); - - // TODO Set player custom name to their name. - // Then it's possible to move the custom name of mobs to the entities - // and to remove the "special" player custom name. - a_Pkt.WriteBEUInt8(2); // Index 2: Custom name - a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); - a_Pkt.WriteString(Player.GetName()); - - a_Pkt.WriteBEUInt8(6); // Start metadata - Index 6: Health - a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); - a_Pkt.WriteBEFloat(static_cast(Player.GetHealth())); - break; - } - 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 - auto & Minecart = reinterpret_cast(a_Entity); - auto maxHealth = a_Entity.GetMaxHealth(); - auto curHealth = a_Entity.GetHealth(); - a_Pkt.WriteVarInt32(static_cast((maxHealth - curHealth) * 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(static_cast(Content)); - - a_Pkt.WriteBEUInt8(9); // Index 9: Block ID and damage - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(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::etBoat: - { - auto & Boat = reinterpret_cast(a_Entity); - - a_Pkt.WriteBEInt8(5); // Index 6: Time since last hit - a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteBEInt32(Boat.GetLastDamage()); - - a_Pkt.WriteBEInt8(6); // Index 7: Forward direction - a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteBEInt32(Boat.GetForwardDirection()); - - a_Pkt.WriteBEInt8(7); // Index 8: Damage taken - a_Pkt.WriteBEInt8(METADATA_TYPE_FLOAT); - a_Pkt.WriteBEFloat(Boat.GetDamageTaken()); - - a_Pkt.WriteBEInt8(8); // Index 9: Type - a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteBEInt32(Boat.GetType()); - - a_Pkt.WriteBEInt8(9); // Index 10: Right paddle turning - a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Boat.IsRightPaddleUsed()); - - a_Pkt.WriteBEInt8(10); // Index 11: Left paddle turning - a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); - a_Pkt.WriteBool(Boat.IsLeftPaddleUsed()); - - break; - } // case etBoat - - 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 : 0xffffffff); - - 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 |= static_cast(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(static_cast(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(static_cast(Appearance)); - - a_Pkt.WriteBEUInt8(16); // Index 16: Armor - a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); - a_Pkt.WriteVarInt32(static_cast(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(static_cast(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(static_cast(Rabbit.GetRabbitType())); - 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(static_cast(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(static_cast(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(static_cast(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? - } - - // 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 deleted file mode 100644 index 64cad081f..000000000 --- a/src/Protocol/Protocol19x.h +++ /dev/null @@ -1,360 +0,0 @@ - -// 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 SendCameraSetTo (const cEntity & a_Entity) 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 SendChatRaw (const AString & a_MessageRaw, eChatType a_Type) 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) 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; - - /** 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 HandlePacketBoatSteer (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 HandlePacketSpectate (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 HandlePacketVehicleMove (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(Int32 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. */ - virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity); - - /** Writes the mob-specific metadata for the specified mob */ - virtual 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 0c67f66f5..04ac1bb2b 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -7,9 +7,10 @@ #include "Globals.h" #include "ProtocolRecognizer.h" -#include "Protocol18x.h" -#include "Protocol19x.h" -#include "Protocol110x.h" +#include "Protocol_1_8.h" +#include "Protocol_1_9.h" +#include "Protocol_1_10.h" +#include "Protocol_1_11.h" #include "Packetizer.h" #include "../ClientHandle.h" #include "../Root.h" @@ -53,7 +54,8 @@ AString cProtocolRecognizer::GetVersionTextFromInt(int a_ProtocolVersion) 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"; - case PROTO_VERSION_1_10_0: return "1.10"; + case PROTO_VERSION_1_10_0: return "1.10"; + case PROTO_VERSION_1_11_0: return "1.11"; } ASSERT(!"Unknown protocol version"); return Printf("Unknown protocol (%d)", a_ProtocolVersion); @@ -220,10 +222,10 @@ void cProtocolRecognizer::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSe -void cProtocolRecognizer::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player) +void cProtocolRecognizer::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) { ASSERT(m_Protocol != nullptr); - m_Protocol->SendCollectEntity(a_Entity, a_Player); + m_Protocol->SendCollectEntity(a_Entity, a_Player, a_Count); } @@ -978,8 +980,7 @@ void cProtocolRecognizer::SendData(const char * a_Data, size_t a_Size) bool cProtocolRecognizer::TryRecognizeProtocol(void) { - // NOTE: If a new protocol is added or an old one is removed, adjust MCS_CLIENT_VERSIONS and - // MCS_PROTOCOL_VERSIONS macros in the header file, as well as PROTO_VERSION_LATEST macro + // NOTE: If a new protocol is added or an old one is removed, adjust MCS_CLIENT_VERSIONS and MCS_PROTOCOL_VERSIONS macros in the header file // Lengthed protocol, try if it has the entire initial handshake packet: UInt32 PacketLen; @@ -1045,37 +1046,42 @@ bool cProtocolRecognizer::TryRecognizeLengthedProtocol(UInt32 a_PacketLengthRema case PROTO_VERSION_1_8_0: { m_Buffer.CommitRead(); - m_Protocol = new cProtocol180(m_Client, ServerAddress, ServerPort, NextState); + m_Protocol = new cProtocol_1_8_0(m_Client, ServerAddress, ServerPort, NextState); return true; } case PROTO_VERSION_1_9_0: { - m_Protocol = new cProtocol190(m_Client, ServerAddress, ServerPort, NextState); + m_Protocol = new cProtocol_1_9_0(m_Client, ServerAddress, ServerPort, NextState); return true; } case PROTO_VERSION_1_9_1: { - m_Protocol = new cProtocol191(m_Client, ServerAddress, ServerPort, NextState); + m_Protocol = new cProtocol_1_9_1(m_Client, ServerAddress, ServerPort, NextState); return true; } case PROTO_VERSION_1_9_2: { - m_Protocol = new cProtocol192(m_Client, ServerAddress, ServerPort, NextState); + m_Protocol = new cProtocol_1_9_2(m_Client, ServerAddress, ServerPort, NextState); return true; } case PROTO_VERSION_1_9_4: { - m_Protocol = new cProtocol194(m_Client, ServerAddress, ServerPort, NextState); + m_Protocol = new cProtocol_1_9_4(m_Client, ServerAddress, ServerPort, NextState); return true; } case PROTO_VERSION_1_10_0: { - m_Protocol = new cProtocol1100(m_Client, ServerAddress, ServerPort, NextState); + m_Protocol = new cProtocol_1_10_0(m_Client, ServerAddress, ServerPort, NextState); + return true; + } + case PROTO_VERSION_1_11_0: + { + m_Protocol = new cProtocol_1_11_0(m_Client, ServerAddress, ServerPort, NextState); return true; } default: { - LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))", + LOGD("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))", m_Client->GetIPString().c_str(), ProtocolVersion, ProtocolVersion ); if (NextState != 1) diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 560819705..081b2136e 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.8.x, 1.9.x, 1.10.x" -#define MCS_PROTOCOL_VERSIONS "47, 107, 108, 109, 110, 210" +#define MCS_CLIENT_VERSIONS "1.8.x, 1.9.x, 1.10.x, 1.11" +#define MCS_PROTOCOL_VERSIONS "47, 107, 108, 109, 110, 210, 315" @@ -39,6 +39,7 @@ public: PROTO_VERSION_1_9_2 = 109, PROTO_VERSION_1_9_4 = 110, PROTO_VERSION_1_10_0 = 210, + PROTO_VERSION_1_11_0 = 315, } ; cProtocolRecognizer(cClientHandle * a_Client); @@ -61,7 +62,7 @@ public: virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; virtual void SendChatRaw (const AString & a_MessageRaw, eChatType a_Type) 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 SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) 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; diff --git a/src/Protocol/Protocol_1_10.cpp b/src/Protocol/Protocol_1_10.cpp new file mode 100644 index 000000000..c0d9d921f --- /dev/null +++ b/src/Protocol/Protocol_1_10.cpp @@ -0,0 +1,883 @@ + +// Protocol_1_10.cpp + +/* +Implements the 1.10 protocol classes: + - cProtocol_1_10_0 + - release 1.10 protocol (#210), also used by 1.10.1 and 1.10.2 +*/ + +#include "Globals.h" +#include "Protocol_1_10.h" +#include "Packetizer.h" + +#include "json/json.h" + +#include "../Root.h" +#include "../Server.h" + +#include "../Entities/Boat.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 "../Mobs/IncludeAllMonsters.h" + +#include "Bindings/PluginManager.h" + + + + + +// The disabled error is intended, since the Metadata have overlapping indexes +// based on the type of the Entity. +// +// IMPORTANT: The enum is used to automate the sequential counting of the +// Metadata indexes. Adding a new enum value causes the following values to +// increase their index. Therefore the ordering of the enum values is VERY important! +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wduplicate-enum" +#endif + +namespace Metadata +{ + enum Metadata_Index + { + // Entity + ENTITY_FLAGS, + ENTITY_AIR, + ENTITY_CUSTOM_NAME, + ENTITY_CUSTOM_NAME_VISIBLE, + ENTITY_SILENT, + ENTITY_NO_GRAVITY, + _ENTITY_NEXT, // Used by descendants + + // Potion + POTION_THROWN = _ENTITY_NEXT, + + // FallingBlock + FALLING_BLOCK_POSITION = _ENTITY_NEXT, + + // AreaEffectCloud + AREA_EFFECT_CLOUD_RADIUS = _ENTITY_NEXT, + AREA_EFFECT_CLOUD_COLOR, + AREA_EFFECT_CLOUD_SINGLE_POINT_EFFECT, + AREA_EFFECT_CLOUD_PARTICLE_ID, + AREA_EFFECT_CLOUD_PARTICLE_PARAMETER1, + AREA_EFFECT_CLOUD_PARTICLE_PARAMETER2, + + // Arrow + ARROW_CRITICAL = _ENTITY_NEXT, + _ARROW_NEXT, + + // TippedArrow + TIPPED_ARROW_COLOR = _ARROW_NEXT, + + // Boat + BOAT_LAST_HIT_TIME = _ENTITY_NEXT, + BOAT_FORWARD_DIRECTION, + BOAT_DAMAGE_TAKEN, + BOAT_TYPE, + BOAT_RIGHT_PADDLE_TURNING, + BOAT_LEFT_PADDLE_TURNING, + + // EnderCrystal + ENDER_CRYSTAL_BEAM_TARGET = _ENTITY_NEXT, + ENDER_CRYSTAL_SHOW_BOTTOM, + + // Fireball + _FIREBALL_NEXT = _ENTITY_NEXT, + + // WitherSkull + WITHER_SKULL_INVULNERABLE = _FIREBALL_NEXT, + + // Fireworks + FIREWORK_INFO = _ENTITY_NEXT, + + // Hanging + _HANGING_NEXT = _ENTITY_NEXT, + + // ItemFrame + ITEM_FRAME_ITEM = _HANGING_NEXT, + ITEM_FRAME_ROTATION, + + // Item + ITEM_ITEM = _ENTITY_NEXT, + + // Living + LIVING_ACTIVE_HAND = _ENTITY_NEXT, + LIVING_HEALTH, + LIVING_POTION_EFFECT_COLOR, + LIVING_POTION_EFFECT_AMBIENT, + LIVING_NUMBER_OF_ARROWS, + _LIVING_NEXT, + + // Player + PLAYER_ADDITIONAL_HEARTHS = _LIVING_NEXT, + PLAYER_SCORE, + PLAYER_DISPLAYED_SKIN_PARTS, + PLAYER_MAIN_HAND, + + // ArmorStand + ARMOR_STAND_STATUS = _LIVING_NEXT, + ARMOR_STAND_HEAD_ROTATION, + ARMOR_STAND_BODY_ROTATION, + ARMOR_STAND_LEFT_ARM_ROTATION, + ARMOR_STAND_RIGHT_ARM_ROTATION, + ARMOR_STAND_LEFT_LEG_ROTATION, + ARMOR_STAND_RIGHT_LEG_ROTATION, + + // Insentient + INSENTIENT_STATUS = _LIVING_NEXT, + _INSENTIENT_NEXT, + + // Ambient + _AMBIENT_NEXT = _INSENTIENT_NEXT, + + // Bat + BAT_HANGING = _AMBIENT_NEXT, + + // Creature + _CREATURE_NEXT = _INSENTIENT_NEXT, + + // Ageable + AGEABLE_BABY = _CREATURE_NEXT, + _AGEABLE_NEXT, + + // PolarBear + POLAR_BEAR_STANDING = _AGEABLE_NEXT, + + // Animal + _ANIMAL_NEXT = _AGEABLE_NEXT, + + // Horse + HORSE_STATUS = _ANIMAL_NEXT, + HORSE_TYPE, + HORSE_VARIANT, + HORSE_OWNER, + HORSE_ARMOR, + + // Pig + PIG_HAS_SADDLE = _ANIMAL_NEXT, + + // Rabbit + RABBIT_TYPE = _ANIMAL_NEXT, + + // Sheep + SHEEP_STATUS = _ANIMAL_NEXT, + + // TameableAnimal + TAMEABLE_ANIMAL_STATUS = _ANIMAL_NEXT, + TAMEABLE_ANIMAL_OWNER, + _TAMEABLE_NEXT, + + // Ocelot + OCELOT_TYPE = _TAMEABLE_NEXT, + + // Wolf + WOLF_DAMAGE_TAKEN = _TAMEABLE_NEXT, + WOLF_BEGGING, + WOLF_COLLAR_COLOR, + + // Villager + VILLAGER_PROFESSION = _AGEABLE_NEXT, + + // Golem + _GOLEM_NEXT = _CREATURE_NEXT, + + // IronGolem + IRON_GOLEM_PLAYER_CREATED = _GOLEM_NEXT, + + // Shulker + SHULKER_FACING_DIRECTION = _GOLEM_NEXT, + SHULKER_ATTACHMENT_FALLING_BLOCK_POSITION, + SHULKER_SHIELD_HEIGHT, + + // Monster + _MONSTER_NEXT = _CREATURE_NEXT, + + // Blaze + BLAZE_ON_FIRE = _MONSTER_NEXT, + + // Creeper + CREEPER_STATE = _MONSTER_NEXT, + CREEPER_POWERED, + CREEPER_IGNITED, + + // Guardian + GUARDIAN_STATUS = _MONSTER_NEXT, + GUARDIAN_TARGET, + + // Skeleton + SKELETON_TYPE = _MONSTER_NEXT, + SKELETON_ARMS_SWINGING, + + // Spider + SPIDER_CLIMBING = _MONSTER_NEXT, + + // Witch + WITCH_AGGRESIVE = _MONSTER_NEXT, + + // Wither + WITHER_FIRST_HEAD_TARGET = _MONSTER_NEXT, + WITHER_SECOND_HEAD_TARGET, + WITHER_THIRD_HEAD_TARGET, + WITHER_INVULNERABLE_TIMER, + + // Zombie + ZOMBIE_IS_BABY = _MONSTER_NEXT, + ZOMBIE_TYPE, + ZOMBIE_CONVERTING, + ZOMBIE_HANDS_RISED_UP, + + // Enderman + ENDERMAN_CARRIED_BLOCK = _MONSTER_NEXT, + ENDERMAN_SCREAMING, + + // EnderDragon + ENDER_DRAGON_DRAGON_PHASE = _INSENTIENT_NEXT, + + // Flying + _FLYING_NEXT = _INSENTIENT_NEXT, + + // Ghast + GHAST_ATTACKING = _FLYING_NEXT, + + // Slime + SLIME_SIZE = _INSENTIENT_NEXT, + + // Minecart + MINECART_SHAKING_POWER = _ENTITY_NEXT, + MINECART_SHAKING_DIRECTION, + MINECART_SHAKING_MULTIPLIER, + MINECART_BLOCK_ID_META, + MINECART_BLOCK_Y, + MINECART_SHOW_BLOCK, + _MINECART_NEXT, + + // MinecartCommandBlock + MINECART_COMMAND_BLOCK_COMMAND = _MINECART_NEXT, + MINECART_COMMAND_BLOCK_LAST_OUTPUT, + + // MinecartFurnace + MINECART_FURNACE_POWERED = _MINECART_NEXT, + + // TNTPrimed + TNT_PRIMED_FUSE_TIME = _ENTITY_NEXT, + }; +} + +#ifdef __clang__ + #pragma clang diagnostic pop // Restore ignored clang errors +#endif + + + + +cProtocol_1_10_0::cProtocol_1_10_0(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol_1_10_0::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(FloorC(a_X * 8.0)); + Pkt.WriteBEInt32(FloorC(a_Y * 8.0)); + Pkt.WriteBEInt32(FloorC(a_Z * 8.0)); + Pkt.WriteBEFloat(a_Volume); + Pkt.WriteBEFloat(a_Pitch); +} + + + + + +void cProtocol_1_10_0::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.10"; + Version["protocol"] = 210; + + // 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 cProtocol_1_10_0::WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) +{ + using namespace Metadata; + + // 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(ENTITY_FLAGS); // Index + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); // Type + a_Pkt.WriteBEInt8(Flags); + + switch (a_Entity.GetEntityType()) + { + case cEntity::etPlayer: + { + auto & Player = reinterpret_cast(a_Entity); + + // TODO Set player custom name to their name. + // Then it's possible to move the custom name of mobs to the entities + // and to remove the "special" player custom name. + a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME); + a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); + a_Pkt.WriteString(Player.GetName()); + + a_Pkt.WriteBEUInt8(LIVING_HEALTH); + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast(Player.GetHealth())); + break; + } + case cEntity::etPickup: + { + a_Pkt.WriteBEUInt8(ITEM_ITEM); + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, reinterpret_cast(a_Entity).GetItem()); + break; + } + case cEntity::etMinecart: + { + a_Pkt.WriteBEUInt8(MINECART_SHAKING_POWER); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + + // The following expression makes Minecarts shake more with less health or higher damage taken + auto & Minecart = reinterpret_cast(a_Entity); + auto maxHealth = a_Entity.GetMaxHealth(); + auto curHealth = a_Entity.GetHealth(); + a_Pkt.WriteVarInt32(static_cast((maxHealth - curHealth) * Minecart.LastDamage() * 4)); + + a_Pkt.WriteBEUInt8(MINECART_SHAKING_DIRECTION); // (doesn't seem to effect anything) + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(1); + + a_Pkt.WriteBEUInt8(MINECART_SHAKING_MULTIPLIER); // or 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(MINECART_BLOCK_ID_META); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + int Content = MinecartContent.m_ItemType; + Content |= MinecartContent.m_ItemDamage << 8; + a_Pkt.WriteVarInt32(static_cast(Content)); + + a_Pkt.WriteBEUInt8(MINECART_BLOCK_Y); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(RideableMinecart.GetBlockHeight())); + + a_Pkt.WriteBEUInt8(MINECART_SHOW_BLOCK); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(true); + } + } + else if (Minecart.GetPayload() == cMinecart::mpFurnace) + { + a_Pkt.WriteBEUInt8(MINECART_FURNACE_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(ARROW_CRITICAL); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(reinterpret_cast(Projectile).IsCritical() ? 1 : 0); + break; + } + case cProjectileEntity::pkFirework: + { + a_Pkt.WriteBEUInt8(FIREWORK_INFO); // 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(POTION_THROWN); // 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::etBoat: + { + auto & Boat = reinterpret_cast(a_Entity); + + a_Pkt.WriteBEInt8(BOAT_LAST_HIT_TIME); + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteBEInt32(Boat.GetLastDamage()); + + a_Pkt.WriteBEInt8(BOAT_FORWARD_DIRECTION); + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteBEInt32(Boat.GetForwardDirection()); + + a_Pkt.WriteBEInt8(BOAT_DAMAGE_TAKEN); + a_Pkt.WriteBEInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(Boat.GetDamageTaken()); + + a_Pkt.WriteBEInt8(BOAT_TYPE); + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteBEInt32(Boat.GetType()); + + a_Pkt.WriteBEInt8(BOAT_RIGHT_PADDLE_TURNING); + a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Boat.IsRightPaddleUsed()); + + a_Pkt.WriteBEInt8(BOAT_LEFT_PADDLE_TURNING); + a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Boat.IsLeftPaddleUsed()); + + break; + } // case etBoat + + case cEntity::etItemFrame: + { + auto & Frame = reinterpret_cast(a_Entity); + a_Pkt.WriteBEUInt8(ITEM_FRAME_ITEM); + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, Frame.GetItem()); + a_Pkt.WriteBEUInt8(ITEM_FRAME_ROTATION); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Frame.GetItemRotation()); + break; + } // case etItemFrame + + default: + { + break; + } + } +} + + + + + +void cProtocol_1_10_0::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) +{ + using namespace Metadata; + + // 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(ENTITY_CUSTOM_NAME); + a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); + a_Pkt.WriteString(a_Mob.GetCustomName()); + + a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME_VISIBLE); // Custom name always visible + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(a_Mob.IsCustomNameAlwaysVisible()); + } + + a_Pkt.WriteBEUInt8(LIVING_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(BAT_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(CREEPER_STATE); // (idle or "blowing") + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Creeper.IsBlowing() ? 1 : static_cast(-1)); + + a_Pkt.WriteBEUInt8(CREEPER_POWERED); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Creeper.IsCharged()); + + a_Pkt.WriteBEUInt8(CREEPER_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(ENDERMAN_CARRIED_BLOCK); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BLOCKID); + UInt32 Carried = 0; + Carried |= static_cast(Enderman.GetCarriedBlock() << 4); + Carried |= Enderman.GetCarriedMeta(); + a_Pkt.WriteVarInt32(Carried); + + a_Pkt.WriteBEUInt8(ENDERMAN_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(GHAST_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(HORSE_STATUS); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(Flags); + + a_Pkt.WriteBEUInt8(HORSE_TYPE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Horse.GetHorseType())); + + a_Pkt.WriteBEUInt8(HORSE_VARIANT); // Color / style + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + int Appearance = 0; + Appearance = Horse.GetHorseColor(); + Appearance |= Horse.GetHorseStyle() << 8; + a_Pkt.WriteVarInt32(static_cast(Appearance)); + + a_Pkt.WriteBEUInt8(HORSE_ARMOR); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Horse.GetHorseArmour())); + + a_Pkt.WriteBEUInt8(AGEABLE_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(SLIME_SIZE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(MagmaCube.GetSize())); + break; + } // case mtMagmaCube + + case mtOcelot: + { + auto & Ocelot = reinterpret_cast(a_Mob); + + a_Pkt.WriteBEUInt8(AGEABLE_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(AGEABLE_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(AGEABLE_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(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Pig.IsBaby()); + + a_Pkt.WriteBEUInt8(PIG_HAS_SADDLE); + 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(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Sheep.IsBaby()); + + a_Pkt.WriteBEUInt8(SHEEP_STATUS); + 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(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Rabbit.IsBaby()); + + a_Pkt.WriteBEUInt8(RABBIT_TYPE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Rabbit.GetRabbitType())); + break; + } // case mtRabbit + + case mtSkeleton: + { + auto & Skeleton = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(SKELETON_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(SLIME_SIZE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Slime.GetSize())); + break; + } // case mtSlime + + case mtVillager: + { + auto & Villager = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Villager.IsBaby()); + + a_Pkt.WriteBEUInt8(VILLAGER_PROFESSION); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Villager.GetVilType())); + break; + } // case mtVillager + + case mtWitch: + { + auto & Witch = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(WITCH_AGGRESIVE); + 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(WITHER_INVULNERABLE_TIMER); + 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(AGEABLE_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(TAMEABLE_ANIMAL_STATUS); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(WolfStatus); + + a_Pkt.WriteBEUInt8(WOLF_DAMAGE_TAKEN); + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); // TODO Not use the current health + + a_Pkt.WriteBEUInt8(WOLF_BEGGING); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Wolf.IsBegging()); + + a_Pkt.WriteBEUInt8(WOLF_COLLAR_COLOR); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Wolf.GetCollarColor())); + break; + } // case mtWolf + + case mtZombie: + { + auto & Zombie = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Zombie.IsBaby()); + + a_Pkt.WriteBEUInt8(ZOMBIE_TYPE); + 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(ZOMBIE_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(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(ZombiePigman.IsBaby()); + break; + } // case mtZombiePigman + } // switch (a_Mob.GetType()) +} diff --git a/src/Protocol/Protocol_1_10.h b/src/Protocol/Protocol_1_10.h new file mode 100644 index 000000000..df64758e4 --- /dev/null +++ b/src/Protocol/Protocol_1_10.h @@ -0,0 +1,37 @@ + +// Protocol_1_10.h + +/* +Declares the 1.10 protocol classes: + - cProtocol_1_10 + - release 1.10 protocol (#210), also used by 1.10.1 and 1.10.2 +*/ + + + + + +#pragma once + +#include "Protocol_1_9.h" + + + + + +class cProtocol_1_10_0 : + public cProtocol_1_9_4 +{ + typedef cProtocol_1_9_4 super; + +public: + cProtocol_1_10_0(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + 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 HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; + +protected: + virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) override; + virtual void WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) override; +}; diff --git a/src/Protocol/Protocol_1_11.cpp b/src/Protocol/Protocol_1_11.cpp new file mode 100644 index 000000000..5d6eca508 --- /dev/null +++ b/src/Protocol/Protocol_1_11.cpp @@ -0,0 +1,189 @@ + +// Protocol_1_11.cpp + +/* +Implements the 1.11 protocol classes: + - cProtocol_1_11_0 + - release 1.11 protocol (#315) +(others may be added later in the future for the 1.11 release series) +*/ + +#include "Globals.h" +#include "Protocol_1_11.h" +#include "ProtocolRecognizer.h" +#include "Packetizer.h" + +#include "../Root.h" +#include "../Server.h" +#include "../ClientHandle.h" +#include "../CompositeChat.h" +#include "../Bindings/PluginManager.h" +#include "../Entities/Player.h" +#include "../Mobs/Monster.h" + + + + + +#define HANDLE_READ(ByteBuf, Proc, Type, Var) \ + Type Var; \ + if (!ByteBuf.Proc(Var))\ + {\ + return;\ + } + + + + + +cProtocol_1_11_0::cProtocol_1_11_0(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol_1_11_0::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0d); // Collect Item packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteVarInt32(a_Player.GetUniqueID()); + Pkt.WriteVarInt32(static_cast(a_Count)); +} + + + + + +void cProtocol_1_11_0::SendHideTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(4); // Hide title +} + + + + + +void cProtocol_1_11_0::SendResetTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(5); // Reset title +} + + + + + +void cProtocol_1_11_0::SendSpawnMob(const cMonster & a_Mob) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0f); // Spawn Mob packet + Pkt.WriteVarInt32(a_Mob.GetUniqueID()); + Pkt.WriteVarInt32(static_cast(a_Mob.GetMobType())); + Pkt.WriteFPInt(a_Mob.GetPosX()); + Pkt.WriteFPInt(a_Mob.GetPosY()); + Pkt.WriteFPInt(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(0x7f); // Metadata terminator +} + + + + + +void cProtocol_1_11_0::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(3); // Set title display times + Pkt.WriteBEInt32(a_FadeInTicks); + Pkt.WriteBEInt32(a_DisplayTicks); + Pkt.WriteBEInt32(a_FadeOutTicks); +} + + + + + + +void cProtocol_1_11_0::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, ReadBEFloat, float, CursorX); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorY); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorZ); + m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), FloorC(CursorX * 16), FloorC(CursorY * 16), FloorC(CursorZ * 16), m_Client->GetPlayer()->GetEquippedItem()); +} + + + + + +void cProtocol_1_11_0::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.11"; + Version["protocol"] = cProtocolRecognizer::PROTO_VERSION_1_11_0; + + // 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()); + } + + // Serialize the response into a packet: + Json::FastWriter Writer; + cPacketizer Pkt(*this, 0x00); // Response packet + Pkt.WriteString(Writer.write(ResponseValue)); +} + + + + + diff --git a/src/Protocol/Protocol_1_11.h b/src/Protocol/Protocol_1_11.h new file mode 100644 index 000000000..a3c7c4d58 --- /dev/null +++ b/src/Protocol/Protocol_1_11.h @@ -0,0 +1,41 @@ + +// Protocol_1_11.h + +/* +Declares the 1.11 protocol classes: + - cProtocol_1_11_0 + - release 1.11 protocol (#315) +(others may be added later in the future for the 1.11 release series) +*/ + + + + + +#pragma once + +#include "Protocol_1_10.h" + + + + + +class cProtocol_1_11_0 : + public cProtocol_1_10_0 +{ + typedef cProtocol_1_10_0 super; + +public: + cProtocol_1_11_0(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + virtual void SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) override; + virtual void SendHideTitle (void) override; + virtual void SendResetTitle (void) override; + virtual void SendSpawnMob (const cMonster & a_Mob) override; + virtual void SendTitleTimes (int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) override; + +protected: + + virtual void HandlePacketBlockPlace (cByteBuffer & a_ByteBuffer) override; + virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; +}; diff --git a/src/Protocol/Protocol_1_8.cpp b/src/Protocol/Protocol_1_8.cpp new file mode 100644 index 000000000..bbeb3ae8b --- /dev/null +++ b/src/Protocol/Protocol_1_8.cpp @@ -0,0 +1,3623 @@ + +// Protocol_1_8.cpp + +/* +Implements the 1.8 protocol classes: + - cProtocol_1_8_0 + - release 1.8 protocol (#47) +*/ + +#include "Globals.h" +#include "json/json.h" +#include "Protocol_1_8.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 "../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; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol_1_8_0: + +cProtocol_1_8_0::cProtocol_1_8_0(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) +{ + + // 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 cProtocol_1_8_0::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 cProtocol_1_8_0::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.GetUniqueID()); + Pkt.WriteBool(false); +} + + + + + +void cProtocol_1_8_0::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, 0x24); // Block Action packet + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); + Pkt.WriteBEInt8(a_Byte1); + Pkt.WriteBEInt8(a_Byte2); + Pkt.WriteVarInt32(a_BlockType); +} + + + + + +void cProtocol_1_8_0::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, 0x25); // Block Break Animation packet + Pkt.WriteVarInt32(a_EntityID); + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); + Pkt.WriteBEInt8(a_Stage); +} + + + + + +void cProtocol_1_8_0::SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x23); // Block Change packet + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); + Pkt.WriteVarInt32((static_cast(a_BlockType) << 4) | (static_cast(a_BlockMeta) & 15)); +} + + + + + +void cProtocol_1_8_0::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x22); // Multi Block Change packet + Pkt.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 cProtocol_1_8_0::SendCameraSetTo(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x43); // Camera Packet (Attach the camera of a player at another entity in spectator mode) + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol_1_8_0::SendChat(const AString & a_Message, eChatType a_Type) +{ + ASSERT(m_State == 3); // In game mode? + + SendChatRaw(Printf("{\"text\":\"%s\"}", EscapeString(a_Message).c_str()), a_Type); +} + + + + + +void cProtocol_1_8_0::SendChat(const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) +{ + ASSERT(m_State == 3); // In game mode? + + SendChatRaw(a_Message.CreateJsonString(a_ShouldUseChatPrefixes), a_Type); +} + + + + + +void cProtocol_1_8_0::SendChatRaw(const AString & a_MessageRaw, eChatType a_Type) +{ + ASSERT(m_State == 3); // In game mode? + + // Send the json string to the client: + cPacketizer Pkt(*this, 0x02); + Pkt.WriteString(a_MessageRaw); + Pkt.WriteBEInt8(a_Type); +} + + + + + +void cProtocol_1_8_0::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_8_0, a_ChunkX, a_ChunkZ); + + cCSLock Lock(m_CSPacket); + SendData(ChunkData.data(), ChunkData.size()); +} + + + + + +void cProtocol_1_8_0::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) +{ + UNUSED(a_Count); + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0d); // Collect Item packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteVarInt32(a_Player.GetUniqueID()); +} + + + + + +void cProtocol_1_8_0::SendDestroyEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x13); // Destroy Entities packet + Pkt.WriteVarInt32(1); + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::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, 0x40); + Pkt.WriteString(Printf("{\"text\":\"%s\"}", EscapeString(a_Reason).c_str())); + break; + } + } +} + + + + + +void cProtocol_1_8_0::SendEditSign(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x36); // Sign Editor Open packet + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); +} + + + + + +void cProtocol_1_8_0::SendEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1D); // 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 cProtocol_1_8_0::SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x04); // Entity Equipment packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt16(a_SlotNum); + WriteItem(Pkt, a_Item); +} + + + + + +void cProtocol_1_8_0::SendEntityHeadLook(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x19); // Entity Head Look packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteByteAngle(a_Entity.GetHeadYaw()); +} + + + + + +void cProtocol_1_8_0::SendEntityLook(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x16); // Entity Look packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol_1_8_0::SendEntityMetadata(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1c); // Entity Metadata packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + WriteEntityMetadata(Pkt, a_Entity); + Pkt.WriteBEUInt8(0x7f); // The termination byte +} + + + + + +void cProtocol_1_8_0::SendEntityProperties(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x20); // Entity Properties packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + WriteEntityProperties(Pkt, a_Entity); +} + + + + + +void cProtocol_1_8_0::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x15); // Entity Relative Move packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt8(a_RelX); + Pkt.WriteBEInt8(a_RelY); + Pkt.WriteBEInt8(a_RelZ); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol_1_8_0::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x17); // Entity Look And Relative Move packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt8(a_RelX); + Pkt.WriteBEInt8(a_RelY); + Pkt.WriteBEInt8(a_RelZ); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol_1_8_0::SendEntityStatus(const cEntity & a_Entity, char a_Status) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1a); // Entity Status packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt8(a_Status); +} + + + + + +void cProtocol_1_8_0::SendEntityVelocity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x12); // 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 cProtocol_1_8_0::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, 0x27); // 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 cProtocol_1_8_0::SendGameMode(eGameMode a_GameMode) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2b); // 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 cProtocol_1_8_0::SendHealth(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x06); // 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 cProtocol_1_8_0::SendHideTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(3); // Hide title +} + + + + + +void cProtocol_1_8_0::SendInventorySlot(char a_WindowID, short a_SlotNum, const cItem & a_Item) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2f); // Set Slot packet + Pkt.WriteBEInt8(a_WindowID); + Pkt.WriteBEInt16(a_SlotNum); + WriteItem(Pkt, a_Item); +} + + + + + +void cProtocol_1_8_0::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, 0x00); // Keep Alive packet + Pkt.WriteVarInt32(a_PingID); +} + + + + + +void cProtocol_1_8_0::SendLogin(const cPlayer & a_Player, const cWorld & a_World) +{ + // Send the Join Game packet: + { + cServer * Server = cRoot::Get()->GetServer(); + cPacketizer Pkt(*this, 0x01); // Join Game packet + Pkt.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? + } + + // Send the spawn position: + { + cPacketizer Pkt(*this, 0x05); // Spawn Position packet + Pkt.WritePosition64(FloorC(a_World.GetSpawnX()), FloorC(a_World.GetSpawnY()), FloorC(a_World.GetSpawnZ())); + } + + // Send the server difficulty: + { + cPacketizer Pkt(*this, 0x41); + Pkt.WriteBEInt8(1); + } + + // Send player abilities: + SendPlayerAbilities(); +} + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::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, 0x10); // Spawn Painting packet + Pkt.WriteVarInt32(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 cProtocol_1_8_0::SendMapData(const cMap & a_Map, int a_DataStartX, int a_DataStartY) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x34); + Pkt.WriteVarInt32(a_Map.GetID()); + Pkt.WriteBEUInt8(static_cast(a_Map.GetScale())); + + 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 cProtocol_1_8_0::SendPickupSpawn(const cPickup & a_Pickup) +{ + ASSERT(m_State == 3); // In game mode? + + { + cPacketizer Pkt(*this, 0x0e); // Spawn Object packet + Pkt.WriteVarInt32(a_Pickup.GetUniqueID()); + Pkt.WriteBEUInt8(2); // Type = Pickup + Pkt.WriteFPInt(a_Pickup.GetPosX()); + Pkt.WriteFPInt(a_Pickup.GetPosY()); + Pkt.WriteFPInt(a_Pickup.GetPosZ()); + Pkt.WriteByteAngle(a_Pickup.GetYaw()); + Pkt.WriteByteAngle(a_Pickup.GetPitch()); + Pkt.WriteBEInt32(0); // No object data + } + + { + cPacketizer Pkt(*this, 0x1c); // Entity Metadata packet + Pkt.WriteVarInt32(a_Pickup.GetUniqueID()); + Pkt.WriteBEUInt8((0x05 << 5) | 10); // Slot type + index 10 + WriteItem(Pkt, a_Pickup.GetItem()); + Pkt.WriteBEUInt8(0x7f); // End of metadata + } +} + + + + + +void cProtocol_1_8_0::SendPlayerAbilities(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x39); // 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 cProtocol_1_8_0::SendEntityAnimation(const cEntity & a_Entity, char a_Animation) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0b); // Animation packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt8(a_Animation); +} + + + + + +void cProtocol_1_8_0::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, 0x2A); + 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 cProtocol_1_8_0::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, 0x2A); + 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 cProtocol_1_8_0::SendPlayerListAddPlayer(const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x38); // 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 cProtocol_1_8_0::SendPlayerListRemovePlayer(const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x38); // Playerlist Item packet + Pkt.WriteVarInt32(4); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); +} + + + + + +void cProtocol_1_8_0::SendPlayerListUpdateGameMode(const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x38); // Playerlist Item packet + Pkt.WriteVarInt32(1); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteVarInt32(static_cast(a_Player.GetGameMode())); +} + + + + + +void cProtocol_1_8_0::SendPlayerListUpdatePing(const cPlayer & a_Player) +{ + ASSERT(m_State == 3); // In game mode? + + auto ClientHandle = a_Player.GetClientHandlePtr(); + if (ClientHandle != nullptr) + { + cPacketizer Pkt(*this, 0x38); // Playerlist Item packet + Pkt.WriteVarInt32(2); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteVarInt32(static_cast(ClientHandle->GetPing())); + } +} + + + + + +void cProtocol_1_8_0::SendPlayerListUpdateDisplayName(const cPlayer & a_Player, const AString & a_CustomName) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x38); // 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 cProtocol_1_8_0::SendPlayerMaxSpeed(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x20); // 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 cProtocol_1_8_0::SendPlayerMoveLook(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x08); // 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); +} + + + + + +void cProtocol_1_8_0::SendPlayerPosition(void) +{ + // There is no dedicated packet for this, send the whole thing: + SendPlayerMoveLook(); +} + + + + + +void cProtocol_1_8_0::SendPlayerSpawn(const cPlayer & a_Player) +{ + // Called to spawn another player for the client + cPacketizer Pkt(*this, 0x0c); // Spawn Player packet + Pkt.WriteVarInt32(a_Player.GetUniqueID()); + Pkt.WriteUUID(cMojangAPI::MakeUUIDShort(a_Player.GetUUID())); + Pkt.WriteFPInt(a_Player.GetPosX()); + Pkt.WriteFPInt(a_Player.GetPosY() + 0.001); // The "+ 0.001" is there because otherwise the player falls through the block they were standing on. + Pkt.WriteFPInt(a_Player.GetPosZ()); + Pkt.WriteByteAngle(a_Player.GetYaw()); + Pkt.WriteByteAngle(a_Player.GetPitch()); + short ItemType = a_Player.GetEquippedItem().IsEmpty() ? 0 : a_Player.GetEquippedItem().m_ItemType; + Pkt.WriteBEInt16(ItemType); + Pkt.WriteBEUInt8((3 << 5) | 6); // Metadata: float + index 6 + Pkt.WriteBEFloat(static_cast(a_Player.GetHealth())); + Pkt.WriteBEUInt8((4 << 5 | (2 & 0x1F)) & 0xFF); + Pkt.WriteString(a_Player.GetName()); + Pkt.WriteBEUInt8(0x7f); // Metadata: end +} + + + + + +void cProtocol_1_8_0::SendPluginMessage(const AString & a_Channel, const AString & a_Message) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3f); + Pkt.WriteString(a_Channel); + Pkt.WriteBuf(a_Message.data(), a_Message.size()); +} + + + + + +void cProtocol_1_8_0::SendRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1e); + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_EffectID)); +} + + + + + +void cProtocol_1_8_0::SendResetTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(4); // Reset title +} + + + + + +void cProtocol_1_8_0::SendRespawn(eDimension a_Dimension) +{ + + cPacketizer Pkt(*this, 0x07); // 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"); +} + + + + + +void cProtocol_1_8_0::SendExperience(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1f); // Experience Packet + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteBEFloat(Player->GetXpPercentage()); + Pkt.WriteVarInt32(static_cast(Player->GetXpLevel())); + Pkt.WriteVarInt32(static_cast(Player->GetCurrentXp())); +} + + + + + +void cProtocol_1_8_0::SendExperienceOrb(const cExpOrb & a_ExpOrb) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x11); + Pkt.WriteVarInt32(a_ExpOrb.GetUniqueID()); + Pkt.WriteFPInt(a_ExpOrb.GetPosX()); + Pkt.WriteFPInt(a_ExpOrb.GetPosY()); + Pkt.WriteFPInt(a_ExpOrb.GetPosZ()); + Pkt.WriteBEInt16(static_cast(a_ExpOrb.GetReward())); +} + + + + + +void cProtocol_1_8_0::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3b); + Pkt.WriteString(a_Name); + Pkt.WriteBEUInt8(a_Mode); + if ((a_Mode == 0) || (a_Mode == 2)) + { + Pkt.WriteString(a_DisplayName); + Pkt.WriteString("integer"); + } +} + + + + + +void cProtocol_1_8_0::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, 0x3c); + Pkt.WriteString(a_Player); + Pkt.WriteBEUInt8(a_Mode); + Pkt.WriteString(a_Objective); + + if (a_Mode != 1) + { + Pkt.WriteVarInt32(static_cast(a_Score)); + } +} + + + + + +void cProtocol_1_8_0::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3d); + Pkt.WriteBEUInt8(static_cast(a_Display)); + Pkt.WriteString(a_Objective); +} + + + + + +void cProtocol_1_8_0::SendSetSubTitle(const cCompositeChat & a_SubTitle) +{ + SendSetRawSubTitle(a_SubTitle.CreateJsonString(false)); +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::SendSetTitle(const cCompositeChat & a_Title) +{ + SendSetRawTitle(a_Title.CreateJsonString(false)); +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::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, 0x29); // Sound Effect packet + Pkt.WriteString(a_SoundName); + 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 cProtocol_1_8_0::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, 0x28); // Effect packet + Pkt.WriteBEInt32(static_cast(a_EffectID)); + Pkt.WritePosition64(a_SrcX, a_SrcY, a_SrcZ); + Pkt.WriteBEInt32(a_Data); + Pkt.WriteBool(false); +} + + + + + +void cProtocol_1_8_0::SendSpawnFallingBlock(const cFallingBlock & a_FallingBlock) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0e); // Spawn Object packet + Pkt.WriteVarInt32(a_FallingBlock.GetUniqueID()); + Pkt.WriteBEUInt8(70); // Falling block + Pkt.WriteFPInt(a_FallingBlock.GetPosX()); + Pkt.WriteFPInt(a_FallingBlock.GetPosY()); + Pkt.WriteFPInt(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 cProtocol_1_8_0::SendSpawnMob(const cMonster & a_Mob) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0f); // Spawn Mob packet + Pkt.WriteVarInt32(a_Mob.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_Mob.GetMobType())); + Pkt.WriteFPInt(a_Mob.GetPosX()); + Pkt.WriteFPInt(a_Mob.GetPosY()); + Pkt.WriteFPInt(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(0x7f); // Metadata terminator +} + + + + + +void cProtocol_1_8_0::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, 0xe); // Spawn Object packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_ObjectType)); + Pkt.WriteFPInt(PosX); + Pkt.WriteFPInt(a_Entity.GetPosY()); + Pkt.WriteFPInt(PosZ); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteByteAngle(Yaw); + Pkt.WriteBEInt32(a_ObjectData); + if (a_ObjectData != 0) + { + 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 cProtocol_1_8_0::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0xe); // Spawn Object packet + Pkt.WriteVarInt32(a_Vehicle.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast(a_VehicleType)); + Pkt.WriteFPInt(a_Vehicle.GetPosX()); + Pkt.WriteFPInt(a_Vehicle.GetPosY()); + Pkt.WriteFPInt(a_Vehicle.GetPosZ()); + Pkt.WriteByteAngle(a_Vehicle.GetPitch()); + Pkt.WriteByteAngle(a_Vehicle.GetYaw()); + Pkt.WriteBEInt32(a_VehicleSubType); + if (a_VehicleSubType != 0) + { + 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 cProtocol_1_8_0::SendStatistics(const cStatManager & a_Manager) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x37); + 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 cProtocol_1_8_0::SendTabCompletionResults(const AStringVector & a_Results) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3a); // 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 cProtocol_1_8_0::SendTeleportEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x18); + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteFPInt(a_Entity.GetPosX()); + Pkt.WriteFPInt(a_Entity.GetPosY()); + Pkt.WriteFPInt(a_Entity.GetPosZ()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol_1_8_0::SendThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2c); // Spawn Global Entity packet + Pkt.WriteVarInt32(0); // EntityID = 0, always + Pkt.WriteBEUInt8(1); // Type = Thunderbolt + Pkt.WriteFPInt(a_BlockX); + Pkt.WriteFPInt(a_BlockY); + Pkt.WriteFPInt(a_BlockZ); +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::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, 0x03); + Pkt.WriteBEInt64(a_WorldAge); + Pkt.WriteBEInt64(a_TimeOfDay); +} + + + + + +void cProtocol_1_8_0::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x21); // Chunk Data packet + Pkt.WriteBEInt32(a_ChunkX); + Pkt.WriteBEInt32(a_ChunkZ); + Pkt.WriteBool(true); + Pkt.WriteBEInt16(0); // Primary bitmap + Pkt.WriteVarInt32(0); // Data size +} + + + + +void cProtocol_1_8_0::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x35); // 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 cProtocol_1_8_0::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, 0x33); + 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 cProtocol_1_8_0::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x0a); + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WritePosition64(a_BlockX, a_BlockY, a_BlockZ); +} + + + + + +void cProtocol_1_8_0::SendWeather(eWeather a_Weather) +{ + ASSERT(m_State == 3); // In game mode? + + { + cPacketizer Pkt(*this, 0x2b); // 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 cProtocol_1_8_0::SendWholeInventory(const cWindow & a_Window) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x30); // 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 cProtocol_1_8_0::SendWindowClose(const cWindow & a_Window) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x2e); + Pkt.WriteBEInt8(a_Window.GetWindowID()); +} + + + + + +void cProtocol_1_8_0::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, 0x2d); + 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 cProtocol_1_8_0::SendWindowProperty(const cWindow & a_Window, short a_Property, short a_Value) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x31); // Window Property packet + Pkt.WriteBEInt8(a_Window.GetWindowID()); + Pkt.WriteBEInt16(a_Property); + Pkt.WriteBEInt16(a_Value); +} + + + + + +bool cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::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("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.8: 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("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 cProtocol_1_8_0::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: HandlePacketKeepAlive (a_ByteBuffer); return true; + case 0x01: HandlePacketChatMessage (a_ByteBuffer); return true; + case 0x02: HandlePacketUseEntity (a_ByteBuffer); return true; + case 0x03: HandlePacketPlayer (a_ByteBuffer); return true; + case 0x04: HandlePacketPlayerPos (a_ByteBuffer); return true; + case 0x05: HandlePacketPlayerLook (a_ByteBuffer); return true; + case 0x06: HandlePacketPlayerPosLook (a_ByteBuffer); return true; + case 0x07: HandlePacketBlockDig (a_ByteBuffer); return true; + case 0x08: HandlePacketBlockPlace (a_ByteBuffer); return true; + case 0x09: HandlePacketSlotSelect (a_ByteBuffer); return true; + case 0x0a: HandlePacketAnimation (a_ByteBuffer); return true; + case 0x0b: HandlePacketEntityAction (a_ByteBuffer); return true; + case 0x0c: HandlePacketSteerVehicle (a_ByteBuffer); return true; + case 0x0d: HandlePacketWindowClose (a_ByteBuffer); return true; + case 0x0e: HandlePacketWindowClick (a_ByteBuffer); return true; + case 0x0f: // Confirm transaction - not used in MCS + case 0x10: HandlePacketCreativeInventoryAction(a_ByteBuffer); return true; + case 0x11: HandlePacketEnchantItem (a_ByteBuffer); return true; + case 0x12: HandlePacketUpdateSign (a_ByteBuffer); return true; + case 0x13: HandlePacketPlayerAbilities (a_ByteBuffer); return true; + case 0x14: HandlePacketTabComplete (a_ByteBuffer); return true; + case 0x15: HandlePacketClientSettings (a_ByteBuffer); return true; + case 0x16: HandlePacketClientStatus (a_ByteBuffer); return true; + case 0x17: HandlePacketPluginMessage (a_ByteBuffer); return true; + case 0x18: HandlePacketSpectate (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 cProtocol_1_8_0::HandlePacketStatusPing(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Timestamp); + + cPacketizer Pkt(*this, 0x01); // Ping packet + Pkt.WriteBEInt64(Timestamp); +} + + + + + +void cProtocol_1_8_0::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.8"; + Version["protocol"] = 47; + + // Players: + Json::Value Players; + Players["online"] = NumPlayers; + Players["max"] = MaxPlayers; + // TODO: Add "sample" + + // Description: + Json::Value Description; + Description["text"] = ServerDescription.c_str(); + + // Create the response: + Json::Value ResponseValue; + ResponseValue["version"] = Version; + ResponseValue["players"] = Players; + ResponseValue["description"] = Description; + if (!Favicon.empty()) + { + ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); + } + + Json::StyledWriter Writer; + AString Response = Writer.write(ResponseValue); + + cPacketizer Pkt(*this, 0x00); // Response packet + Pkt.WriteString(Response); +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::HandlePacketAnimation(cByteBuffer & a_ByteBuffer) +{ + m_Client->HandleAnimation(0); // Packet exists solely for arm-swing notification +} + + + + + +void cProtocol_1_8_0::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, ReadBEInt8, Int8, Face); + m_Client->HandleLeftClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), Status); +} + + + + + +void cProtocol_1_8_0::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) +{ + int BlockX, BlockY, BlockZ; + if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) + { + return; + } + + HANDLE_READ(a_ByteBuffer, ReadBEInt8, Int8, Face); + + cItem Item; + ReadItem(a_ByteBuffer, Item, 3); + + 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 cProtocol_1_8_0::HandlePacketChatMessage(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Message); + m_Client->HandleChat(Message); +} + + + + + +void cProtocol_1_8_0::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); + + m_Client->SetLocale(Locale); + m_Client->SetViewDistance(ViewDistance); + // TODO: Handle other values +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::HandlePacketKeepAlive(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, KeepAliveID); + m_Client->HandleKeepAlive(KeepAliveID); +} + + + + + +void cProtocol_1_8_0::HandlePacketPlayer(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); + // TODO: m_Client->HandlePlayerOnGround(IsOnGround); +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::HandlePacketSlotSelect(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, SlotNum); + m_Client->HandleSlotSelected(SlotNum); +} + + + + + +void cProtocol_1_8_0::HandlePacketSpectate(cByteBuffer &a_ByteBuffer) +{ + AString playerUUID; + if (!a_ByteBuffer.ReadUUID(playerUUID)) + { + return; + } + + m_Client->HandleSpectate(playerUUID); +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::HandlePacketTabComplete(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Text); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, HasPosition); + + if (HasPosition) + { + HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Position); + } + + m_Client->HandleTabCompletion(Text); +} + + + + + +void cProtocol_1_8_0::HandlePacketUpdateSign(cByteBuffer & a_ByteBuffer) +{ + int BlockX, BlockY, BlockZ; + if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) + { + return; + } + + AString Lines[4]; + Json::Value root; + Json::Reader reader; + for (int i = 0; i < 4; i++) + { + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Line); + if (reader.parse(Line, root, false)) + { + Lines[i] = root.asString(); + } + } + + m_Client->HandleUpdateSign(BlockX, BlockY, BlockZ, Lines[0], Lines[1], Lines[2], Lines[3]); +} + + + + + +void cProtocol_1_8_0::HandlePacketUseEntity(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, EntityID); + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, Type); + + switch (Type) + { + case 0: + { + 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); + + // TODO: Do anything + break; + } + default: + { + ASSERT(!"Unhandled use entity type!"); + return; + } + } +} + + + + + +void cProtocol_1_8_0::HandlePacketEnchantItem(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Enchantment); + + m_Client->HandleEnchantItem(WindowID, Enchantment); +} + + + + + +void cProtocol_1_8_0::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, ReadBEUInt8, UInt8, 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 cProtocol_1_8_0::HandlePacketWindowClose(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); + m_Client->HandleWindowClose(WindowID); +} + + + + + +void cProtocol_1_8_0::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", "\x08""Cuberite"); + 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 cProtocol_1_8_0::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 cProtocol_1_8_0::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 cProtocol_1_8_0::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)); + } + break; + } + case TAG_Int: + { + if (TagName == "RepairCost") + { + a_Item.m_RepairCost = NBT.GetInt(tag); + } + } + default: LOGD("Unimplemented NBT data when parsing!"); break; + } + } +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::FaceIntToBlockFace(Int8 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; + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol_1_8_0::cPacketizer: + +void cProtocol_1_8_0::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 (!cProtocol_1_8_0::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 cProtocol_1_8_0::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; + } + + a_Pkt.WriteBEInt16(ItemType); + a_Pkt.WriteBEInt8(a_Item.m_ItemCount); + a_Pkt.WriteBEInt16(a_Item.m_ItemDamage); + + if (a_Item.m_Enchantments.IsEmpty() && a_Item.IsBothNameAndLoreEmpty() && (a_Item.m_ItemType != E_ITEM_FIREWORK_ROCKET) && (a_Item.m_ItemType != E_ITEM_FIREWORK_STAR) && !a_Item.m_ItemColor.IsValid()) + { + 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)); + } + Writer.Finish(); + + AString Result = Writer.GetResult(); + if (Result.size() == 0) + { + a_Pkt.WriteBEInt8(0); + return; + } + a_Pkt.WriteBuf(Result.data(), Result.size()); +} + + + + + +void cProtocol_1_8_0::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 cProtocol_1_8_0::WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) +{ + // Common metadata: + Byte 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); // Byte(0) + index 0 + a_Pkt.WriteBEUInt8(Flags); + + switch (a_Entity.GetEntityType()) + { + case cEntity::etPlayer: break; // TODO? + case cEntity::etPickup: + { + a_Pkt.WriteBEUInt8((5 << 5) | 10); // Slot(5) + index 10 + WriteItem(a_Pkt, reinterpret_cast(a_Entity).GetItem()); + break; + } + case cEntity::etMinecart: + { + a_Pkt.WriteBEUInt8(0x51); + + // 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.WriteBEInt32((((a_Entity.GetMaxHealth() / 2) - (a_Entity.GetHealth() - (a_Entity.GetMaxHealth() / 2))) * Minecart.LastDamage()) * 4); + a_Pkt.WriteBEUInt8(0x52); + a_Pkt.WriteBEInt32(1); // Shaking direction, doesn't seem to affect anything + a_Pkt.WriteBEUInt8(0x73); + a_Pkt.WriteBEFloat(static_cast(Minecart.LastDamage() + 10)); // Damage taken / shake effect multiplyer + + if (Minecart.GetPayload() == cMinecart::mpNone) + { + auto & RideableMinecart = reinterpret_cast(Minecart); + const cItem & MinecartContent = RideableMinecart.GetContent(); + if (!MinecartContent.IsEmpty()) + { + a_Pkt.WriteBEUInt8(0x54); + int Content = MinecartContent.m_ItemType; + Content |= MinecartContent.m_ItemDamage << 8; + a_Pkt.WriteBEInt32(Content); + a_Pkt.WriteBEUInt8(0x55); + a_Pkt.WriteBEInt32(RideableMinecart.GetBlockHeight()); + a_Pkt.WriteBEUInt8(0x56); + a_Pkt.WriteBEUInt8(1); + } + } + else if (Minecart.GetPayload() == cMinecart::mpFurnace) + { + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(reinterpret_cast(Minecart).IsFueled() ? 1 : 0); + } + break; + } // case etMinecart + + case cEntity::etProjectile: + { + auto & Projectile = reinterpret_cast(a_Entity); + switch (Projectile.GetProjectileKind()) + { + case cProjectileEntity::pkArrow: + { + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(reinterpret_cast(Projectile).IsCritical() ? 1 : 0); + break; + } + case cProjectileEntity::pkFirework: + { + a_Pkt.WriteBEUInt8(0xa8); + WriteItem(a_Pkt, reinterpret_cast(Projectile).GetItem()); + break; + } + 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(0xa8); + WriteItem(a_Pkt, Frame.GetItem()); + a_Pkt.WriteBEUInt8(0x09); + a_Pkt.WriteBEUInt8(Frame.GetItemRotation()); + break; + } // case etItemFrame + + default: + { + break; + } + } +} + + + + + +void cProtocol_1_8_0::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) +{ + // Living Enitiy Metadata + if (a_Mob.HasCustomName()) + { + a_Pkt.WriteBEUInt8(0x82); + a_Pkt.WriteString(a_Mob.GetCustomName()); + + a_Pkt.WriteBEUInt8(0x03); + a_Pkt.WriteBool(a_Mob.IsCustomNameAlwaysVisible()); + } + + a_Pkt.WriteBEUInt8(0x66); + a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); + + switch (a_Mob.GetMobType()) + { + case mtBat: + { + auto & Bat = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(Bat.IsHanging() ? 1 : 0); + break; + } // case mtBat + + case mtCreeper: + { + auto & Creeper = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(Creeper.IsBlowing() ? 1 : 255); + a_Pkt.WriteBEUInt8(0x11); + a_Pkt.WriteBEUInt8(Creeper.IsCharged() ? 1 : 0); + break; + } // case mtCreeper + + case mtEnderman: + { + auto & Enderman = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x30); + a_Pkt.WriteBEInt16(static_cast(Enderman.GetCarriedBlock())); + a_Pkt.WriteBEUInt8(0x11); + a_Pkt.WriteBEUInt8(static_cast(Enderman.GetCarriedMeta())); + a_Pkt.WriteBEUInt8(0x12); + a_Pkt.WriteBEUInt8(Enderman.IsScreaming() ? 1 : 0); + break; + } // case mtEnderman + + case mtGhast: + { + auto & Ghast = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(Ghast.IsCharging()); + break; + } // case mtGhast + + case mtHorse: + { + auto & Horse = reinterpret_cast(a_Mob); + int 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(0x50); // Int at index 16 + a_Pkt.WriteBEInt32(Flags); + a_Pkt.WriteBEUInt8(0x13); // Byte at index 19 + a_Pkt.WriteBEUInt8(static_cast(Horse.GetHorseType())); + a_Pkt.WriteBEUInt8(0x54); // Int at index 20 + int Appearance = 0; + Appearance = Horse.GetHorseColor(); + Appearance |= Horse.GetHorseStyle() << 8; + a_Pkt.WriteBEInt32(Appearance); + a_Pkt.WriteBEUInt8(0x56); // Int at index 22 + a_Pkt.WriteBEInt32(Horse.GetHorseArmour()); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Horse.IsBaby() ? -1 : (Horse.IsInLoveCooldown() ? 1 : 0)); + break; + } // case mtHorse + + case mtMagmaCube: + { + auto & MagmaCube = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(static_cast(MagmaCube.GetSize())); + break; + } // case mtMagmaCube + + case mtOcelot: + { + auto & Ocelot = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Ocelot.IsBaby() ? -1 : (Ocelot.IsInLoveCooldown() ? 1 : 0)); + break; + } // case mtOcelot + + case mtCow: + { + auto & Cow = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Cow.IsBaby() ? -1 : (Cow.IsInLoveCooldown() ? 1 : 0)); + break; + } // case mtCow + + case mtChicken: + { + auto & Chicken = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Chicken.IsBaby() ? -1 : (Chicken.IsInLoveCooldown() ? 1 : 0)); + break; + } // case mtChicken + + case mtPig: + { + auto & Pig = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Pig.IsBaby() ? -1 : (Pig.IsInLoveCooldown() ? 1 : 0)); + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(Pig.IsSaddled() ? 1 : 0); + break; + } // case mtPig + + case mtSheep: + { + auto & Sheep = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Sheep.IsBaby() ? -1 : (Sheep.IsInLoveCooldown() ? 1 : 0)); + + a_Pkt.WriteBEUInt8(0x10); + Byte SheepMetadata = 0; + SheepMetadata = static_cast(Sheep.GetFurColor()); + if (Sheep.IsSheared()) + { + SheepMetadata |= 0x10; + } + a_Pkt.WriteBEUInt8(SheepMetadata); + break; + } // case mtSheep + + case mtRabbit: + { + auto & Rabbit = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x12); + a_Pkt.WriteBEUInt8(static_cast(Rabbit.GetRabbitType())); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Rabbit.IsBaby() ? -1 : (Rabbit.IsInLoveCooldown() ? 1 : 0)); + break; + } // case mtRabbit + + case mtSkeleton: + { + auto & Skeleton = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0d); + a_Pkt.WriteBEUInt8(Skeleton.IsWither() ? 1 : 0); + break; + } // case mtSkeleton + + case mtSlime: + { + auto & Slime = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(static_cast(Slime.GetSize())); + break; + } // case mtSlime + + case mtVillager: + { + auto & Villager = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x50); + a_Pkt.WriteBEInt32(Villager.GetVilType()); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Villager.IsBaby() ? -1 : 0); + break; + } // case mtVillager + + case mtWitch: + { + auto & Witch = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x15); + a_Pkt.WriteBEUInt8(Witch.IsAngry() ? 1 : 0); + break; + } // case mtWitch + + case mtWither: + { + auto & Wither = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x54); // Int at index 20 + a_Pkt.WriteBEInt32(static_cast(Wither.GetWitherInvulnerableTicks())); + a_Pkt.WriteBEUInt8(0x66); // Float at index 6 + a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); + break; + } // case mtWither + + case mtWolf: + { + auto & Wolf = reinterpret_cast(a_Mob); + Byte WolfStatus = 0; + if (Wolf.IsSitting()) + { + WolfStatus |= 0x1; + } + if (Wolf.IsAngry()) + { + WolfStatus |= 0x2; + } + if (Wolf.IsTame()) + { + WolfStatus |= 0x4; + } + a_Pkt.WriteBEUInt8(0x10); + a_Pkt.WriteBEUInt8(WolfStatus); + + a_Pkt.WriteBEUInt8(0x72); + a_Pkt.WriteBEFloat(static_cast(a_Mob.GetHealth())); + a_Pkt.WriteBEUInt8(0x13); + a_Pkt.WriteBEUInt8(Wolf.IsBegging() ? 1 : 0); + a_Pkt.WriteBEUInt8(0x14); + a_Pkt.WriteBEUInt8(static_cast(Wolf.GetCollarColor())); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Wolf.IsBaby() ? -1 : 0); + break; + } // case mtWolf + + case mtZombie: + { + auto & Zombie = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Zombie.IsBaby() ? 1 : -1); + a_Pkt.WriteBEUInt8(0x0d); + a_Pkt.WriteBEUInt8(Zombie.IsVillagerZombie() ? 1 : 0); + a_Pkt.WriteBEUInt8(0x0e); + a_Pkt.WriteBEUInt8(Zombie.IsConverting() ? 1 : 0); + break; + } // case mtZombie + + case mtZombiePigman: + { + auto & ZombiePigman = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(ZombiePigman.IsBaby() ? 1 : -1); + break; + } // case mtZombiePigman + } // switch (a_Mob.GetType()) +} + + + + + +void cProtocol_1_8_0::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 +} + + + diff --git a/src/Protocol/Protocol_1_8.h b/src/Protocol/Protocol_1_8.h new file mode 100644 index 000000000..564443a38 --- /dev/null +++ b/src/Protocol/Protocol_1_8.h @@ -0,0 +1,267 @@ + +// Protocol_1_8.h + +/* +Declares the 1.8 protocol classes: + - cProtocol_1_8_0 + - release 1.8 protocol (#47) +*/ + + + + + +#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 cProtocol_1_8_0 : + public cProtocol +{ + typedef cProtocol super; + +public: + + cProtocol_1_8_0(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 SendCameraSetTo (const cEntity & a_Entity) 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 SendChatRaw (const AString & a_MessageRaw, eChatType a_Type) 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, int a_Count) 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) 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; + + /** 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): + void HandlePacketStatusPing(cByteBuffer & a_ByteBuffer); + 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 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 HandlePacketSpectate (cByteBuffer & a_ByteBuffer); + void HandlePacketSteerVehicle (cByteBuffer & a_ByteBuffer); + void HandlePacketTabComplete (cByteBuffer & a_ByteBuffer); + void HandlePacketUpdateSign (cByteBuffer & a_ByteBuffer); + void HandlePacketUseEntity (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(Int8 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 0x7f. */ + 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); +} ; + + + diff --git a/src/Protocol/Protocol_1_9.cpp b/src/Protocol/Protocol_1_9.cpp new file mode 100644 index 000000000..680675ca3 --- /dev/null +++ b/src/Protocol/Protocol_1_9.cpp @@ -0,0 +1,4406 @@ + +// Protocol_1_9.cpp + +/* +Implements the 1.9 protocol classes: + - cProtocol_1_9_0 + - release 1.9 protocol (#107) + - cProtocol_1_9_1 + - release 1.9.1 protocol (#108) + - cProtocol_1_9_2 + - release 1.9.2 protocol (#109) + - cProtocol_1_9_4 + - release 1.9.4 protocol (#110) +*/ + +#include "Globals.h" +#include "json/json.h" +#include "Protocol_1_9.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/Boat.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; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol_1_9_0: + +cProtocol_1_9_0::cProtocol_1_9_0(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) +{ + + // 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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::SendCameraSetTo(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x36); // Camera Packet (Attach the camera of a player at another entity in spectator mode) + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol_1_9_0::SendChat(const AString & a_Message, eChatType a_Type) +{ + ASSERT(m_State == 3); // In game mode? + + SendChatRaw(Printf("{\"text\":\"%s\"}", EscapeString(a_Message).c_str()), a_Type); +} + + + + + +void cProtocol_1_9_0::SendChat(const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) +{ + ASSERT(m_State == 3); // In game mode? + + SendChatRaw(a_Message.CreateJsonString(a_ShouldUseChatPrefixes), a_Type); +} + + + + + +void cProtocol_1_9_0::SendChatRaw(const AString & a_MessageRaw, eChatType a_Type) +{ + ASSERT(m_State == 3); // In game mode? + + // Send the json string to the client: + cPacketizer Pkt(*this, 0x0f); // Chat Message packet + Pkt.WriteString(a_MessageRaw); + Pkt.WriteBEInt8(a_Type); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) +{ + UNUSED(a_Count); + 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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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(static_cast(a_SlotNum)); + WriteItem(Pkt, a_Item); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::SendHideTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(3); // Hide title +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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? + } + + // 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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::SendPickupSpawn(const cPickup & a_Pickup) +{ + ASSERT(m_State == 3); // In game mode? + + { // TODO Use SendSpawnObject + 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); + } + + SendEntityMetadata(a_Pickup); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::SendPlayerPosition(void) +{ + // There is no dedicated packet for this, send the whole thing: + SendPlayerMoveLook(); +} + + + + + +void cProtocol_1_9_0::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()); + WriteEntityMetadata(Pkt, a_Player); + Pkt.WriteBEUInt8(0xff); // Metadata: end +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::SendResetTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x45); // Title packet + Pkt.WriteVarInt32(4); // Reset title +} + + + + + +void cProtocol_1_9_0::SendRespawn(eDimension a_Dimension) +{ + 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"); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::SendSetSubTitle(const cCompositeChat & a_SubTitle) +{ + SendSetRawSubTitle(a_SubTitle.CreateJsonString(false)); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::SendSetTitle(const cCompositeChat & a_Title) +{ + SendSetRawTitle(a_Title.CreateJsonString(false)); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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: HandlePacketVehicleMove (a_ByteBuffer); return true; + case 0x11: HandlePacketBoatSteer (a_ByteBuffer); return true; + 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: HandlePacketSpectate (a_ByteBuffer); return true; + 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 cProtocol_1_9_0::HandlePacketStatusPing(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Timestamp); + + cPacketizer Pkt(*this, 0x01); // Ping packet + Pkt.WriteBEInt64(Timestamp); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::HandlePacketAnimation(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Hand); + + m_Client->HandleAnimation(0); // Packet exists solely for arm-swing notification +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::HandlePacketBoatSteer(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBool, bool, RightPaddle); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, LeftPaddle); + + // Get the players vehicle + cPlayer * Player = m_Client->GetPlayer(); + cEntity * Vehicle = Player->GetAttached(); + + if (Vehicle) + { + if (Vehicle->GetEntityType() == cEntity::etBoat) + { + auto * Boat = reinterpret_cast(Vehicle); + Boat->UpdatePaddles(RightPaddle, LeftPaddle); + } + } +} + + + + + +void cProtocol_1_9_0::HandlePacketChatMessage(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Message); + m_Client->HandleChat(Message); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::HandlePacketKeepAlive(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, KeepAliveID); + m_Client->HandleKeepAlive(KeepAliveID); +} + + + + + +void cProtocol_1_9_0::HandlePacketPlayer(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); + // TODO: m_Client->HandlePlayerOnGround(IsOnGround); +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::HandlePacketSlotSelect(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, SlotNum); + m_Client->HandleSlotSelected(SlotNum); +} + + + + + +void cProtocol_1_9_0::HandlePacketSpectate(cByteBuffer & a_ByteBuffer) +{ + AString playerUUID; + if (!a_ByteBuffer.ReadUUID(playerUUID)) + { + return; + } + + m_Client->HandleSpectate(playerUUID); +} + + + + + +void cProtocol_1_9_0::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) + { + // TODO: Handle vehicle jump (for animals) + } + else + { + m_Client->HandleSteerVehicle(Forward, Sideways); + } +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::HandlePacketEnchantItem(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Enchantment); + + m_Client->HandleEnchantItem(WindowID, Enchantment); +} + + + + + +void cProtocol_1_9_0::HandlePacketVehicleMove(cByteBuffer & a_ByteBuffer) +{ + // This handles updating the vehicles location server side + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, xPos); + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, yPos); + HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, zPos); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, yaw); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, pitch); + + // Get the players vehicle + cEntity * Vehicle = m_Client->GetPlayer()->GetAttached(); + + if (Vehicle) + { + Vehicle->SetPosX(xPos); + Vehicle->SetPosY(yPos); + Vehicle->SetPosZ(zPos); + Vehicle->SetYaw(yaw); + Vehicle->SetPitch(pitch); + } +} + + + + + +void cProtocol_1_9_0::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 cProtocol_1_9_0::HandlePacketWindowClose(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); + m_Client->HandleWindowClose(WindowID); +} + + + + + +void cProtocol_1_9_0::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", "\x08""Cuberite"); + 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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::FaceIntToBlockFace(Int32 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; + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol_1_9_0::cPacketizer: + +void cProtocol_1_9_0::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 (!cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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 cProtocol_1_9_0::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: + { + auto & Player = reinterpret_cast(a_Entity); + + // TODO Set player custom name to their name. + // Then it's possible to move the custom name of mobs to the entities + // and to remove the "special" player custom name. + a_Pkt.WriteBEUInt8(2); // Index 2: Custom name + a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); + a_Pkt.WriteString(Player.GetName()); + + a_Pkt.WriteBEUInt8(6); // Start metadata - Index 6: Health + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast(Player.GetHealth())); + break; + } + 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 + auto & Minecart = reinterpret_cast(a_Entity); + auto maxHealth = a_Entity.GetMaxHealth(); + auto curHealth = a_Entity.GetHealth(); + a_Pkt.WriteVarInt32(static_cast((maxHealth - curHealth) * 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(static_cast(Content)); + + a_Pkt.WriteBEUInt8(9); // Index 9: Block ID and damage + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(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::etBoat: + { + auto & Boat = reinterpret_cast(a_Entity); + + a_Pkt.WriteBEInt8(5); // Index 6: Time since last hit + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteBEInt32(Boat.GetLastDamage()); + + a_Pkt.WriteBEInt8(6); // Index 7: Forward direction + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteBEInt32(Boat.GetForwardDirection()); + + a_Pkt.WriteBEInt8(7); // Index 8: Damage taken + a_Pkt.WriteBEInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(Boat.GetDamageTaken()); + + a_Pkt.WriteBEInt8(8); // Index 9: Type + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteBEInt32(Boat.GetType()); + + a_Pkt.WriteBEInt8(9); // Index 10: Right paddle turning + a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Boat.IsRightPaddleUsed()); + + a_Pkt.WriteBEInt8(10); // Index 11: Left paddle turning + a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Boat.IsLeftPaddleUsed()); + + break; + } // case etBoat + + 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 cProtocol_1_9_0::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 : 0xffffffff); + + 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 |= static_cast(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(static_cast(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(static_cast(Appearance)); + + a_Pkt.WriteBEUInt8(16); // Index 16: Armor + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(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(static_cast(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(static_cast(Rabbit.GetRabbitType())); + 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(static_cast(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(static_cast(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(static_cast(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 cProtocol_1_9_0::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 +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol_1_9_1: + +cProtocol_1_9_1::cProtocol_1_9_1(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol_1_9_1::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? + } + + // 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 cProtocol_1_9_1::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); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol_1_9_2: + +cProtocol_1_9_2::cProtocol_1_9_2(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol_1_9_2::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); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cProtocol_1_9_4: + +cProtocol_1_9_4::cProtocol_1_9_4(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol_1_9_4::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 cProtocol_1_9_4::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) +{ + UNUSED(a_Count); + 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 cProtocol_1_9_4::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 cProtocol_1_9_4::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 cProtocol_1_9_4::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 cProtocol_1_9_4::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 cProtocol_1_9_4::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 cProtocol_1_9_4::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/Protocol_1_9.h b/src/Protocol/Protocol_1_9.h new file mode 100644 index 000000000..99fc87b69 --- /dev/null +++ b/src/Protocol/Protocol_1_9.h @@ -0,0 +1,358 @@ + +// Protocol_1_9.h + +/* +Declares the 1.9 protocol classes: + - cProtocol_1_9_0 + - release 1.9 protocol (#107) + - cProtocol_1_9_1 + - release 1.9.1 protocol (#108) + - cProtocol_1_9_2 + - release 1.9.2 protocol (#109) + - cProtocol_1_9_4 + - release 1.9.4 protocol (#110) +*/ + + + + + +#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 cProtocol_1_9_0 : + public cProtocol +{ + typedef cProtocol super; + +public: + + cProtocol_1_9_0(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 SendCameraSetTo (const cEntity & a_Entity) 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 SendChatRaw (const AString & a_MessageRaw, eChatType a_Type) 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, int a_Count) 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) 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; + + /** 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. */ + virtual 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): + virtual void HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer); + virtual void HandlePacketLoginStart(cByteBuffer & a_ByteBuffer); + + // Packet handlers while in the Game state (m_State == 3): + virtual void HandlePacketAnimation (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketBlockDig (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketBlockPlace (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketBoatSteer (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketChatMessage (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketClientSettings (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketClientStatus (cByteBuffer & a_ByteBuffer); + virtual void HandleConfirmTeleport (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketCreativeInventoryAction(cByteBuffer & a_ByteBuffer); + virtual void HandlePacketEntityAction (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketKeepAlive (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketPlayer (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketPlayerAbilities (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketPlayerLook (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketPlayerPos (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketPlayerPosLook (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketPluginMessage (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketSlotSelect (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketSteerVehicle (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketSpectate (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketTabComplete (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketUpdateSign (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketUseEntity (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketUseItem (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketEnchantItem (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketVehicleMove (cByteBuffer & a_ByteBuffer); + virtual void HandlePacketWindowClick (cByteBuffer & a_ByteBuffer); + virtual 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 */ + virtual 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(Int32 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. */ + virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity); + + /** Writes the mob-specific metadata for the specified mob */ + virtual 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 cProtocol_1_9_1 : + public cProtocol_1_9_0 +{ + typedef cProtocol_1_9_0 super; + +public: + cProtocol_1_9_1(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + // cProtocol_1_9_0 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 cProtocol_1_9_2 : + public cProtocol_1_9_1 +{ + typedef cProtocol_1_9_1 super; + +public: + cProtocol_1_9_2(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + // cProtocol_1_9_1 overrides: + virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; + +} ; + + + + + +/** The version 110 protocol, used by 1.9.3 and 1.9.4. */ +class cProtocol_1_9_4 : + public cProtocol_1_9_2 +{ + typedef cProtocol_1_9_2 super; + +public: + cProtocol_1_9_4(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + // cProtocol_1_9_2 overrides: + virtual void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) 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; + +} ; + + + + -- cgit v1.2.3