diff --git a/README.md b/README.md index 7939381..f261ced 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ Join with your favorite IRC client. | Game version | Protocol version | Supported? | | ------ | --- | --- | +| 1.16.4 | 754 | ✓ | +| 1.16.3 | 753 | ✓ | +| 1.16.2 | 751 | ✓ | | 1.16.1 | 736 | ✓ | | 1.16 | 735 | ✓ | | 1.15.2 | 578 | ✓ | diff --git a/protocol/src/protocol/mod.rs b/protocol/src/protocol/mod.rs index 0dde0ca..81d9b9c 100644 --- a/protocol/src/protocol/mod.rs +++ b/protocol/src/protocol/mod.rs @@ -40,9 +40,9 @@ use std::net::TcpStream; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use std::time::{Duration, Instant}; -pub const SUPPORTED_PROTOCOLS: [i32; 21] = [ - 736, 735, 578, 575, 498, 490, 485, 480, 477, 452, 451, 404, 340, 316, 315, 210, 109, 107, 74, - 47, 5, +pub const SUPPORTED_PROTOCOLS: [i32; 24] = [ + 754, 753, 751, 736, 735, 578, 575, 498, 490, 485, 480, 477, 452, 451, 404, 340, 316, 315, 210, + 109, 107, 74, 47, 5, ]; static CURRENT_PROTOCOL_VERSION: AtomicI32 = AtomicI32::new(SUPPORTED_PROTOCOLS[0]); @@ -1156,6 +1156,8 @@ impl Conn { let pos = buf.position() as usize; let ibuf = buf.into_inner(); if ibuf.len() != pos { + debug!("pos = {:?}", pos); + debug!("ibuf = {:?}", ibuf); return Result::Err(Error::Err(format!( "Failed to read all of packet 0x{:X}, \ had {} bytes left", diff --git a/protocol/src/protocol/packet.rs b/protocol/src/protocol/packet.rs index 09d9e88..6c5e48d 100644 --- a/protocol/src/protocol/packet.rs +++ b/protocol/src/protocol/packet.rs @@ -353,6 +353,16 @@ state_packets!( field crafting_book_open: bool = when(|p: &CraftingBookData| p.action.0 == 1), field crafting_filter: bool = when(|p: &CraftingBookData| p.action.0 == 1), } + /// SetDisplayedRecipe replaces CraftingBookData, type 0. + packet SetDisplayedRecipe { + field recipe_id: String =, + } + /// SetRecipeBookState replaces CraftingBookData, type 1. + packet SetRecipeBookState { + field book_id: VarInt =, // TODO: enum, 0: crafting, 1: furnace, 2: blast furnace, 3: smoker + field book_open: bool =, + field filter_active: bool =, + } packet NameItem { field item_name: String =, } @@ -881,6 +891,11 @@ state_packets!( field message: format::Component =, } /// MultiBlockChange is used to update a batch of blocks in a single packet. + packet MultiBlockChange_Packed { + field chunk_section_pos: u64 =, + field no_trust_edges: bool =, + field records: LenPrefixed =, + } packet MultiBlockChange_VarInt { field chunk_x: i32 =, field chunk_z: i32 =, @@ -1047,6 +1062,16 @@ state_packets!( } /// ChunkData sends or updates a single chunk on the client. If New is set /// then biome data should be sent too. + packet ChunkData_Biomes3D_VarInt { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: VarInt =, + field heightmaps: Option =, + field biomes: LenPrefixed = when(|p: &ChunkData_Biomes3D_VarInt| p.new), + field data: LenPrefixedBytes =, + field block_entities: LenPrefixed> =, + } packet ChunkData_Biomes3D_bool { field chunk_x: i32 =, field chunk_z: i32 =, @@ -1200,6 +1225,39 @@ state_packets!( } /// JoinGame is sent after completing the login process. This /// sets the initial state for the client. + packet JoinGame_WorldNames_IsHard { + /// The entity id the client will be referenced by + field entity_id: i32 =, + /// Whether hardcore mode is enabled + field is_hardcore: bool =, + /// The starting gamemode of the client + field gamemode: u8 =, + /// The previous gamemode of the client + field previous_gamemode: u8 =, + /// Identifiers for all worlds on the server + field world_names: LenPrefixed =, + /// Represents a dimension registry + field dimension_codec: Option =, + /// The dimension the client is starting in + field dimension: Option =, + /// The world being spawned into + field world_name: String =, + /// Truncated SHA-256 hash of world's seed + field hashed_seed: i64 =, + /// The max number of players on the server + field max_players: VarInt =, + /// The render distance (2-32) + field view_distance: VarInt =, + /// Whether the client should reduce the amount of debug + /// information it displays in F3 mode + field reduced_debug_info: bool =, + /// Whether to prompt or immediately respawn + field enable_respawn_screen: bool =, + /// Whether the world is in debug mode + field is_debug: bool =, + /// Whether the world is a superflat world + field is_flat: bool =, + } packet JoinGame_WorldNames { /// The entity id the client will be referenced by field entity_id: i32 =, @@ -1231,7 +1289,6 @@ state_packets!( /// Whether the world is a superflat world field is_flat: bool =, } - packet JoinGame_HashedSeed_Respawn { /// The entity id the client will be referenced by field entity_id: i32 =, @@ -1527,6 +1584,19 @@ state_packets!( field recipe_ids: LenPrefixed =, field recipe_ids2: LenPrefixed = when(|p: &UnlockRecipes_WithSmelting| p.action.0 == 0), } + packet UnlockRecipes_WithBlastSmoker { + field action: VarInt =, + field crafting_book_open: bool =, + field filtering_craftable: bool =, + field smelting_book_open: bool =, + field filtering_smeltable: bool =, + field blast_furnace_open: bool =, + field filtering_blast_furnace: bool =, + field smoker_open: bool =, + field filtering_smoker: bool =, + field recipe_ids: LenPrefixed =, + field recipe_ids2: LenPrefixed = when(|p: &UnlockRecipes_WithBlastSmoker| p.action.0 == 0), + } /// EntityDestroy destroys the entities with the ids in the provided slice. packet EntityDestroy { field entity_ids: LenPrefixed =, diff --git a/protocol/src/protocol/versions.rs b/protocol/src/protocol/versions.rs index 0033477..78f00c5 100644 --- a/protocol/src/protocol/versions.rs +++ b/protocol/src/protocol/versions.rs @@ -14,6 +14,7 @@ mod v1_14_3; mod v1_14_4; mod v1_15; mod v1_16_1; +mod v1_16_4; mod v1_7_10; mod v1_8_9; mod v1_9; @@ -25,6 +26,9 @@ mod v1_9_2; pub fn protocol_name_to_protocol_version(s: String) -> i32 { match s.as_ref() { "" => SUPPORTED_PROTOCOLS[0], + "1.16.4" => 754, + "1.16.3" => 753, + "1.16.2" => 751, "1.16.1" => 736, "1.16" => 735, "1.15.2" => 578, @@ -64,6 +68,7 @@ pub fn translate_internal_packet_id_for_version( to_internal: bool, ) -> i32 { match version { + 754 | 753 | 751 => v1_16_4::translate_internal_packet_id(state, dir, id, to_internal), 736 => v1_16_1::translate_internal_packet_id(state, dir, id, to_internal), 735 => v1_16_1::translate_internal_packet_id(state, dir, id, to_internal), 578 => v1_15::translate_internal_packet_id(state, dir, id, to_internal), diff --git a/protocol/src/protocol/versions/v1_16_4.rs b/protocol/src/protocol/versions/v1_16_4.rs new file mode 100644 index 0000000..c5e5102 --- /dev/null +++ b/protocol/src/protocol/versions/v1_16_4.rs @@ -0,0 +1,179 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => SetDifficulty + 0x03 => ChatMessage + 0x04 => ClientStatus + 0x05 => ClientSettings + 0x06 => TabComplete + 0x07 => ConfirmTransactionServerbound + 0x08 => ClickWindowButton + 0x09 => ClickWindow + 0x0a => CloseWindow + 0x0b => PluginMessageServerbound + 0x0c => EditBook + 0x0d => QueryEntityNBT + 0x0e => UseEntity_Sneakflag + 0x0f => GenerateStructure + 0x10 => KeepAliveServerbound_i64 + 0x11 => LockDifficulty + 0x12 => PlayerPosition + 0x13 => PlayerPositionLook + 0x14 => PlayerLook + 0x15 => Player + 0x16 => VehicleMove + 0x17 => SteerBoat + 0x18 => PickItem + 0x19 => CraftRecipeRequest + 0x1a => ClientAbilities_u8 + 0x1b => PlayerDigging + 0x1c => PlayerAction + 0x1d => SteerVehicle + 0x1e => SetDisplayedRecipe + 0x1f => SetRecipeBookState + 0x20 => NameItem + 0x21 => ResourcePackStatus + 0x22 => AdvancementTab + 0x23 => SelectTrade + 0x24 => SetBeaconEffect + 0x25 => HeldItemChange + 0x26 => UpdateCommandBlock + 0x27 => UpdateCommandBlockMinecart + 0x28 => CreativeInventoryAction + 0x29 => UpdateJigsawBlock_Joint + 0x2a => UpdateStructureBlock + 0x2b => SetSign + 0x2c => ArmSwing + 0x2d => SpectateTeleport + 0x2e => PlayerBlockPlacement_insideblock + 0x2f => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject_VarInt + 0x01 => SpawnExperienceOrb + 0x02 => SpawnMob_NoMeta + 0x03 => SpawnPainting_VarInt + 0x04 => SpawnPlayer_f64_NoMeta + 0x05 => Animation + 0x06 => Statistics + 0x07 => AcknowledgePlayerDigging + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty_Locked + 0x0e => ServerMessage_Sender + 0x0f => TabCompleteReply + 0x10 => DeclareCommands + 0x11 => ConfirmTransaction + 0x12 => WindowClose + 0x13 => WindowItems + 0x14 => WindowProperty + 0x15 => WindowSetSlot + 0x16 => SetCooldown + 0x17 => PluginMessageClientbound + 0x18 => NamedSoundEffect + 0x19 => Disconnect + 0x1a => EntityAction + 0x1b => Explosion + 0x1c => ChunkUnload + 0x1d => ChangeGameState + 0x1e => WindowOpenHorse + 0x1f => KeepAliveClientbound_i64 + 0x20 => ChunkData_Biomes3D_VarInt + 0x21 => Effect + 0x22 => Particle_f64 + 0x23 => UpdateLight_WithTrust + 0x24 => JoinGame_WorldNames_IsHard + 0x25 => Maps + 0x26 => TradeList_WithRestock + 0x27 => EntityMove_i16 + 0x28 => EntityLookAndMove_i16 + 0x29 => EntityLook_VarInt + 0x2a => Entity + 0x2b => VehicleTeleport + 0x2c => OpenBook + 0x2d => WindowOpen_VarInt + 0x2e => SignEditorOpen + 0x2f => CraftRecipeResponse + 0x30 => PlayerAbilities + 0x31 => CombatEvent + 0x32 => PlayerInfo + 0x33 => FacePlayer + 0x34 => TeleportPlayer_WithConfirm + 0x35 => UnlockRecipes_WithBlastSmoker + 0x36 => EntityDestroy + 0x37 => EntityRemoveEffect + 0x38 => ResourcePackSend + 0x39 => Respawn_WorldName + 0x3a => EntityHeadLook + 0x3b => MultiBlockChange_Packed + 0x3c => SelectAdvancementTab + 0x3d => WorldBorder + 0x3e => Camera + 0x3f => SetCurrentHotbarSlot + 0x40 => UpdateViewPosition + 0x41 => UpdateViewDistance + 0x42 => SpawnPosition + 0x43 => ScoreboardDisplay + 0x44 => EntityMetadata + 0x45 => EntityAttach + 0x46 => EntityVelocity + 0x47 => EntityEquipment_VarInt // TODO: changed to an array, but earlier than 1.16.1 + 0x48 => SetExperience + 0x49 => UpdateHealth + 0x4a => ScoreboardObjective + 0x4b => SetPassengers + 0x4c => Teams_VarInt + 0x4d => UpdateScore + 0x4e => TimeUpdate + 0x4f => Title + 0x50 => EntitySoundEffect + 0x51 => SoundEffect + 0x52 => StopSound + 0x53 => PlayerListHeaderFooter + 0x54 => NBTQueryResponse + 0x55 => CollectItem + 0x56 => EntityTeleport_f64 + 0x57 => Advancements + 0x58 => EntityProperties + 0x59 => EntityEffect + 0x5a => DeclareRecipes + 0x5b => TagsWithEntities + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess_UUID + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); diff --git a/protocol/src/types/bit/map.rs b/protocol/src/types/bit/map.rs index 66cfc98..3af1fd4 100644 --- a/protocol/src/types/bit/map.rs +++ b/protocol/src/types/bit/map.rs @@ -16,6 +16,7 @@ pub struct Map { bits: Vec, pub bit_size: usize, length: usize, + padded: bool, } #[test] @@ -53,17 +54,20 @@ impl Map { bit_size: size, length: len, bits: Vec::with_capacity((len * size) / 64), + padded: false, }; for _ in 0..len { map.bits.push(0) } map } - pub fn from_raw(bits: Vec, size: usize) -> Map { + + pub fn from_raw(bits: Vec, size: usize, padded: bool) -> Map { Map { length: (bits.len() * 64 + (size - 1)) / size, bit_size: size, bits, + padded, } } @@ -75,8 +79,17 @@ impl Map { n } + fn get_bit_offset(&self, i: usize) -> usize { + let padding = if self.padded { + i / (64 / self.bit_size) * (64 % self.bit_size) + } else { + 0 + }; + i * self.bit_size + padding + } + pub fn set(&mut self, i: usize, val: usize) { - let i = i * self.bit_size; + let i = self.get_bit_offset(i); let pos = i / 64; let mask = (1u64 << self.bit_size) - 1; let ii = i % 64; @@ -90,7 +103,7 @@ impl Map { } pub fn get(&self, i: usize) -> usize { - let i = i * self.bit_size; + let i = self.get_bit_offset(i); let pos = i / 64; let mask = (1 << self.bit_size) - 1; let ii = i % 64; diff --git a/src/server/mod.rs b/src/server/mod.rs index 44671f6..5281e26 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -509,6 +509,7 @@ impl Server { self pck { PluginMessageClientbound_i16 => on_plugin_message_clientbound_i16, PluginMessageClientbound => on_plugin_message_clientbound_1, + JoinGame_WorldNames_IsHard => on_game_join_worldnames_ishard, JoinGame_WorldNames => on_game_join_worldnames, JoinGame_HashedSeed_Respawn => on_game_join_hashedseed_respawn, JoinGame_i32_ViewDistance => on_game_join_i32_viewdistance, @@ -521,6 +522,7 @@ impl Server { KeepAliveClientbound_i64 => on_keep_alive_i64, KeepAliveClientbound_VarInt => on_keep_alive_varint, KeepAliveClientbound_i32 => on_keep_alive_i32, + ChunkData_Biomes3D_VarInt => on_chunk_data_biomes3d_varint, ChunkData_Biomes3D_bool => on_chunk_data_biomes3d_bool, ChunkData => on_chunk_data, ChunkData_Biomes3D => on_chunk_data_biomes3d, @@ -533,6 +535,7 @@ impl Server { ChunkUnload => on_chunk_unload, BlockChange_VarInt => on_block_change_varint, BlockChange_u8 => on_block_change_u8, + MultiBlockChange_Packed => on_multi_block_change_packed, MultiBlockChange_VarInt => on_multi_block_change_varint, MultiBlockChange_u16 => on_multi_block_change_u16, TeleportPlayer_WithConfirm => on_teleport_player_withconfirm, @@ -970,6 +973,13 @@ impl Server { } } + fn on_game_join_worldnames_ishard( + &mut self, + join: packet::play::clientbound::JoinGame_WorldNames_IsHard, + ) { + self.on_game_join(join.gamemode, join.entity_id) + } + fn on_game_join_worldnames(&mut self, join: packet::play::clientbound::JoinGame_WorldNames) { self.on_game_join(join.gamemode, join.entity_id) } @@ -1777,6 +1787,22 @@ impl Server { } } + fn on_chunk_data_biomes3d_varint( + &mut self, + chunk_data: packet::play::clientbound::ChunkData_Biomes3D_VarInt, + ) { + self.world + .load_chunk115( + chunk_data.chunk_x, + chunk_data.chunk_z, + chunk_data.new, + chunk_data.bitmask.0 as u16, + chunk_data.data.data, + ) + .unwrap(); + self.load_block_entities(chunk_data.block_entities.data); + } + fn on_chunk_data_biomes3d_bool( &mut self, chunk_data: packet::play::clientbound::ChunkData_Biomes3D_bool, @@ -1934,6 +1960,31 @@ impl Server { ); } + fn on_multi_block_change_packed( + &mut self, + block_change: packet::play::clientbound::MultiBlockChange_Packed, + ) { + let sx = (block_change.chunk_section_pos >> 42) as i32; + let sy = ((block_change.chunk_section_pos << 44) >> 44) as i32; + let sz = ((block_change.chunk_section_pos << 22) >> 42) as i32; + + for record in block_change.records.data { + let block_raw_id = record.0 >> 12; + let lz = (record.0 & 0xf) as i32; + let ly = ((record.0 >> 4) & 0xf) as i32; + let lx = ((record.0 >> 8) & 0xf) as i32; + + self.world.set_block( + Position::new(sx + lx as i32, sy + ly as i32, sz + lz as i32), + block::Block::by_vanilla_id( + block_raw_id as usize, + self.protocol_version, + &self.world.modded_block_ids, + ), + ); + } + } + fn on_multi_block_change_varint( &mut self, block_change: packet::play::clientbound::MultiBlockChange_VarInt, diff --git a/src/world/mod.rs b/src/world/mod.rs index 47bd499..9c9a6c4 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1066,7 +1066,8 @@ impl World { } let bits = LenPrefixed::::read_from(&mut data)?.data; - let m = bit::Map::from_raw(bits, bit_size as usize); + let padded = self.protocol_version >= 736; + let m = bit::Map::from_raw(bits, bit_size as usize, padded); for bi in 0..4096 { let id = m.get(bi);