From e9a3770c1e409a30b1465571d8d982102dcdea92 Mon Sep 17 00:00:00 2001 From: Kyler <59854022+KylerOlsen@users.noreply.github.com> Date: Wed, 20 Dec 2023 23:41:01 -0700 Subject: [PATCH] Refactored status.rs to use traits --- src/main.rs | 5 +- src/status.rs | 409 +++++++++++++++++++------------------------ src/status_handle.rs | 215 +++++++++++++++++++++++ 3 files changed, 395 insertions(+), 234 deletions(-) create mode 100644 src/status_handle.rs diff --git a/src/main.rs b/src/main.rs index eda0438..777f41c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod mc_types; mod handshake; mod status; mod login; +mod status_handle; use mc_types::Packet; @@ -50,7 +51,7 @@ async fn handle_client(client_socket: TcpStream) { let packet_id: u8 = buffer[0]; if packet_id == 0xFE { - status::respond_legacy_status(&mut client_writer) + status_handle::respond_legacy_status(&mut client_writer) .await.expect("Error handling legacy status request"); return; } else { @@ -60,7 +61,7 @@ async fn handle_client(client_socket: TcpStream) { println!("Next state: {}", handshake_packet.next_state); if handshake_packet.next_state == 1 { println!("Receiving Status Request"); - status::respond_status( + status_handle::respond_status( &mut client_reader, &mut client_writer, &mut server_reader, diff --git a/src/status.rs b/src/status.rs index 52f1e75..1f9dbdd 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,249 +1,194 @@ // Yeahbut December 2023 -use std::fs::{self, File}; -use std::io::Read; +pub mod clientbound { -use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; -use tokio::io::AsyncWriteExt; -use serde::{Serialize, Deserialize}; -use serde_json::Value; -use base64::{Engine as _, engine::general_purpose}; -use rand::Rng; + use tokio::net::tcp::OwnedReadHalf; + use serde::{Serialize, Deserialize}; -use crate::mc_types::{self, Result, Packet}; -use crate::handshake; + use crate::mc_types::{self, Result, Packet, PacketError}; -#[derive(Serialize, Deserialize)] -pub struct StatusVersion { - pub name: String, - pub protocol: i32, -} - -#[derive(Serialize, Deserialize)] -pub struct StatusPlayerInfo { - pub name: String, - pub id: String, -} - -#[derive(Serialize, Deserialize)] -pub struct StatusPlayers { - pub max: i32, - pub online: i32, - #[serde(skip_serializing_if = "Option::is_none")] - pub sample: Option> -} - -#[derive(Serialize, Deserialize)] -pub struct StatusResponseData { - pub version: StatusVersion, - pub description: mc_types::Chat, - pub players: StatusPlayers, - #[serde(skip_serializing_if = "Option::is_none")] - pub favicon: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub enforcesSecureChat: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub previewsChat: Option, -} - -async fn online_players( - server_reader: &mut OwnedReadHalf, - server_writer: &mut OwnedWriteHalf, -) -> Result { - Ok(get_upstream_status(server_reader, server_writer).await?.players) -} - -fn motd() -> String { - let default = "A Minecraft Server Proxy".to_string(); - let file_path = "./motd.json"; - - let data = match fs::read_to_string(file_path) { - Ok(data) => data, - Err(_) => return default, - }; - - let motd_data: Value = match serde_json::from_str(&data) { - Ok(value) => value, - Err(_) => return default, - }; - - let length1 = motd_data["line1"].as_array().map_or(0, |v| v.len()); - let length2 = motd_data["line2"].as_array().map_or(0, |v| v.len()); - - if length1 == 0 || length2 == 0 { - return default; + #[derive(Serialize, Deserialize)] + pub struct StatusVersion { + pub name: String, + pub protocol: i32, } - let mut rng = rand::thread_rng(); - let rand1 = rng.gen_range(0..length1) as usize; - let rand2 = rng.gen_range(0..length2) as usize; + #[derive(Serialize, Deserialize)] + pub struct StatusPlayerInfo { + pub name: String, + pub id: String, + } - let line1: &str = match motd_data["line1"][rand1].as_str() { - Some(s) => s, - None => return default, - }; + #[derive(Serialize, Deserialize)] + pub struct StatusPlayers { + pub max: i32, + pub online: i32, + #[serde(skip_serializing_if = "Option::is_none")] + pub sample: Option> + } - // TODO: Birthdays, Holidays, and Announcements + #[derive(Serialize, Deserialize)] + pub struct StatusResponseData { + pub version: StatusVersion, + pub description: mc_types::Chat, + pub players: StatusPlayers, + #[serde(skip_serializing_if = "Option::is_none")] + pub favicon: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enforcesSecureChat: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub previewsChat: Option, + } - let line2: &str = match motd_data["line2"][rand2].as_str() { - Some(s) => s, - None => return default, - }; + pub enum StatusPackets { + Status(Status), + Ping(Ping), + } - let line: String = format!("{}\n{}", line1, line2); - line -} - -fn favicon() -> Option { - let file_path = "./main_icon.png"; - - let mut file = match File::open(file_path) { - Ok(file) => file, - Err(_) => return None, - }; - - let mut buffer = Vec::new(); - if let Err(_) = file.read_to_end(&mut buffer) { - return None - }; - - let base64_string = general_purpose::STANDARD_NO_PAD.encode(buffer); - let full_string: String = - format!("data:image/png;base64,{}", base64_string); - - Some(full_string) -} - -pub async fn respond_status( - client_reader: &mut OwnedReadHalf, - client_writer: &mut OwnedWriteHalf, - server_reader: &mut Option, - server_writer: &mut Option, -)-> Result<()> { - loop { - println!("Status Handling"); - let mut data = mc_types::read_data(client_reader).await?; - let packet_id = mc_types::get_var_int(&mut data)?; - - println!("Status Packet ID: {}", packet_id); - - if packet_id == 0x00 { - println!("Handling Status"); - let favicon = favicon(); - - let online_players = match server_reader { - Some(server_reader) => match server_writer { - Some(server_writer) => match online_players( - server_reader, - server_writer, - ).await { - Ok(value) => Some(value), - Err(_) => None, - }, - None => None, - }, - None => None, - }; - - let status_response = - match online_players { - Some(online_players) => StatusResponseData { - version: StatusVersion { - name: mc_types::VERSION_NAME.to_string(), - protocol: mc_types::VERSION_PROTOCOL, - }, - description: mc_types::Chat { - text: motd(), - }, - players: StatusPlayers { - max: -13, - online: online_players.online, - sample: online_players.sample, - }, - favicon: favicon, - enforcesSecureChat: Some(false), - previewsChat: Some(false), - }, - None => StatusResponseData { - version: StatusVersion { - name: "Old".to_string(), - protocol: 0, - }, - description: mc_types::Chat { - text: "Server Error (Server may be starting)" - .to_string() + "\nPurple Cello Server", - }, - players: StatusPlayers { - max: 0, - online: 0, - sample: None, - }, - favicon: favicon, - enforcesSecureChat: Some(false), - previewsChat: Some(false), - }, - }; - - let json = serde_json::to_string(&status_response)?; - - let mut out_data: Vec = vec![0]; - out_data.append(&mut mc_types::convert_string(&json)); - mc_types::write_data(client_writer, &mut out_data).await?; - } else if packet_id == 0x01 { - println!("Handling Ping"); - let mut out_data: Vec = vec![1]; - out_data.append(&mut data); - mc_types::write_data(client_writer, &mut out_data).await?; - break; - } else { - break; + impl StatusPackets { + pub async fn read(stream: &mut OwnedReadHalf) -> Result { + let mut data = mc_types::read_data(stream).await?; + let packet_id = mc_types::get_var_int(&mut data)?; + if packet_id == Status::packet_id() { + return Ok(Self::Status(Status::get(&mut data)?)) + } else if packet_id == Ping::packet_id() { + return Ok(Self::Ping(Ping::get(&mut data)?)) + } else { + return Err(Box::new(PacketError::InvalidPacketId)) + } } } - Ok(()) -} -pub async fn get_upstream_status( - server_reader: &mut OwnedReadHalf, - server_writer: &mut OwnedWriteHalf, -) -> Result { - handshake::serverbound::Handshake{ - protocol_version: mc_types::VERSION_PROTOCOL, - server_address: "localhost".to_string(), - server_port: 25565, - next_state: 1, - }.write(server_writer).await?; - mc_types::write_data(server_writer, &mut vec![0]).await?; - let mut data = mc_types::read_data(server_reader).await?; - - mc_types::get_u8(&mut data); - let json = mc_types::get_string(&mut data)?; - let status_response: StatusResponseData = serde_json::from_str(&json)?; - - // let mut out_data: Vec = vec![1]; - // out_data.append(&mut mc_types::convert_i64(0)); - // mc_types::write_packet(server_writer, &mut out_data).await?; - - Ok(status_response) -} - -pub async fn respond_legacy_status( - client_writer: &mut OwnedWriteHalf, -) -> Result<()> { - println!("Old Style Status"); - client_writer.write_u8(0xFF).await?; - - let s = "§1\0127\0".to_string() + - mc_types::VERSION_NAME + - "\0YTD Proxy§0§10"; - let utf16_vec: Vec = s - .encode_utf16() - .flat_map(|c| std::iter::once(c).chain(std::iter::once(0))) - .collect(); - - client_writer.write_u16((utf16_vec.len() / 2) as u16).await?; - for utf16_char in utf16_vec { - client_writer.write_u16(utf16_char).await?; + pub struct Status { + pub response: String + } + + impl Status { + + pub fn from_json(data: StatusResponseData) -> Result { + Ok(Self { + response: serde_json::to_string(&data)? + }) + } + + pub fn get_json(&self) -> Result { + Ok(serde_json::from_str(&self.response)?) + } + + } + + impl Packet for Status { + + fn packet_id() -> i32 {0} + + fn get(mut data: &mut Vec) -> Result { + Ok(Self { + response: mc_types::get_string(&mut data)? + }) + } + + fn convert(&self) -> Vec { + let mut data: Vec = vec![]; + data.append(&mut mc_types::convert_var_int(Self::packet_id())); + data.append(&mut mc_types::convert_string(&self.response)); + + data + } + + } + + pub struct Ping { + pub payload: i64 + } + + impl Packet for Ping { + + fn packet_id() -> i32 {1} + + fn get(mut data: &mut Vec) -> Result { + Ok(Self { + payload: mc_types::get_i64(&mut data) + }) + } + + fn convert(&self) -> Vec { + let mut data: Vec = vec![]; + data.append(&mut mc_types::convert_var_int(Self::packet_id())); + data.append(&mut mc_types::convert_i64(self.payload)); + + data + } + + } + +} + +pub mod serverbound { + + use tokio::net::tcp::OwnedReadHalf; + + use crate::mc_types::{self, Result, Packet, PacketError}; + + pub enum StatusPackets { + Status(Status), + Ping(Ping), + } + + impl StatusPackets { + pub async fn read(stream: &mut OwnedReadHalf) -> Result { + let mut data = mc_types::read_data(stream).await?; + let packet_id = mc_types::get_var_int(&mut data)?; + if packet_id == Status::packet_id() { + return Ok(Self::Status(Status::get(&mut data)?)) + } else if packet_id == Ping::packet_id() { + return Ok(Self::Ping(Ping::get(&mut data)?)) + } else { + return Err(Box::new(PacketError::InvalidPacketId)) + } + } + } + + pub struct Status {} + + impl Packet for Status { + + fn packet_id() -> i32 {0} + + fn get(mut data: &mut Vec) -> Result { + Ok(Self {}) + } + + fn convert(&self) -> Vec { + let mut data: Vec = vec![]; + data.append(&mut mc_types::convert_var_int(Self::packet_id())); + + data + } + + } + + pub struct Ping { + pub payload: i64 + } + + impl Packet for Ping { + + fn packet_id() -> i32 {1} + + fn get(mut data: &mut Vec) -> Result { + Ok(Self { + payload: mc_types::get_i64(&mut data) + }) + } + + fn convert(&self) -> Vec { + let mut data: Vec = vec![]; + data.append(&mut mc_types::convert_var_int(Self::packet_id())); + data.append(&mut mc_types::convert_i64(self.payload)); + + data + } + } - Ok(()) } diff --git a/src/status_handle.rs b/src/status_handle.rs new file mode 100644 index 0000000..7de6ed7 --- /dev/null +++ b/src/status_handle.rs @@ -0,0 +1,215 @@ +// Yeahbut December 2023 + +use std::fs::{self, File}; +use std::io::Read; + +use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; +use tokio::io::AsyncWriteExt; +use serde_json::Value; +use base64::{Engine as _, engine::general_purpose}; +use rand::Rng; + +use crate::mc_types::{self, Result, Packet}; +use crate::status; +use crate::handshake; + +async fn online_players( + server_reader: &mut OwnedReadHalf, + server_writer: &mut OwnedWriteHalf, +) -> Result { + Ok(get_upstream_status(server_reader, server_writer).await?.players) +} + +fn motd() -> String { + let default = "A Minecraft Server Proxy".to_string(); + let file_path = "./motd.json"; + + let data = match fs::read_to_string(file_path) { + Ok(data) => data, + Err(_) => return default, + }; + + let motd_data: Value = match serde_json::from_str(&data) { + Ok(value) => value, + Err(_) => return default, + }; + + let length1 = motd_data["line1"].as_array().map_or(0, |v| v.len()); + let length2 = motd_data["line2"].as_array().map_or(0, |v| v.len()); + + if length1 == 0 || length2 == 0 { + return default; + } + + let mut rng = rand::thread_rng(); + let rand1 = rng.gen_range(0..length1) as usize; + let rand2 = rng.gen_range(0..length2) as usize; + + let line1: &str = match motd_data["line1"][rand1].as_str() { + Some(s) => s, + None => return default, + }; + + // TODO: Birthdays, Holidays, and Announcements + + let line2: &str = match motd_data["line2"][rand2].as_str() { + Some(s) => s, + None => return default, + }; + + let line: String = format!("{}\n{}", line1, line2); + line +} + +fn favicon() -> Option { + let file_path = "./main_icon.png"; + + let mut file = match File::open(file_path) { + Ok(file) => file, + Err(_) => return None, + }; + + let mut buffer = Vec::new(); + if let Err(_) = file.read_to_end(&mut buffer) { + return None + }; + + let base64_string = general_purpose::STANDARD_NO_PAD.encode(buffer); + let full_string: String = + format!("data:image/png;base64,{}", base64_string); + + Some(full_string) +} + +pub async fn respond_status( + client_reader: &mut OwnedReadHalf, + client_writer: &mut OwnedWriteHalf, + server_reader: &mut Option, + server_writer: &mut Option, +)-> Result<()> { + loop { + println!("Status Handling"); + let packet = + status::serverbound::StatusPackets::read(client_reader).await?; + match packet { + status::serverbound::StatusPackets::Status(_) => { + println!("Handling Status"); + let favicon = favicon(); + + let online_players = match server_reader { + Some(server_reader) => match server_writer { + Some(server_writer) => match online_players( + server_reader, + server_writer, + ).await { + Ok(value) => Some(value), + Err(_) => None, + }, + None => None, + }, + None => None, + }; + + let status_response = + match online_players { + Some(online_players) => + status::clientbound::StatusResponseData { + version: status::clientbound::StatusVersion { + name: mc_types::VERSION_NAME.to_string(), + protocol: mc_types::VERSION_PROTOCOL, + }, + description: mc_types::Chat { + text: motd(), + }, + players: status::clientbound::StatusPlayers { + max: -13, + online: online_players.online, + sample: online_players.sample, + }, + favicon: favicon, + enforcesSecureChat: Some(false), + previewsChat: Some(false), + }, + None => status::clientbound::StatusResponseData { + version: status::clientbound::StatusVersion { + name: "Old".to_string(), + protocol: 0, + }, + description: mc_types::Chat { + text: "Server Error (Server may be starting)" + .to_string() + "\nPurple Cello Server", + }, + players: status::clientbound::StatusPlayers { + max: 0, + online: 0, + sample: None, + }, + favicon: favicon, + enforcesSecureChat: Some(false), + previewsChat: Some(false), + }, + }; + + let packet = + status::clientbound::Status::from_json(status_response)?; + packet.write(client_writer).await?; + }, + status::serverbound::StatusPackets::Ping(packet) => { + println!("Handling Ping"); + let new_packet = status::clientbound::Ping{ + payload: packet.payload, + }; + new_packet.write(client_writer).await?; + break; + } + } + } + Ok(()) +} + +pub async fn get_upstream_status( + server_reader: &mut OwnedReadHalf, + server_writer: &mut OwnedWriteHalf, +) -> Result { + handshake::serverbound::Handshake{ + protocol_version: mc_types::VERSION_PROTOCOL, + server_address: "localhost".to_string(), + server_port: 25565, + next_state: 1, + }.write(server_writer).await?; + mc_types::write_data(server_writer, &mut vec![0]).await?; + let mut data = mc_types::read_data(server_reader).await?; + + mc_types::get_u8(&mut data); + let json = mc_types::get_string(&mut data)?; + let status_response: status::clientbound::StatusResponseData = + serde_json::from_str(&json)?; + + // let mut out_data: Vec = vec![1]; + // out_data.append(&mut mc_types::convert_i64(0)); + // mc_types::write_packet(server_writer, &mut out_data).await?; + + Ok(status_response) +} + +pub async fn respond_legacy_status( + client_writer: &mut OwnedWriteHalf, +) -> Result<()> { + println!("Old Style Status"); + client_writer.write_u8(0xFF).await?; + + let s = "§1\0127\0".to_string() + + mc_types::VERSION_NAME + + "\0YTD Proxy§0§10"; + let utf16_vec: Vec = s + .encode_utf16() + .flat_map(|c| std::iter::once(c).chain(std::iter::once(0))) + .collect(); + + client_writer.write_u16((utf16_vec.len() / 2) as u16).await?; + for utf16_char in utf16_vec { + client_writer.write_u16(utf16_char).await?; + } + + Ok(()) +}