diff --git a/Cargo.lock b/Cargo.lock index b3c78926e..9e11ea864 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -776,6 +776,16 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1984,6 +1994,7 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ + "md-5", "serde", ] diff --git a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs new file mode 100644 index 000000000..2980d85d0 --- /dev/null +++ b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs @@ -0,0 +1,31 @@ +use pumpkin_macros::packet; +use pumpkin_text::TextComponent; +use serde::Serialize; + +#[derive(Serialize)] +#[packet(0x09)] +pub struct CConfigAddResourcePack { + uuid: uuid::Uuid, + url: String, + hash: String, + forced: bool, + prompt_message: Option, +} + +impl CConfigAddResourcePack { + pub fn new( + uuid: uuid::Uuid, + url: String, + hash: String, + forced: bool, + prompt_message: Option, + ) -> Self { + Self { + uuid, + url, + hash, + forced, + prompt_message, + } + } +} diff --git a/pumpkin-protocol/src/client/config/mod.rs b/pumpkin-protocol/src/client/config/mod.rs index 99128ab90..1e53e20d5 100644 --- a/pumpkin-protocol/src/client/config/mod.rs +++ b/pumpkin-protocol/src/client/config/mod.rs @@ -1,3 +1,4 @@ +mod c_add_resource_pack; mod c_config_disconnect; mod c_cookie_request; mod c_finish_config; @@ -5,6 +6,7 @@ mod c_known_packs; mod c_plugin_message; mod c_registry_data; +pub use c_add_resource_pack::*; pub use c_config_disconnect::*; pub use c_cookie_request::*; pub use c_finish_config::*; diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 83738121d..e4b726028 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -46,6 +46,6 @@ log = "0.4" mio = { version = "1.0.1", features = ["os-poll", "net"]} crossbeam-channel = "0.5.13" -uuid = { version = "1.10", features = ["serde"]} +uuid = { version = "1.10", features = ["serde", "v3"]} tokio.workspace = true \ No newline at end of file diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index c13388a8c..2de7a4b20 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -1,9 +1,9 @@ -use std::{collections::HashMap, net::IpAddr}; +use std::{collections::HashMap, net::IpAddr, time::Duration}; use base64::{engine::general_purpose, Engine}; use num_bigint::BigInt; use pumpkin_protocol::Property; -use reqwest::{StatusCode, Url}; +use reqwest::{header::CONTENT_TYPE, StatusCode, Url}; use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; @@ -52,14 +52,16 @@ pub async fn authenticate( ip: &IpAddr, server: &mut Server, ) -> Result { - assert!(server.advanced_config.authentication.use_authentication); + assert!(server.advanced_config.authentication.enabled); assert!(server.auth_client.is_some()); let address = if server .advanced_config .authentication .prevent_proxy_connections { - format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}") + let test = format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}"); + dbg!(&test); + test } else { format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}") }; diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 754103392..c8cefdf28 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -1,7 +1,9 @@ +use std::str::FromStr; + use num_traits::FromPrimitive; use pumpkin_protocol::{ client::{ - config::{CFinishConfig, CKnownPacks, CRegistryData}, + config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData}, login::{CEncryptionRequest, CLoginSuccess, CSetCompression}, status::{CPingResponse, CStatusResponse}, }, @@ -14,6 +16,7 @@ use pumpkin_protocol::{ ConnectionState, KnownPack, RawBytes, }; use pumpkin_registry::Registry; +use pumpkin_text::TextComponent; use rsa::Pkcs1v15Encrypt; use sha1::{Digest, Sha1}; @@ -167,6 +170,23 @@ impl Client { ) { self.connection_state = ConnectionState::Config; server.send_brand(self); + + let resource_config = &server.advanced_config.resource_pack; + if resource_config.enabled { + let prompt_message = if resource_config.prompt_message.is_empty() { + None + } else { + Some(TextComponent::from(resource_config.prompt_message.clone())) + }; + self.send_packet(CConfigAddResourcePack::new( + uuid::Uuid::from_str(&resource_config.resource_pack_url).unwrap(), + resource_config.resource_pack_url.clone(), + resource_config.resource_pack_sha1.clone(), + resource_config.force, + prompt_message, + )); + } + // known data packs self.send_packet(CKnownPacks::new(&[KnownPack { namespace: "minecraft", diff --git a/pumpkin/src/config/auth_config.rs b/pumpkin/src/config/auth_config.rs index 0fe373ca8..5768cd1fc 100644 --- a/pumpkin/src/config/auth_config.rs +++ b/pumpkin/src/config/auth_config.rs @@ -5,7 +5,7 @@ use crate::client::authentication::ProfileAction; #[derive(Deserialize, Serialize)] pub struct Authentication { /// Whether to use Mojang authentication. - pub use_authentication: bool, + pub enabled: bool, /// Prevent proxy connections. pub prevent_proxy_connections: bool, @@ -84,7 +84,7 @@ impl Default for TextureTypes { impl Default for Authentication { fn default() -> Self { Self { - use_authentication: true, + enabled: true, prevent_proxy_connections: true, player_profile: Default::default(), textures: Default::default(), diff --git a/pumpkin/src/config/mod.rs b/pumpkin/src/config/mod.rs index 02acb657a..98779ec4a 100644 --- a/pumpkin/src/config/mod.rs +++ b/pumpkin/src/config/mod.rs @@ -1,11 +1,13 @@ use std::path::Path; use auth_config::Authentication; +use resource_pack::ResourcePack; use serde::{Deserialize, Serialize}; use crate::{entity::player::GameMode, server::Difficulty}; pub mod auth_config; +pub mod resource_pack; /// Current Config version of the Base Config const CURRENT_BASE_VERSION: &str = "1.0.0"; @@ -18,6 +20,7 @@ pub struct AdvancedConfiguration { pub commands: Commands, pub authentication: Authentication, pub packet_compression: Compression, + pub resource_pack: ResourcePack, } #[derive(Deserialize, Serialize)] @@ -63,6 +66,7 @@ impl Default for AdvancedConfiguration { authentication: Authentication::default(), commands: Commands::default(), packet_compression: Compression::default(), + resource_pack: ResourcePack::default(), } } } @@ -83,10 +87,6 @@ pub struct BasicConfiguration { pub view_distance: u8, /// The maximum simulated view distance. pub simulation_distance: u8, - /// The path to the resource pack. - pub resource_pack: String, - /// The SHA1 hash of the resource pack. - pub resource_pack_sha1: String, /// The default game difficulty. pub default_difficulty: Difficulty, /// Whether the Nether dimension is enabled. @@ -113,8 +113,6 @@ impl Default for BasicConfiguration { max_players: 100000, view_distance: 10, simulation_distance: 10, - resource_pack: "".to_string(), - resource_pack_sha1: "".to_string(), default_difficulty: Difficulty::Normal, allow_nether: true, hardcore: false, @@ -130,21 +128,30 @@ impl AdvancedConfiguration { pub fn load>(path: P) -> AdvancedConfiguration { if path.as_ref().exists() { let toml = std::fs::read_to_string(path).expect("Couldn't read configuration"); - toml::from_str(toml.as_str()).expect("Couldn't parse, Proberbly old config") + let config: AdvancedConfiguration = + toml::from_str(toml.as_str()).expect("Couldn't parse features.toml, Proberbly old config, Replacing with a new one or just delete it"); + config.validate(); + config } else { let config = AdvancedConfiguration::default(); let toml = toml::to_string(&config).expect("Couldn't create toml!"); std::fs::write(path, toml).expect("Couldn't save configuration"); + config.validate(); config } } + pub fn validate(&self) { + self.resource_pack.validate() + } } impl BasicConfiguration { pub fn load>(path: P) -> BasicConfiguration { if path.as_ref().exists() { let toml = std::fs::read_to_string(path).expect("Couldn't read configuration"); - toml::from_str(toml.as_str()).expect("Couldn't parse") + let config: BasicConfiguration = toml::from_str(toml.as_str()).expect("Couldn't parse configuration.toml, Proberbly old config, Replacing with a new one or just delete it"); + config.validate(); + config } else { let config = BasicConfiguration::default(); let toml = toml::to_string(&config).expect("Couldn't create toml!"); @@ -170,10 +177,5 @@ impl BasicConfiguration { "When Online Mode is enabled, Encryption must be enabled" ) } - assert_eq!( - !self.resource_pack.is_empty(), - !self.resource_pack_sha1.is_empty(), - "Resource Pack path or Sha1 hash is missing" - ); } } diff --git a/pumpkin/src/config/resource_pack.rs b/pumpkin/src/config/resource_pack.rs new file mode 100644 index 000000000..25e97d759 --- /dev/null +++ b/pumpkin/src/config/resource_pack.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +pub struct ResourcePack { + pub enabled: bool, + /// The path to the resource pack. + pub resource_pack_url: String, + /// The SHA1 hash (40) of the resource pack. + pub resource_pack_sha1: String, + /// Custom propmt Text component, Leave blank for none + pub prompt_message: String, + /// Will force the Player to accept the resource pack + pub force: bool, +} + +impl ResourcePack { + pub fn validate(&self) { + assert_eq!( + !self.resource_pack_url.is_empty(), + !self.resource_pack_sha1.is_empty(), + "Resource Pack path or Sha1 hash is missing" + ); + assert!( + self.resource_pack_sha1.len() <= 40, + "Resource pack sha1 hash is too long (max. 40)" + ) + } +} + +impl Default for ResourcePack { + fn default() -> Self { + Self { + enabled: false, + resource_pack_url: "".into(), + resource_pack_sha1: "".into(), + force: false, + prompt_message: "".into(), + } + } +} diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index afcf829a2..e4ac7464e 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -4,6 +4,7 @@ use std::{ io::Cursor, rc::Rc, sync::atomic::{AtomicI32, Ordering}, + time::Duration, }; use base64::{engine::general_purpose, Engine}; @@ -77,7 +78,12 @@ impl Server { ) .into_boxed_slice(); let auth_client = if config.0.online_mode { - Some(reqwest::Client::new()) + Some( + reqwest::Client::builder() + .timeout(Duration::from_millis(5000)) + .build() + .expect("Failed to to make reqwest client"), + ) } else { None };