use std::io::{Error, ErrorKind, Read, Write, Result}; use std::net::{SocketAddr, TcpStream}; use crate::leb128::LEB128; #[derive(serde::Serialize, serde::Deserialize)] pub struct MinecraftVersion { pub name: String, pub protocol: u32, } #[derive(serde::Serialize, serde::Deserialize)] pub struct MinecraftPlayer { pub name: String, pub id: String, } #[derive(serde::Serialize, serde::Deserialize)] pub struct MinecraftPlayers { pub online: u32, pub max: u32, pub sample: Vec, } #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] enum MinecraftDescriptionExtra { Extra(MinecraftDescription), String(String), } #[derive(serde::Serialize, serde::Deserialize)] pub struct MinecraftDescription { text: String, extra: Option>, bold: Option, color: Option, } #[derive(serde::Serialize, serde::Deserialize)] pub struct MinecraftStatus { pub version: MinecraftVersion, pub players: MinecraftPlayers, description: MinecraftDescription, pub favicon: Option, #[serde(alias = "enforcesSecureChat")] enforces_secure_chat: Option, } impl MinecraftStatus { pub fn fetch(address: SocketAddr) -> Result { // println!("Connecting to {address}..."); let mut stream = TcpStream::connect(address.to_string()).unwrap(); // println!("Connected!"); let mut send_buffer: Vec = Vec::new(); // println!("Sending payload..."); send_buffer.push(0x00); LEB128::write_leb128(&mut send_buffer, 769); // 1.21.4 LEB128::write_leb128(&mut send_buffer, address.ip().to_string().len().try_into().unwrap()); send_buffer.extend_from_slice(address.ip().to_string().as_bytes()); send_buffer.extend_from_slice(&address.port().to_be_bytes()); LEB128::write_leb128(&mut send_buffer, 1); send_packet(&mut stream, &send_buffer).unwrap(); send_packet(&mut stream, &[0x00]).unwrap(); let mut data: Vec = Vec::new(); let mut len: usize = 0; let mut msg_len: usize = 0; let mut object_len: usize = 0; let mut offset: usize = 0; loop { let mut recv_buffer: [u8; 10240] = [0; 10240]; len += stream.read(&mut recv_buffer)?; if len > 0 { if msg_len == 0 { let mut val: u32; (val, offset) = LEB128::read_leb128(&recv_buffer); msg_len = val as usize; if recv_buffer[offset] != 0x00 { return Err(Error::new(ErrorKind::InvalidData, format!("Expected packet type 0x00, but got 0x{:02x?}!", recv_buffer[offset]))); } offset += 1; // skip message type bit let offset2: usize; (val, offset2) = LEB128::read_leb128(&recv_buffer[offset..]); object_len = val as usize; offset += offset2; } data.extend_from_slice(&recv_buffer); if len >= offset + object_len { break; } } } let msg = std::str::from_utf8(&data[offset..]).unwrap().trim(); let sanitised: String = msg.chars().filter(|&c| c >= '\u{20}' || c == '\n' || c == '\r' || c == '\t').collect(); // println!("{sanitised}"); let status: MinecraftStatus = serde_json::from_slice(sanitised.as_bytes()).unwrap(); Ok(status) } pub fn parse_description(&self) -> String { _description(&self.description) } pub fn enforces_secure_chat(&self) -> bool { self.enforces_secure_chat.unwrap_or(false) } } fn _description(description: &MinecraftDescription) -> String { if description.extra.is_some() { let mut extras = String::new(); for extra in description.extra.as_ref().unwrap() { match extra { MinecraftDescriptionExtra::Extra(description) => { extras += &_description(&description); } MinecraftDescriptionExtra::String(string) => { extras += &string; } } } return description.text.clone() + &extras; } description.text.clone() } fn send_packet(stream: &mut TcpStream, data: &[u8]) -> Result<()> { let mut packet: Vec = Vec::new(); LEB128::write_leb128(&mut packet, data.len() as u64); packet.extend_from_slice(&data); stream.write(&packet).unwrap(); Ok(()) }