From c4dc376bc94ac4e9824350e462baab0c26997080 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Mon, 9 Sep 2024 22:00:54 -0400 Subject: [PATCH] extract server status publishing into its own file --- .../src/client/config/c_add_resource_pack.rs | 4 +- pumpkin-protocol/src/uuid.rs | 6 + pumpkin/src/client/authentication.rs | 3 +- pumpkin/src/client/client_packet.rs | 28 ++--- pumpkin/src/server/bikeshed_server_listing.rs | 94 ++++++++++++++++ pumpkin/src/server/mod.rs | 103 +++--------------- 6 files changed, 136 insertions(+), 102 deletions(-) create mode 100644 pumpkin/src/server/bikeshed_server_listing.rs diff --git a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs index 824141e25..926fb6e39 100644 --- a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs +++ b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs @@ -16,14 +16,14 @@ pub struct CConfigAddResourcePack<'a> { impl<'a> CConfigAddResourcePack<'a> { pub fn new( - uuid: UUID, + uuid: uuid::Uuid, url: &'a str, hash: &'a str, forced: bool, prompt_message: Option>, ) -> Self { Self { - uuid, + uuid: UUID(uuid), url, hash, forced, diff --git a/pumpkin-protocol/src/uuid.rs b/pumpkin-protocol/src/uuid.rs index a187a083a..39988ae40 100644 --- a/pumpkin-protocol/src/uuid.rs +++ b/pumpkin-protocol/src/uuid.rs @@ -13,3 +13,9 @@ impl Serialize for UUID { serializer.serialize_bytes(self.0.as_bytes()) } } + +impl UUID { + pub fn new(data: &[u8]) -> Self { + Self(uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, data)) + } +} diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 0d3a537c4..20a3c28e7 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -92,7 +92,8 @@ pub fn unpack_textures(property: Property, config: &TextureConfig) -> Result<(), for texture in textures.textures { let url = Url::parse(&texture.1.url).map_err(|e| TextureError::InvalidURL(e.to_string()))?; - is_texture_url_valid(url, config)? } + is_texture_url_valid(url, config)? + } Ok(()) } diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 817adb245..9c4391405 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -7,7 +7,7 @@ use pumpkin_protocol::{ client::{ config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData}, login::{CLoginSuccess, CSetCompression}, - status::{CPingResponse, CStatusResponse}, + status::CPingResponse, }, server::{ config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage}, @@ -17,6 +17,7 @@ use pumpkin_protocol::{ }, ConnectionState, KnownPack, CURRENT_MC_PROTOCOL, }; +use uuid::Uuid; use crate::{ client::authentication::{self, GameProfile}, @@ -54,7 +55,7 @@ impl Client { } pub fn handle_status_request(&self, server: &Arc, _status_request: SStatusRequest) { - self.send_packet(&CStatusResponse::new(&server.status_response_json)); + self.send_packet(&server.get_status()); } pub fn handle_ping_request(&self, _server: &Arc, ping_request: SStatusPingRequest) { @@ -185,25 +186,26 @@ impl Client { _login_acknowledged: SLoginAcknowledged, ) { self.connection_state.store(ConnectionState::Config); - server.send_brand(self); + self.send_packet(&server.get_branding()); let resource_config = &ADVANCED_CONFIG.resource_pack; if resource_config.enabled { - let prompt_message = if resource_config.prompt_message.is_empty() { - None - } else { - Some(TextComponent::text(&resource_config.prompt_message)) - }; - self.send_packet(&CConfigAddResourcePack::new( - pumpkin_protocol::uuid::UUID(uuid::Uuid::new_v3( + let resource_pack = CConfigAddResourcePack::new( + Uuid::new_v3( &uuid::Uuid::NAMESPACE_DNS, resource_config.resource_pack_url.as_bytes(), - )), + ), &resource_config.resource_pack_url, &resource_config.resource_pack_sha1, resource_config.force, - prompt_message, - )); + if !resource_config.prompt_message.is_empty() { + Some(TextComponent::text(&resource_config.prompt_message)) + } else { + None + }, + ); + + self.send_packet(&resource_pack); } // known data packs diff --git a/pumpkin/src/server/bikeshed_server_listing.rs b/pumpkin/src/server/bikeshed_server_listing.rs new file mode 100644 index 000000000..57ca00fc7 --- /dev/null +++ b/pumpkin/src/server/bikeshed_server_listing.rs @@ -0,0 +1,94 @@ +use std::{io::Cursor, path::Path}; + +use base64::{engine::general_purpose, Engine as _}; +use image::GenericImageView as _; +use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; +use pumpkin_protocol::{ + client::{config::CPluginMessage, status::CStatusResponse}, + Players, Sample, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL, +}; + +use super::CURRENT_MC_VERSION; + +pub struct BikeShedServerListing { + _status_response: StatusResponse, + // We cache the json response here so we don't parse it every time someone makes a Status request. + // Keep in mind that we must parse this again, when the StatusResponse changes which usually happen when a player joins or leaves + status_response_json: String, + /// Cached Server brand buffer so we don't have to rebuild them every time a player joins + cached_server_brand: Vec, +} + +impl BikeShedServerListing { + pub fn new() -> Self { + let status_response = Self::build_response(&BASIC_CONFIG); + let status_response_json = serde_json::to_string(&status_response) + .expect("Failed to parse Status response into JSON"); + let cached_server_brand = Self::build_brand(); + + BikeShedServerListing { + _status_response: status_response, + status_response_json, + cached_server_brand, + } + } + + pub fn get_branding(&self) -> CPluginMessage { + CPluginMessage::new("minecraft:brand", &self.cached_server_brand) + } + + pub fn get_status(&self) -> CStatusResponse<'_> { + CStatusResponse::new(&self.status_response_json) + } + + pub fn build_response(config: &BasicConfiguration) -> StatusResponse { + let icon_path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); + let icon = if Path::new(icon_path).exists() { + Some(Self::load_icon(icon_path)) + } else { + None + }; + + StatusResponse { + version: Some(Version { + name: CURRENT_MC_VERSION.into(), + protocol: CURRENT_MC_PROTOCOL, + }), + players: Some(Players { + max: config.max_players, + online: 0, + sample: vec![Sample { + name: "".into(), + id: "".into(), + }], + }), + description: config.motd.clone(), + favicon: icon, + enforece_secure_chat: false, + } + } + + fn load_icon(path: &str) -> String { + let icon = match image::open(path).map_err(|e| panic!("error loading icon: {}", e)) { + Ok(icon) => icon, + Err(_) => return "".into(), + }; + let dimension = icon.dimensions(); + assert!(dimension.0 == 64, "Icon width must be 64"); + assert!(dimension.1 == 64, "Icon height must be 64"); + let mut image = Vec::with_capacity(64 * 64 * 4); + icon.write_to(&mut Cursor::new(&mut image), image::ImageFormat::Png) + .unwrap(); + let mut result = "data:image/png;base64,".to_owned(); + general_purpose::STANDARD.encode_string(image, &mut result); + result + } + + fn build_brand() -> Vec { + let brand = "Pumpkin"; + let mut buf = vec![]; + let _ = VarInt(brand.len() as i32).encode(&mut buf); + buf.extend_from_slice(brand.as_bytes()); + buf + } +} diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 9569e14c4..ca43f5211 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,22 +1,17 @@ -use base64::{engine::general_purpose, Engine}; use bikeshed_key_store::BikeShedKeyStore; -use image::GenericImageView; +use bikeshed_server_listing::BikeShedServerListing; use mio::Token; use parking_lot::{Mutex, RwLock}; -use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; +use pumpkin_config::BASIC_CONFIG; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; use pumpkin_plugin::PluginLoader; use pumpkin_protocol::client::login::CEncryptionRequest; -use pumpkin_protocol::{ - client::config::CPluginMessage, ClientPacket, Players, Sample, StatusResponse, VarInt, Version, - CURRENT_MC_PROTOCOL, -}; +use pumpkin_protocol::client::status::CStatusResponse; +use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_world::dimension::Dimension; use std::collections::HashMap; use std::{ - io::Cursor, - path::Path, sync::{ atomic::{AtomicI32, Ordering}, Arc, @@ -37,22 +32,16 @@ use crate::{ }; mod bikeshed_key_store; +mod bikeshed_server_listing; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { key_store: BikeShedKeyStore, + server_listing: BikeShedServerListing, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, - pub worlds: Vec>, - pub status_response: StatusResponse, - // We cache the json response here so we don't parse it every time someone makes a Status request. - // Keep in mind that we must parse this again, when the StatusResponse changes which usally happen when a player joins or leaves - pub status_response_json: String, - - /// Cache the Server brand buffer so we don't have to rebuild them every time a player joins - pub cached_server_brand: Vec, /// Cache the registry so we don't have to parse it every time a player joins pub cached_registry: Vec, @@ -68,13 +57,9 @@ pub struct Server { impl Server { #[allow(clippy::new_without_default)] pub fn new() -> Self { - let status_response = Self::build_response(&BASIC_CONFIG); - let status_response_json = serde_json::to_string(&status_response) - .expect("Failed to parse Status response into JSON"); - let cached_server_brand = Self::build_brand(); - // TODO: only create when needed let key_store = BikeShedKeyStore::new(); + let server_listing = BikeShedServerListing::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( reqwest::Client::builder() @@ -106,9 +91,7 @@ impl Server { command_dispatcher: Arc::new(command_dispatcher), auth_client, key_store, - status_response, - status_response_json, - cached_server_brand, + server_listing, } } @@ -118,7 +101,7 @@ impl Server { GameMode::Undefined => GameMode::Survival, game_mode => game_mode, }; - // Basicly the default world + // Basically the default world // TODO: select default from config let world = self.worlds[0].clone(); @@ -165,6 +148,14 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } + pub fn get_branding(&self) -> CPluginMessage<'_> { + self.server_listing.get_branding() + } + + pub fn get_status(&self) -> CStatusResponse<'_> { + self.server_listing.get_status() + } + pub fn encryption_request<'a>( &'a self, verification_token: &'a [u8; 4], @@ -181,64 +172,4 @@ impl Server { pub fn digest_secret(&self, secret: &[u8]) -> String { self.key_store.get_digest(secret) } - - pub fn build_brand() -> Vec { - let brand = "Pumpkin"; - let mut buf = vec![]; - let _ = VarInt(brand.len() as i32).encode(&mut buf); - buf.extend_from_slice(brand.as_bytes()); - buf - } - - pub fn send_brand(&self, client: &Client) { - // send server brand - client.send_packet(&CPluginMessage::new( - "minecraft:brand", - &self.cached_server_brand, - )); - } - - pub fn build_response(config: &BasicConfiguration) -> StatusResponse { - let icon_path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); - let icon = if Path::new(icon_path).exists() { - Some(Self::load_icon(icon_path)) - } else { - None - }; - - StatusResponse { - version: Some(Version { - name: CURRENT_MC_VERSION.into(), - protocol: CURRENT_MC_PROTOCOL, - }), - players: Some(Players { - max: config.max_players, - online: 0, - sample: vec![Sample { - name: "".into(), - id: "".into(), - }], - }), - description: config.motd.clone(), - favicon: icon, - // TODO - enforece_secure_chat: false, - } - } - - pub fn load_icon(path: &str) -> String { - let icon = match image::open(path).map_err(|e| panic!("error loading icon: {}", e)) { - Ok(icon) => icon, - Err(_) => return "".into(), - }; - let dimension = icon.dimensions(); - assert!(dimension.0 == 64, "Icon width must be 64"); - assert!(dimension.1 == 64, "Icon height must be 64"); - let mut image = Vec::with_capacity(64 * 64 * 4); - icon.write_to(&mut Cursor::new(&mut image), image::ImageFormat::Png) - .unwrap(); - let mut result = "data:image/png;base64,".to_owned(); - general_purpose::STANDARD.encode_string(image, &mut result); - result - } }