From be6e1f79f16fc1b3a5abc4b5dac4997d9585c348 Mon Sep 17 00:00:00 2001 From: iceiix <43691553+iceiix@users.noreply.github.com> Date: Sun, 2 Dec 2018 19:32:52 -0800 Subject: [PATCH] 1.12.2 protocol support (340) (#40) * Add new 1.12.2 packets and shift IDs CraftRecipeResponse AdvancementTab SelectAdvancementTab Advancements CraftingRecipeRequest UnlockRecipes CraftingBookData * Fix unlock recipes packet, add length-prefixed arrays https://wiki.vg/index.php?title=Protocol&oldid=14204#Unlock_Recipes * Update resources to 1.12.2 * Handle NBTTag metadata (value 13), parsed as nbt::NamedTag https://wiki.vg/index.php?title=Entity_metadata&oldid=14048#Entity_Metadata_Format https://github.com/iceiix/steven/pull/40#issuecomment-443454757 * Fix entity packet IDs, 0x25 now is Entity https://wiki.vg/index.php?title=Protocol&oldid=14204#Entity * Add NBT long array (type 12) support https://wiki.vg/NBT#Specification * Entity metadata type is now a VarInt, not u8: https://wiki.vg/index.php?title=Entity_metadata&oldid=14048#Entity_Metadata_Format * Keep alives changed to longs, no longer VarInts https://wiki.vg/index.php?title=Protocol&oldid=14204#Keep_Alive_.28serverbound.29 * Parse CraftRecipeResponse (0x2b) * Add structs for advancements data * Implement Serializable trait for Advancement and AdvancementDisplay * Implement advancement progress parsing; advancement packet works * Particle packet adds fallingdust (46) with length 1 https://wiki.vg/index.php?title=Protocol&oldid=14204#Particle_2 --- src/nbt/mod.rs | 24 +++++ src/protocol/mod.rs | 115 ++++++++++++----------- src/protocol/packet.rs | 204 ++++++++++++++++++++++++++++++++++++++++- src/resources.rs | 8 +- src/types/metadata.rs | 30 +++++- 5 files changed, 319 insertions(+), 62 deletions(-) diff --git a/src/nbt/mod.rs b/src/nbt/mod.rs index 3e79578..e72fad6 100644 --- a/src/nbt/mod.rs +++ b/src/nbt/mod.rs @@ -34,6 +34,7 @@ pub enum Tag { List(Vec), Compound(HashMap), IntArray(Vec), + LongArray(Vec), } #[derive(Debug, Clone)] @@ -155,6 +156,14 @@ impl Tag { } } + pub fn as_long_array(&self) -> Option<&[i64]> { + match *self { + Tag::LongArray(ref val) => Some(&val[..]), + _ => None, + } + } + + fn internal_id(&self) -> u8 { match *self { Tag::End => 0, @@ -169,6 +178,7 @@ impl Tag { Tag::List(_) => 9, Tag::Compound(_) => 10, Tag::IntArray(_) => 11, + Tag::LongArray(_) => 12, } } @@ -217,6 +227,14 @@ impl Tag { } data })), + 12 => Ok(Tag::LongArray({ + let len: i32 = Serializable::read_from(buf)?; + let mut data = Vec::with_capacity(len as usize); + for _ in 0..len { + data.push(buf.read_i64::()?); + } + data + })), _ => Err(protocol::Error::Err("invalid tag".to_owned())), } } @@ -267,6 +285,12 @@ impl Serializable for Tag { v.write_to(buf)?; } } + Tag::LongArray(ref val) => { + (val.len() as i32).write_to(buf)?; + for v in val { + v.write_to(buf)?; + } + } } Result::Ok(()) } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 7a61b26..c0f7a6c 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -36,7 +36,7 @@ use flate2::Compression; use std::time::{Instant, Duration}; use crate::shared::Position; -pub const SUPPORTED_PROTOCOL: i32 = 316; +pub const SUPPORTED_PROTOCOL: i32 = 340; /// Helper macro for defining packets @@ -96,24 +96,27 @@ pub const CloseWindow: i32 = 0x08; pub const PluginMessageServerbound: i32 = 0x09; pub const UseEntity: i32 = 0x0a; pub const KeepAliveServerbound: i32 = 0x0b; -pub const PlayerPosition: i32 = 0x0c; -pub const PlayerPositionLook: i32 = 0x0d; -pub const PlayerLook: i32 = 0x0e; -pub const Player: i32 = 0x0f; +pub const Player: i32 = 0x0c; +pub const PlayerPosition: i32 = 0x0d; +pub const PlayerPositionLook: i32 = 0x0e; +pub const PlayerLook: i32 = 0x0f; pub const VehicleMove: i32 = 0x10; pub const SteerBoat: i32 = 0x11; -pub const ClientAbilities: i32 = 0x12; -pub const PlayerDigging: i32 = 0x13; -pub const PlayerAction: i32 = 0x14; -pub const SteerVehicle: i32 = 0x15; -pub const ResourcePackStatus: i32 = 0x16; -pub const HeldItemChange: i32 = 0x17; -pub const CreativeInventoryAction: i32 = 0x18; -pub const SetSign: i32 = 0x19; -pub const ArmSwing: i32 = 0x1a; -pub const SpectateTeleport: i32 = 0x1b; -pub const PlayerBlockPlacement: i32 = 0x1c; -pub const UseItem: i32 = 0x1d; +pub const CraftRecipeRequest: i32 = 0x12; +pub const ClientAbilities: i32 = 0x13; +pub const PlayerDigging: i32 = 0x14; +pub const PlayerAction: i32 = 0x15; +pub const SteerVehicle: i32 = 0x16; +pub const CraftingBookData: i32 = 0x17; +pub const ResourcePackStatus: i32 = 0x18; +pub const AdvancementTab: i32 = 0x19; +pub const HeldItemChange: i32 = 0x1a; +pub const CreativeInventoryAction: i32 = 0x1b; +pub const SetSign: i32 = 0x1c; +pub const ArmSwing: i32 = 0x1d; +pub const SpectateTeleport: i32 = 0x1e; +pub const PlayerBlockPlacement: i32 = 0x1f; +pub const UseItem: i32 = 0x20; pub const SpawnObject: i32 = 0x00; pub const SpawnExperienceOrb: i32 = 0x01; pub const SpawnGlobalEntity: i32 = 0x02; @@ -151,45 +154,49 @@ pub const Effect: i32 = 0x21; pub const Particle: i32 = 0x22; pub const JoinGame: i32 = 0x23; pub const Maps: i32 = 0x24; -pub const EntityMove: i32 = 0x25; -pub const EntityLookAndMove: i32 = 0x26; -pub const EntityLook: i32 = 0x27; -pub const Entity: i32 = 0x28; +pub const Entity: i32 = 0x25; +pub const EntityMove: i32 = 0x26; +pub const EntityLookAndMove: i32 = 0x27; +pub const EntityLook: i32 = 0x28; pub const VehicleTeleport: i32 = 0x29; pub const SignEditorOpen: i32 = 0x2a; -pub const PlayerAbilities: i32 = 0x2b; -pub const CombatEvent: i32 = 0x2c; -pub const PlayerInfo: i32 = 0x2d; -pub const TeleportPlayer: i32 = 0x2e; -pub const EntityUsedBed: i32 = 0x2f; -pub const EntityDestroy: i32 = 0x30; -pub const EntityRemoveEffect: i32 = 0x31; -pub const ResourcePackSend: i32 = 0x32; -pub const Respawn: i32 = 0x33; -pub const EntityHeadLook: i32 = 0x34; -pub const WorldBorder: i32 = 0x35; -pub const Camera: i32 = 0x36; -pub const SetCurrentHotbarSlot: i32 = 0x37; -pub const ScoreboardDisplay: i32 = 0x38; -pub const EntityMetadata: i32 = 0x39; -pub const EntityAttach: i32 = 0x3a; -pub const EntityVelocity: i32 = 0x3b; -pub const EntityEquipment: i32 = 0x3c; -pub const SetExperience: i32 = 0x3d; -pub const UpdateHealth: i32 = 0x3e; -pub const ScoreboardObjective: i32 = 0x3f; -pub const SetPassengers: i32 = 0x40; -pub const Teams: i32 = 0x41; -pub const UpdateScore: i32 = 0x42; -pub const SpawnPosition: i32 = 0x43; -pub const TimeUpdate: i32 = 0x44; -pub const Title: i32 = 0x45; -pub const SoundEffect: i32 = 0x46; -pub const PlayerListHeaderFooter: i32 = 0x47; -pub const CollectItem: i32 = 0x48; -pub const EntityTeleport: i32 = 0x49; -pub const EntityProperties: i32 = 0x4a; -pub const EntityEffect: i32 = 0x4b; +pub const CraftRecipeResponse: i32 = 0x2b; +pub const PlayerAbilities: i32 = 0x2c; +pub const CombatEvent: i32 = 0x2d; +pub const PlayerInfo: i32 = 0x2e; +pub const TeleportPlayer: i32 = 0x2f; +pub const EntityUsedBed: i32 = 0x30; +pub const UnlockRecipes: i32 = 0x31; +pub const EntityDestroy: i32 = 0x32; +pub const EntityRemoveEffect: i32 = 0x33; +pub const ResourcePackSend: i32 = 0x34; +pub const Respawn: i32 = 0x35; +pub const EntityHeadLook: i32 = 0x36; +pub const SelectAdvancementTab: i32 = 0x37; +pub const WorldBorder: i32 = 0x38; +pub const Camera: i32 = 0x39; +pub const SetCurrentHotbarSlot: i32 = 0x3a; +pub const ScoreboardDisplay: i32 = 0x3b; +pub const EntityMetadata: i32 = 0x3c; +pub const EntityAttach: i32 = 0x3d; +pub const EntityVelocity: i32 = 0x3e; +pub const EntityEquipment: i32 = 0x3f; +pub const SetExperience: i32 = 0x40; +pub const UpdateHealth: i32 = 0x41; +pub const ScoreboardObjective: i32 = 0x42; +pub const SetPassengers: i32 = 0x43; +pub const Teams: i32 = 0x44; +pub const UpdateScore: i32 = 0x45; +pub const SpawnPosition: i32 = 0x46; +pub const TimeUpdate: i32 = 0x47; +pub const Title: i32 = 0x48; +pub const SoundEffect: i32 = 0x49; +pub const PlayerListHeaderFooter: i32 = 0x4a; +pub const CollectItem: i32 = 0x4b; +pub const EntityTeleport: i32 = 0x4c; +pub const Advancements: i32 = 0x4d; +pub const EntityProperties: i32 = 0x4e; +pub const EntityEffect: i32 = 0x4f; pub const LoginStart: i32 = 0x00; pub const EncryptionResponse: i32 = 0x01; pub const LoginDisconnect: i32 = 0x00; diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index dc6dcca..f23e353 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -124,7 +124,7 @@ state_packets!( /// KeepAliveClientbound. If the client doesn't reply the server /// may disconnect the client. packet KeepAliveServerbound { - field id: VarInt =, + field id: i64 =, } /// PlayerPosition is used to update the player's position. packet PlayerPosition { @@ -166,6 +166,12 @@ state_packets!( field unknown: bool =, field unknown2: bool =, } + /// CraftRecipeRequest is sent when player clicks a recipe in the crafting book. + packet CraftRecipeRequest { + field window_id: u8 =, + field recipe: VarInt =, + field make_all: bool =, + } /// ClientAbilities is used to modify the players current abilities. /// Currently flying is the only one packet ClientAbilities { @@ -193,11 +199,23 @@ state_packets!( field forward: f32 =, field flags: u8 =, } + /// CraftingBookData is sent when the player interacts with the crafting book. + packet CraftingBookData { + field action: VarInt =, + field recipe_id: i32 = when(|p: &CraftingBookData| p.action.0 == 0), + field crafting_book_open: bool = when(|p: &CraftingBookData| p.action.0 == 1), + field crafting_filter: bool = when(|p: &CraftingBookData| p.action.0 == 1), + } /// ResourcePackStatus informs the server of the client's current progress /// in activating the requested resource pack packet ResourcePackStatus { field result: VarInt =, } + // TODO: Document + packet AdvancementTab { + field action: VarInt =, + field tab_id: String = when(|p: &AdvancementTab| p.action.0 == 0), + } /// HeldItemChange is sent when the player changes the currently active /// hotbar slot. packet HeldItemChange { @@ -488,7 +506,7 @@ state_packets!( /// The client should reply with the KeepAliveServerbound /// packet setting ID to the same as this one. packet KeepAliveClientbound { - field id: VarInt =, + field id: i64 =, } /// ChunkData sends or updates a single chunk on the client. If New is set /// then biome data should be sent too. @@ -522,7 +540,7 @@ state_packets!( field offset_z: f32 =, field speed: f32 =, field count: i32 =, - field data1: VarInt = when(|p: &Particle| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38), + field data1: VarInt = when(|p: &Particle| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38 || p.particle_id == 46), field data2: VarInt = when(|p: &Particle| p.particle_id == 36), } /// JoinGame is sent after completing the login process. This @@ -598,6 +616,11 @@ state_packets!( packet SignEditorOpen { field location: Position =, } + /// CraftRecipeResponse is a response to CraftRecipeRequest, notifies the UI. + packet CraftRecipeResponse { + field window_id: u8 =, + field recipe: VarInt =, + } /// PlayerAbilities is used to modify the players current abilities. Flying, /// creative, god mode etc. packet PlayerAbilities { @@ -636,6 +659,13 @@ state_packets!( field entity_id: VarInt =, field location: Position =, } + packet UnlockRecipes { + field action: VarInt =, + field crafting_book_open: bool =, + field filtering_craftable: bool =, + field recipe_ids: LenPrefixed =, + field recipe_ids2: LenPrefixed = when(|p: &UnlockRecipes| p.action.0 == 0), + } /// EntityDestroy destroys the entities with the ids in the provided slice. packet EntityDestroy { field entity_ids: LenPrefixed =, @@ -664,6 +694,11 @@ state_packets!( field entity_id: VarInt =, field head_yaw: i8 =, } + /// SelectAdvancementTab indicates the client should switch the advancement tab. + packet SelectAdvancementTab { + field has_id: bool =, + field tab_id: String = when(|p: &SelectAdvancementTab| p.has_id), + } /// WorldBorder configures the world's border. packet WorldBorder { field action: VarInt =, @@ -818,6 +853,12 @@ state_packets!( field pitch: i8 =, field on_ground: bool =, } + packet Advancements { + field reset_clear: bool =, + field mapping: LenPrefixed =, + field identifiers: LenPrefixed =, + field progress: LenPrefixed =, + } /// EntityProperties updates the properties for an entity. packet EntityProperties { field entity_id: VarInt =, @@ -1043,6 +1084,163 @@ impl Default for MapIcon { } } +#[derive(Debug, Default)] +pub struct Advancement { + pub id: String, + pub parent_id: Option, + pub display_data: Option, + pub criteria: LenPrefixed, + pub requirements: LenPrefixed>, +} + +impl Serializable for Advancement { + fn read_from(buf: &mut R) -> Result { + let id: String = Serializable::read_from(buf)?; + let parent_id = { + let has_parent: u8 = Serializable::read_from(buf)?; + if has_parent != 0 { + let parent_id: String = Serializable::read_from(buf)?; + Some(parent_id) + } else { + None + } + }; + + let has_display: u8 = Serializable::read_from(buf)?; + let display_data = { + if has_display != 0 { + let display_data: AdvancementDisplay = Serializable::read_from(buf)?; + Some(display_data) + } else { + None + } + }; + + let criteria: LenPrefixed = Serializable::read_from(buf)?; + let requirements: LenPrefixed> = Serializable::read_from(buf)?; + Ok(Advancement { + id, + parent_id, + display_data, + criteria, + requirements, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.id.write_to(buf)?; + self.parent_id.write_to(buf)?; + self.display_data.write_to(buf)?; + self.criteria.write_to(buf)?; + self.requirements.write_to(buf) + } + +} + +#[derive(Debug, Default)] +pub struct AdvancementDisplay { + pub title: String, + pub description: String, + pub icon: Option, + pub frame_type: VarInt, + pub flags: i32, + pub background_texture: Option, + pub x_coord: f32, + pub y_coord: f32, +} + +impl Serializable for AdvancementDisplay { + fn read_from(buf: &mut R) -> Result { + let title: String = Serializable::read_from(buf)?; + let description: String = Serializable::read_from(buf)?; + let icon: Option = Serializable::read_from(buf)?; + let frame_type: VarInt = Serializable::read_from(buf)?; + let flags: i32 = Serializable::read_from(buf)?; + let background_texture: Option = if flags & 1 != 0 { + Serializable::read_from(buf)? + } else { + None + }; + let x_coord: f32 = Serializable::read_from(buf)?; + let y_coord: f32 = Serializable::read_from(buf)?; + + Ok(AdvancementDisplay { + title, + description, + icon, + frame_type, + flags, + background_texture, + x_coord, + y_coord, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.title.write_to(buf)?; + self.description.write_to(buf)?; + self.icon.write_to(buf)?; + self.frame_type.write_to(buf)?; + self.flags.write_to(buf)?; + if self.flags & 1 != 0 { + self.background_texture.write_to(buf)?; + } + self.x_coord.write_to(buf)?; + self.y_coord.write_to(buf) + } + +} + +#[derive(Debug, Default)] +pub struct AdvancementProgress { + pub id: String, + pub criteria: LenPrefixed, +} + +impl Serializable for AdvancementProgress { + fn read_from(buf: &mut R) -> Result { + Ok(AdvancementProgress { + id: Serializable::read_from(buf)?, + criteria: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.id.write_to(buf)?; + self.criteria.write_to(buf) + } +} + +#[derive(Debug, Default)] +pub struct CriterionProgress { + pub id: String, + pub date_of_achieving: Option, +} + +impl Serializable for CriterionProgress { + fn read_from(buf: &mut R) -> Result { + let id = Serializable::read_from(buf)?; + let achieved: u8 = Serializable::read_from(buf)?; + let date_of_achieving: Option = if achieved != 0 { + Serializable::read_from(buf)? + } else { + None + }; + + Ok(CriterionProgress { + id, + date_of_achieving, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.id.write_to(buf)?; + self.date_of_achieving.write_to(buf) + } +} + + + #[derive(Debug, Default)] pub struct EntityProperty { pub key: String, diff --git a/src/resources.rs b/src/resources.rs index 249f9da..b5dbb69 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -30,10 +30,10 @@ use zip; use crate::types::hash::FNVHash; use crate::ui; -const RESOURCES_VERSION: &str = "1.11.2"; -const VANILLA_CLIENT_URL: &str = "https://launcher.mojang.com/mc/game/1.11.2/client/db5aa600f0b0bf508aaf579509b345c4e34087be/client.jar"; -const ASSET_VERSION: &str = "1.11"; -const ASSET_INDEX_URL: &str = "https://launchermeta.mojang.com/mc/assets/1.11/e02b8fba4390e173057895c56ecc91e3ce3bbd40/1.11.json"; +const RESOURCES_VERSION: &str = "1.12.2"; +const VANILLA_CLIENT_URL: &str = "https://launcher.mojang.com/v1/objects/0f275bc1547d01fa5f56ba34bdc87d981ee12daf/client.jar"; +const ASSET_VERSION: &str = "1.12"; +const ASSET_INDEX_URL: &str = "https://launchermeta.mojang.com/mc/assets/1.12/67e29e024e664064c1f04c728604f83c24cbc218/1.12.json"; pub trait Pack: Sync + Send { fn open(&self, name: &str) -> Option>; diff --git a/src/types/metadata.rs b/src/types/metadata.rs index 228a034..396b983 100644 --- a/src/types/metadata.rs +++ b/src/types/metadata.rs @@ -21,6 +21,7 @@ use crate::protocol::Serializable; use crate::format; use crate::item; use crate::shared::Position; +use crate::nbt; pub struct MetadataKey { index: i32, @@ -68,7 +69,7 @@ impl Serializable for Metadata { if index == 0xFF { break; } - let ty = u8::read_from(buf)?; + let ty = protocol::VarInt::read_from(buf)?.0; match ty { 0 => m.put_raw(index, i8::read_from(buf)?), 1 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0), @@ -98,6 +99,15 @@ impl Serializable for Metadata { } } 12 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0 as u16), + 13 => { + let ty = u8::read_from(buf)?; + if ty != 0 { + let name = nbt::read_string(buf)?; + let tag = nbt::Tag::read_from(buf)?; + + m.put_raw(index, nbt::NamedTag(name, tag)); + } + } _ => return Err(protocol::Error::Err("unknown metadata type".to_owned())), } } @@ -164,6 +174,11 @@ impl Serializable for Metadata { u8::write_to(&11, buf)?; protocol::VarInt(*val as i32).write_to(buf)?; } + Value::NBTTag(ref _val) => { + u8::write_to(&13, buf)?; + // TODO: write NBT tags metadata + //nbt::Tag(*val).write_to(buf)?; + } } } u8::write_to(&0xFF, buf)?; @@ -202,6 +217,7 @@ pub enum Value { Direction(protocol::VarInt), // TODO: Proper type OptionalUUID(Option), Block(u16), // TODO: Proper type + NBTTag(nbt::NamedTag), } pub trait MetaValue { @@ -365,6 +381,18 @@ impl MetaValue for u16 { } } +impl MetaValue for nbt::NamedTag { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::NBTTag(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::NBTTag(self) + } +} + #[cfg(test)] mod test { use super::*;