diff --git a/src/format.rs b/src/format.rs index a762cc0..7928967 100644 --- a/src/format.rs +++ b/src/format.rs @@ -4,251 +4,251 @@ use std::fmt; #[derive(Debug)] pub enum Component { - Text(TextComponent), - None + Text(TextComponent), + None } impl Component { - pub fn from_value(v: &serde_json::Value) -> Self { - let modifier = Modifier::from_value(v); - if let Some(val) = v.as_string() { - Component::Text(TextComponent{text: val.to_owned(), modifier: modifier}) - } else if v.find("text").is_some(){ - Component::Text(TextComponent::from_value(v, modifier)) - } else { - Component::None - } - } + pub fn from_value(v: &serde_json::Value) -> Self { + let modifier = Modifier::from_value(v); + if let Some(val) = v.as_string() { + Component::Text(TextComponent{text: val.to_owned(), modifier: modifier}) + } else if v.find("text").is_some(){ + Component::Text(TextComponent::from_value(v, modifier)) + } else { + Component::None + } + } - pub fn to_value(&self) -> serde_json::Value { - unimplemented!() - } + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } } impl fmt::Display for Component { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Component::Text(ref txt) => write!(f, "{}", txt), - Component::None => Result::Ok(()), - } - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Component::Text(ref txt) => write!(f, "{}", txt), + Component::None => Result::Ok(()), + } + } } impl Default for Component { - fn default() -> Self { - Component::None - } + fn default() -> Self { + Component::None + } } #[derive(Debug)] pub struct Modifier { - pub extra: Option>, - pub bold: Option, - pub italic: Option, - pub underlined: Option, - pub strikethrough: Option, - pub obfuscated: Option, - pub color: Option, + pub extra: Option>, + pub bold: Option, + pub italic: Option, + pub underlined: Option, + pub strikethrough: Option, + pub obfuscated: Option, + pub color: Option, - // click_event - // hover_event - // insertion + // click_event + // hover_event + // insertion } impl Modifier { - pub fn from_value(v: &serde_json::Value) -> Self { - let mut m = Modifier { - bold: v.find("bold").map_or(Option::None, |v| v.as_boolean()), - italic: v.find("italic").map_or(Option::None, |v| v.as_boolean()), - underlined: v.find("underlined").map_or(Option::None, |v| v.as_boolean()), - strikethrough: v.find("strikethrough").map_or(Option::None, |v| v.as_boolean()), - obfuscated: v.find("obfuscated").map_or(Option::None, |v| v.as_boolean()), - color: v.find("color").map_or(Option::None, |v| v.as_string()).map(|v| Color::from_string(&v.to_owned())), - extra: Option::None, - }; - if let Some(extra) = v.find("extra") { - if let Some(data) = extra.as_array() { - let mut ex = Vec::new(); - for e in data { - ex.push(Component::from_value(e)); - } - m.extra = Some(ex); - } - } - m - } + pub fn from_value(v: &serde_json::Value) -> Self { + let mut m = Modifier { + bold: v.find("bold").map_or(Option::None, |v| v.as_boolean()), + italic: v.find("italic").map_or(Option::None, |v| v.as_boolean()), + underlined: v.find("underlined").map_or(Option::None, |v| v.as_boolean()), + strikethrough: v.find("strikethrough").map_or(Option::None, |v| v.as_boolean()), + obfuscated: v.find("obfuscated").map_or(Option::None, |v| v.as_boolean()), + color: v.find("color").map_or(Option::None, |v| v.as_string()).map(|v| Color::from_string(&v.to_owned())), + extra: Option::None, + }; + if let Some(extra) = v.find("extra") { + if let Some(data) = extra.as_array() { + let mut ex = Vec::new(); + for e in data { + ex.push(Component::from_value(e)); + } + m.extra = Some(ex); + } + } + m + } - pub fn to_value(&self) -> serde_json::Value { - unimplemented!() - } + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } } #[derive(Debug)] pub struct TextComponent { - pub text: String, - pub modifier: Modifier, + pub text: String, + pub modifier: Modifier, } impl TextComponent { - pub fn from_value(v: &serde_json::Value, modifier: Modifier) -> Self { - TextComponent { - text: v.find("text").unwrap().as_string().unwrap_or("").to_owned(), - modifier: modifier, - } - } + pub fn from_value(v: &serde_json::Value, modifier: Modifier) -> Self { + TextComponent { + text: v.find("text").unwrap().as_string().unwrap_or("").to_owned(), + modifier: modifier, + } + } - pub fn to_value(&self) -> serde_json::Value { - unimplemented!() - } + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } } impl fmt::Display for TextComponent { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(write!(f, "{}", self.text)); - if let Some(ref extra) = self.modifier.extra { - for c in extra { - try!(write!(f, "{}", c)); - } - } - Result::Ok(()) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "{}", self.text)); + if let Some(ref extra) = self.modifier.extra { + for c in extra { + try!(write!(f, "{}", c)); + } + } + Result::Ok(()) + } } #[derive(Debug)] pub enum Color { - Black, - DarkBlue, - DarkGreen, - DarkAqua, - DarkRed, - DarkPurple, - Gold, - Gray, - DarkGray, - Blue, - Green, - Aqua, - Red, - LightPurple, - Yellow, - White, - RGB(u8, u8, u8) + Black, + DarkBlue, + DarkGreen, + DarkAqua, + DarkRed, + DarkPurple, + Gold, + Gray, + DarkGray, + Blue, + Green, + Aqua, + Red, + LightPurple, + Yellow, + White, + RGB(u8, u8, u8) } impl fmt::Display for Color { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.to_string()) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } } impl Color { - fn from_string(val: &String) -> Self { - match val.as_ref() { - "black" => Color::Black, - "dark_blue" => Color::DarkBlue, - "dark_green" => Color::DarkGreen, - "dark_aqua" => Color::DarkAqua, - "dark_red" => Color::DarkRed, - "dark_purple" => Color::DarkPurple, - "gold" => Color::Gold, - "gray" => Color::Gray, - "dark_gray" => Color::DarkGray, - "blue" => Color::Blue, - "green" => Color::Green, - "aqua" => Color::Aqua, - "red" => Color::Red, - "light_purple" => Color::LightPurple, - "yellow" => Color::Yellow, - "white" => Color::White, - val if val.len() == 7 && val.as_bytes()[0] == b'#' => { - let r = match u8::from_str_radix(&val[1..3], 16) { - Ok(r) => r, - Err(_) => return Color::White, - }; - let g = match u8::from_str_radix(&val[3..5], 16) { - Ok(g) => g, - Err(_) => return Color::White, - }; - let b = match u8::from_str_radix(&val[5..7], 16) { - Ok(b) => b, - Err(_) => return Color::White, - }; - Color::RGB( - r, - g, - b - ) - } - _ => Color::White, - } - } + fn from_string(val: &String) -> Self { + match val.as_ref() { + "black" => Color::Black, + "dark_blue" => Color::DarkBlue, + "dark_green" => Color::DarkGreen, + "dark_aqua" => Color::DarkAqua, + "dark_red" => Color::DarkRed, + "dark_purple" => Color::DarkPurple, + "gold" => Color::Gold, + "gray" => Color::Gray, + "dark_gray" => Color::DarkGray, + "blue" => Color::Blue, + "green" => Color::Green, + "aqua" => Color::Aqua, + "red" => Color::Red, + "light_purple" => Color::LightPurple, + "yellow" => Color::Yellow, + "white" => Color::White, + val if val.len() == 7 && val.as_bytes()[0] == b'#' => { + let r = match u8::from_str_radix(&val[1..3], 16) { + Ok(r) => r, + Err(_) => return Color::White, + }; + let g = match u8::from_str_radix(&val[3..5], 16) { + Ok(g) => g, + Err(_) => return Color::White, + }; + let b = match u8::from_str_radix(&val[5..7], 16) { + Ok(b) => b, + Err(_) => return Color::White, + }; + Color::RGB( + r, + g, + b + ) + } + _ => Color::White, + } + } - fn to_string(&self) -> String { - match *self { - Color::Black => "black".to_owned(), - Color::DarkBlue => "dark_blue".to_owned(), - Color::DarkGreen => "dark_green".to_owned(), - Color::DarkAqua => "dark_aqua".to_owned(), - Color::DarkRed => "dark_red".to_owned(), - Color::DarkPurple => "dark_purple".to_owned(), - Color::Gold => "gold".to_owned(), - Color::Gray => "gray".to_owned(), - Color::DarkGray => "dark_gray".to_owned(), - Color::Blue => "blue".to_owned(), - Color::Green => "green".to_owned(), - Color::Aqua => "aqua".to_owned(), - Color::Red => "red".to_owned(), - Color::LightPurple => "light_purple".to_owned(), - Color::Yellow => "yellow".to_owned(), - Color::White => "white".to_owned(), - Color::RGB(r, g, b) => format!("#{:02X}{:02X}{:02X}", r, g, b), - } - } + fn to_string(&self) -> String { + match *self { + Color::Black => "black".to_owned(), + Color::DarkBlue => "dark_blue".to_owned(), + Color::DarkGreen => "dark_green".to_owned(), + Color::DarkAqua => "dark_aqua".to_owned(), + Color::DarkRed => "dark_red".to_owned(), + Color::DarkPurple => "dark_purple".to_owned(), + Color::Gold => "gold".to_owned(), + Color::Gray => "gray".to_owned(), + Color::DarkGray => "dark_gray".to_owned(), + Color::Blue => "blue".to_owned(), + Color::Green => "green".to_owned(), + Color::Aqua => "aqua".to_owned(), + Color::Red => "red".to_owned(), + Color::LightPurple => "light_purple".to_owned(), + Color::Yellow => "yellow".to_owned(), + Color::White => "white".to_owned(), + Color::RGB(r, g, b) => format!("#{:02X}{:02X}{:02X}", r, g, b), + } + } - #[allow(dead_code)] - fn to_rgb(&self) -> (u8, u8, u8) { - match *self { - Color::Black =>(0, 0, 0), - Color::DarkBlue =>(0, 0, 170), - Color::DarkGreen =>(0, 170, 0), - Color::DarkAqua =>(0, 170, 170), - Color::DarkRed =>(170, 0, 0), - Color::DarkPurple =>(170, 0, 170), - Color::Gold =>(255, 170, 0), - Color::Gray =>(170, 170, 170), - Color::DarkGray =>(85, 85, 85), - Color::Blue =>(85, 85, 255), - Color::Green =>(85, 255, 85), - Color::Aqua =>(85, 255, 255), - Color::Red =>(255, 85, 85), - Color::LightPurple =>(255, 85, 255), - Color::Yellow =>(255, 255, 85), - Color::White =>(255, 255, 255), - Color::RGB(r, g, b) => (r, g, b), - } - } + #[allow(dead_code)] + fn to_rgb(&self) -> (u8, u8, u8) { + match *self { + Color::Black =>(0, 0, 0), + Color::DarkBlue =>(0, 0, 170), + Color::DarkGreen =>(0, 170, 0), + Color::DarkAqua =>(0, 170, 170), + Color::DarkRed =>(170, 0, 0), + Color::DarkPurple =>(170, 0, 170), + Color::Gold =>(255, 170, 0), + Color::Gray =>(170, 170, 170), + Color::DarkGray =>(85, 85, 85), + Color::Blue =>(85, 85, 255), + Color::Green =>(85, 255, 85), + Color::Aqua =>(85, 255, 255), + Color::Red =>(255, 85, 85), + Color::LightPurple =>(255, 85, 255), + Color::Yellow =>(255, 255, 85), + Color::White =>(255, 255, 255), + Color::RGB(r, g, b) => (r, g, b), + } + } } #[test] fn test_color_from() { - let test = Color::from_string(&"#FF0000".to_owned()); - match test { - Color::RGB(r, g, b) => assert!(r == 255 && g == 0 && b == 0), - _ => panic!("Wrong type"), - } - let test = Color::from_string(&"#123456".to_owned()); - match test { - Color::RGB(r, g, b) => assert!(r == 0x12 && g == 0x34 && b == 0x56), - _ => panic!("Wrong type"), - } - let test = Color::from_string(&"red".to_owned()); - match test { - Color::Red => {}, - _ => panic!("Wrong type"), - } - let test = Color::from_string(&"dark_blue".to_owned()); - match test { - Color::DarkBlue => {}, - _ => panic!("Wrong type"), - } + let test = Color::from_string(&"#FF0000".to_owned()); + match test { + Color::RGB(r, g, b) => assert!(r == 255 && g == 0 && b == 0), + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"#123456".to_owned()); + match test { + Color::RGB(r, g, b) => assert!(r == 0x12 && g == 0x34 && b == 0x56), + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"red".to_owned()); + match test { + Color::Red => {}, + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"dark_blue".to_owned()); + match test { + Color::DarkBlue => {}, + _ => panic!("Wrong type"), + } } diff --git a/src/protocol/mojang.rs b/src/protocol/mojang.rs index 2941ad8..e126793 100644 --- a/src/protocol/mojang.rs +++ b/src/protocol/mojang.rs @@ -3,9 +3,9 @@ extern crate serde_json; extern crate hyper; pub struct Profile { - pub username: String, - pub id: String, - pub access_token: String + pub username: String, + pub id: String, + pub access_token: String } const JOIN_URL: &'static str = "https://sessionserver.mojang.com/session/minecraft/join"; @@ -54,12 +54,12 @@ impl Profile { } fn twos_compliment(data: &mut Vec) { - let mut carry = true; - for i in (0 .. data.len()).rev() { - data[i] = !data[i]; - if carry { - carry = data[i] == 0xFF; - data[i] += 1; - } - } + let mut carry = true; + for i in (0 .. data.len()).rev() { + data[i] = !data[i]; + if carry { + carry = data[i] == 0xFF; + data[i] += 1; + } + } } diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 02d9e68..fcc07e1 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -1,186 +1,186 @@ state_packets!( - handshake Handshaking { - serverbound Serverbound { - // Handshake is the first packet sent in the protocol. - // Its used for deciding if the request is a client - // is requesting status information about the server - // (MOTD, players etc) or trying to login to the server. - // - // The host and port fields are not used by the vanilla - // server but are there for virtual server hosting to - // be able to redirect a client to a target server with - // a single address + port. - // - // Some modified servers/proxies use the handshake field - // differently, packing information into the field other - // than the hostname due to the protocol not providing - // any system for custom information to be transfered - // by the client to the server until after login. - Handshake => 0x00 { - // The protocol version of the connecting client - protocol_version: VarInt =, - // The hostname the client connected to - host: String =, - // The port the client connected to - port: u16 =, - // The next protocol state the client wants - next: VarInt =, - } - } - clientbound Clientbound { - } - } - play Play { - serverbound Serverbound { - // TabComplete is sent by the client when the client presses tab in - // the chat box. - TabComplete => 0x00 { - text: String =, - has_target: bool =, - target: Option = when(|p: &TabComplete| p.has_target == true), - } - // ChatMessage is sent by the client when it sends a chat message or - // executes a command (prefixed by '/'). - ChatMessage => 0x01 { - message: String =, - } - // ClientStatus is sent to update the client's status - ClientStatus => 0x02 { - action_id: VarInt =, - } - // ClientSettings is sent by the client to update its current settings. - ClientSettings => 0x03 { - locale: String =, - view_distance: u8 =, - chat_mode: u8 =, - chat_colors: bool =, - displayed_skin_parts: u8 =, - main_hand: VarInt =, - } - // ConfirmTransactionServerbound is a reply to ConfirmTransaction. - ConfirmTransactionServerbound => 0x04 { - id: u8 =, - action_number: i16 =, - accepted: bool =, - } - // EnchantItem is sent when the client enchants an item. - EnchantItem => 0x05 { - id: u8 =, - enchantment: u8 =, - } - // ClickWindow is sent when the client clicks in a window. - ClickWindow => 0x06 { - id: u8 =, - slot: i16 =, - button: u8 =, - action_number: u16 =, - mode: u8 =, - clicked_item: ()=, // TODO - } - } - clientbound Clientbound { - ServerMessage => 15 { - message: format::Component =, - position: u8 =, - } - } - } - login Login { - serverbound Serverbound { - // LoginStart is sent immeditately after switching into the login - // state. The passed username is used by the server to authenticate - // the player in online mode. - LoginStart => 0 { - username: String =, - } - // EncryptionResponse is sent as a reply to EncryptionRequest. All - // packets following this one must be encrypted with AES/CFB8 - // encryption. - EncryptionResponse => 1 { - // The key for the AES/CFB8 cipher encrypted with the - // public key - shared_secret: LenPrefixed =, - // The verify token from the request encrypted with the - // public key - verify_token: LenPrefixed =, - } - } - clientbound Clientbound { - // LoginDisconnect is sent by the server if there was any issues - // authenticating the player during login or the general server - // issues (e.g. too many players). - LoginDisconnect => 0 { - reason: format::Component =, - } - // EncryptionRequest is sent by the server if the server is in - // online mode. If it is not sent then its assumed the server is - // in offline mode. - EncryptionRequest => 1 { - // Generally empty, left in from legacy auth - // but is still used by the client if provided - server_id: String =, - // A RSA Public key serialized in x.509 PRIX format - public_key: LenPrefixed =, - // Token used by the server to verify encryption is working - // correctly - verify_token: LenPrefixed =, - } - // LoginSuccess is sent by the server if the player successfully - // authenicates with the session servers (online mode) or straight - // after LoginStart (offline mode). - LoginSuccess => 2 { - // String encoding of a uuid (with hyphens) - uuid: String =, - username: String =, - } - // SetInitialCompression sets the compression threshold during the - // login state. - SetInitialCompression => 3 { - // Threshold where a packet should be sent compressed - threshold: VarInt =, - } - } - } - status Status { - serverbound Serverbound { + handshake Handshaking { + serverbound Serverbound { + // Handshake is the first packet sent in the protocol. + // Its used for deciding if the request is a client + // is requesting status information about the server + // (MOTD, players etc) or trying to login to the server. + // + // The host and port fields are not used by the vanilla + // server but are there for virtual server hosting to + // be able to redirect a client to a target server with + // a single address + port. + // + // Some modified servers/proxies use the handshake field + // differently, packing information into the field other + // than the hostname due to the protocol not providing + // any system for custom information to be transfered + // by the client to the server until after login. + Handshake => 0x00 { + // The protocol version of the connecting client + protocol_version: VarInt =, + // The hostname the client connected to + host: String =, + // The port the client connected to + port: u16 =, + // The next protocol state the client wants + next: VarInt =, + } + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + // TabComplete is sent by the client when the client presses tab in + // the chat box. + TabComplete => 0x00 { + text: String =, + has_target: bool =, + target: Option = when(|p: &TabComplete| p.has_target == true), + } + // ChatMessage is sent by the client when it sends a chat message or + // executes a command (prefixed by '/'). + ChatMessage => 0x01 { + message: String =, + } + // ClientStatus is sent to update the client's status + ClientStatus => 0x02 { + action_id: VarInt =, + } + // ClientSettings is sent by the client to update its current settings. + ClientSettings => 0x03 { + locale: String =, + view_distance: u8 =, + chat_mode: u8 =, + chat_colors: bool =, + displayed_skin_parts: u8 =, + main_hand: VarInt =, + } + // ConfirmTransactionServerbound is a reply to ConfirmTransaction. + ConfirmTransactionServerbound => 0x04 { + id: u8 =, + action_number: i16 =, + accepted: bool =, + } + // EnchantItem is sent when the client enchants an item. + EnchantItem => 0x05 { + id: u8 =, + enchantment: u8 =, + } + // ClickWindow is sent when the client clicks in a window. + ClickWindow => 0x06 { + id: u8 =, + slot: i16 =, + button: u8 =, + action_number: u16 =, + mode: u8 =, + clicked_item: ()=, // TODO + } + } + clientbound Clientbound { + ServerMessage => 15 { + message: format::Component =, + position: u8 =, + } + } + } + login Login { + serverbound Serverbound { + // LoginStart is sent immeditately after switching into the login + // state. The passed username is used by the server to authenticate + // the player in online mode. + LoginStart => 0 { + username: String =, + } + // EncryptionResponse is sent as a reply to EncryptionRequest. All + // packets following this one must be encrypted with AES/CFB8 + // encryption. + EncryptionResponse => 1 { + // The key for the AES/CFB8 cipher encrypted with the + // public key + shared_secret: LenPrefixed =, + // The verify token from the request encrypted with the + // public key + verify_token: LenPrefixed =, + } + } + clientbound Clientbound { + // LoginDisconnect is sent by the server if there was any issues + // authenticating the player during login or the general server + // issues (e.g. too many players). + LoginDisconnect => 0 { + reason: format::Component =, + } + // EncryptionRequest is sent by the server if the server is in + // online mode. If it is not sent then its assumed the server is + // in offline mode. + EncryptionRequest => 1 { + // Generally empty, left in from legacy auth + // but is still used by the client if provided + server_id: String =, + // A RSA Public key serialized in x.509 PRIX format + public_key: LenPrefixed =, + // Token used by the server to verify encryption is working + // correctly + verify_token: LenPrefixed =, + } + // LoginSuccess is sent by the server if the player successfully + // authenicates with the session servers (online mode) or straight + // after LoginStart (offline mode). + LoginSuccess => 2 { + // String encoding of a uuid (with hyphens) + uuid: String =, + username: String =, + } + // SetInitialCompression sets the compression threshold during the + // login state. + SetInitialCompression => 3 { + // Threshold where a packet should be sent compressed + threshold: VarInt =, + } + } + } + status Status { + serverbound Serverbound { // StatusRequest is sent by the client instantly after // switching to the Status protocol state and is used // to signal the server to send a StatusResponse to the // client - StatusRequest => 0 { - empty: () =, - } + StatusRequest => 0 { + empty: () =, + } // StatusPing is sent by the client after recieving a // StatusResponse. The client uses the time from sending // the ping until the time of recieving a pong to measure // the latency between the client and the server. - StatusPing => 1 { - ping: i64 =, - } - } - clientbound Clientbound { - // StatusResponse is sent as a reply to a StatusRequest. - // The Status should contain a json encoded structure with - // version information, a player sample, a description/MOTD - // and optionally a favicon. - // - // The structure is as follows - // { - // "version": { - // "name": "1.8.3", - // "protocol": 47, - // }, - // "players": { - // "max": 20, - // "online": 1, - // "sample": [ - // {"name": "Thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"} - // ] - // }, - // "description": "Hello world", - // "favicon": "data:image/png;base64," - // } + StatusPing => 1 { + ping: i64 =, + } + } + clientbound Clientbound { + // StatusResponse is sent as a reply to a StatusRequest. + // The Status should contain a json encoded structure with + // version information, a player sample, a description/MOTD + // and optionally a favicon. + // + // The structure is as follows + // { + // "version": { + // "name": "1.8.3", + // "protocol": 47, + // }, + // "players": { + // "max": 20, + // "online": 1, + // "sample": [ + // {"name": "Thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"} + // ] + // }, + // "description": "Hello world", + // "favicon": "data:image/png;base64," + // } StatusResponse => 0 { status: String =, } @@ -190,6 +190,6 @@ state_packets!( StatusPong => 1 { ping: i64 =, } - } - } + } + } );