diff --git a/protocol/src/protocol/forge.rs b/protocol/src/protocol/forge.rs new file mode 100644 index 0000000..21d47b3 --- /dev/null +++ b/protocol/src/protocol/forge.rs @@ -0,0 +1,180 @@ + +/// Implements https://wiki.vg/Minecraft_Forge_Handshake +use std::io; +use byteorder::WriteBytesExt; + +use crate::protocol::{Serializable, Error, LenPrefixed, VarInt}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Phase { + // Client handshake states (written) + Start, + WaitingServerData, + WaitingServerComplete, + PendingComplete, + + // Server handshake states (read) + WaitingCAck, + + // Both client and server handshake states (different values on the wire) + Complete, +} + +impl Serializable for Phase { + /// Read server handshake state from server + fn read_from(buf: &mut R) -> Result { + let phase: i8 = Serializable::read_from(buf)?; + Ok(match phase { + 2 => Phase::WaitingCAck, + 3 => Phase::Complete, + _ => panic!("bad FML|HS server phase: {}", phase), + }) + } + + /// Send client handshake state from client + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_u8(match self { + Phase::WaitingServerData => 2, + Phase::WaitingServerComplete => 3, + Phase::PendingComplete => 4, + Phase::Complete => 5, + _ => panic!("bad FML|HS client phase: {:?}", self), + })?; + Ok(()) + } +} + + +#[derive(Clone, Debug, Default)] +pub struct ForgeMod { + pub modid: String, + pub version: String, +} + +impl Serializable for ForgeMod { + fn read_from(buf: &mut R) -> Result { + Ok(ForgeMod { + modid: Serializable::read_from(buf)?, + version: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.modid.write_to(buf)?; + self.version.write_to(buf) + } +} + +#[derive(Debug)] +pub struct ModIdMapping { + pub name: String, + pub id: VarInt, +} + +impl Serializable for ModIdMapping { + fn read_from(buf: &mut R) -> Result { + Ok(ModIdMapping { + name: Serializable::read_from(buf)?, + id: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.name.write_to(buf)?; + self.id.write_to(buf) + } +} + +#[derive(Debug)] +pub enum FmlHs { + ServerHello { + fml_protocol_version: i8, + override_dimension: Option, + }, + ClientHello { + fml_protocol_version: i8, + }, + ModList { + mods: LenPrefixed, + }, + /* TODO: 1.8+ https://wiki.vg/Minecraft_Forge_Handshake#Differences_from_Forge_1.7.10 + RegistryData { + has_more: bool, + name: String, + ids: LenPrefixed, + substitutions: LenPrefixed, + dummies: LenPrefixed, + }, + */ + ModIdData { + mappings: LenPrefixed, + block_substitutions: LenPrefixed, + item_substitutions: LenPrefixed, + }, + HandshakeAck { + phase: Phase, + }, + HandshakeReset, +} + +impl Serializable for FmlHs { + fn read_from(buf: &mut R) -> Result { + let discriminator: u8 = Serializable::read_from(buf)?; + + match discriminator { + 0 => { + let fml_protocol_version: i8 = Serializable::read_from(buf)?; + let override_dimension = if fml_protocol_version > 1 { + let dimension: i32 = Serializable::read_from(buf)?; + Some(dimension) + } else { + None + }; + + println!("FML|HS ServerHello: fml_protocol_version={}, override_dimension={:?}", fml_protocol_version, override_dimension); + + Ok(FmlHs::ServerHello { + fml_protocol_version, + override_dimension, + }) + }, + 1 => panic!("Received unexpected FML|HS ClientHello from server"), + 2 => { + Ok(FmlHs::ModList { + mods: Serializable::read_from(buf)?, + }) + }, + 3 => { + Ok(FmlHs::ModIdData { + mappings: Serializable::read_from(buf)?, + block_substitutions: Serializable::read_from(buf)?, + item_substitutions: Serializable::read_from(buf)?, + }) + }, + 255 => { + Ok(FmlHs::HandshakeAck { + phase: Serializable::read_from(buf)?, + }) + }, + _ => panic!("Unhandled FML|HS packet: discriminator={}", discriminator), + } + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + match self { + FmlHs::ClientHello { fml_protocol_version } => { + buf.write_u8(1)?; + fml_protocol_version.write_to(buf) + }, + FmlHs::ModList { mods } => { + buf.write_u8(2)?; + mods.write_to(buf) + }, + FmlHs::HandshakeAck { phase } => { + buf.write_u8(255)?; + phase.write_to(buf) + }, + _ => unimplemented!() + } + } +} diff --git a/protocol/src/protocol/mod.rs b/protocol/src/protocol/mod.rs index be57ae3..97423ca 100644 --- a/protocol/src/protocol/mod.rs +++ b/protocol/src/protocol/mod.rs @@ -23,6 +23,7 @@ use serde_json; use reqwest; pub mod mojang; +pub mod forge; use crate::nbt; use crate::format; @@ -660,6 +661,65 @@ impl fmt::Debug for VarInt { } } +/// `VarShort` have a variable size (2 or 3 bytes) and are backwards-compatible +/// with vanilla shorts, used for Forge custom payloads +#[derive(Clone, Copy)] +pub struct VarShort(pub i32); + +impl Lengthable for VarShort { + fn into(self) -> usize { + self.0 as usize + } + + fn from(u: usize) -> VarShort { + VarShort(u as i32) + } +} + +impl Serializable for VarShort { + fn read_from(buf: &mut R) -> Result { + let low = buf.read_u16::()? as u32; + let val = if (low & 0x8000) != 0 { + let high = buf.read_u8()? as u32; + + (high << 15) | (low & 0x7fff) + } else { + low + }; + + Result::Ok(VarShort(val as i32)) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + assert!(self.0 >= 0 && self.0 <= 0x7fffff, "VarShort invalid value: {}", self.0); + let mut low = self.0 & 0x7fff; + let high = (self.0 & 0x7f8000) >> 15; + if high != 0 { + low |= 0x8000; + } + + buf.write_u16::(low as u16)?; + + if high != 0 { + buf.write_u8(high as u8)?; + } + + Ok(()) + } +} + +impl default::Default for VarShort { + fn default() -> VarShort { + VarShort(0) + } +} + +impl fmt::Debug for VarShort { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + /// `VarLong` have a variable size (between 1 and 10 bytes) when encoded based /// on the size of the number #[derive(Clone, Copy)] @@ -1005,6 +1065,29 @@ impl Conn { let version = val.get("version").ok_or(invalid_status())?; let players = val.get("players").ok_or(invalid_status())?; + // For modded servers, get the list of Forge mods installed + let mut forge_mods: std::vec::Vec = vec![]; + if let Some(modinfo) = val.get("modinfo") { + if let Some(modinfo_type) = modinfo.get("type") { + if modinfo_type == "FML" { + if let Some(modlist) = modinfo.get("modList") { + if let Value::Array(items) = modlist { + for item in items { + if let Value::Object(obj) = item { + let modid = obj.get("modid").unwrap().as_str().unwrap().to_string(); + let version = obj.get("version").unwrap().as_str().unwrap().to_string(); + + forge_mods.push(crate::protocol::forge::ForgeMod { modid, version }); + } + } + } + } + } else { + panic!("Unrecognized modinfo type in server ping response: {} in {}", modinfo_type, modinfo); + } + } + } + Ok((Status { version: StatusVersion { name: version.get("name").and_then(Value::as_str).ok_or(invalid_status())? @@ -1025,6 +1108,7 @@ impl Conn { description: format::Component::from_value(val.get("description") .ok_or(invalid_status())?), favicon: val.get("favicon").and_then(Value::as_str).map(|v| v.to_owned()), + forge_mods, }, ping)) } @@ -1036,6 +1120,7 @@ pub struct Status { pub players: StatusPlayers, pub description: format::Component, pub favicon: Option, + pub forge_mods: Vec, } #[derive(Debug)] diff --git a/protocol/src/protocol/packet.rs b/protocol/src/protocol/packet.rs index 6b75acb..6e78020 100644 --- a/protocol/src/protocol/packet.rs +++ b/protocol/src/protocol/packet.rs @@ -161,7 +161,7 @@ state_packets!( } packet PluginMessageServerbound_i16 { field channel: String =, - field data: LenPrefixedBytes =, + field data: LenPrefixedBytes =, } packet EditBook { field new_book: Option =, @@ -875,7 +875,7 @@ state_packets!( } packet PluginMessageClientbound_i16 { field channel: String =, - field data: LenPrefixedBytes =, + field data: LenPrefixedBytes =, } /// Plays a sound by name on the client packet NamedSoundEffect { @@ -1747,6 +1747,9 @@ state_packets!( field id: VarInt =, field trades: LenPrefixed =, } + packet CoFHLib_SendUUID { + field player_uuid: UUID =, + } } } login Login { diff --git a/protocol/src/protocol/versions/v1_7_10.rs b/protocol/src/protocol/versions/v1_7_10.rs index 5ca41d9..1bc92ff 100644 --- a/protocol/src/protocol/versions/v1_7_10.rs +++ b/protocol/src/protocol/versions/v1_7_10.rs @@ -99,6 +99,7 @@ protocol_packet_ids!( 0x3e => Teams_NoVisColor 0x3f => PluginMessageClientbound_i16 0x40 => Disconnect + -0x1a => CoFHLib_SendUUID } } login Login {