extract server status publishing into its own file
This commit is contained in:
parent
e7fc40632d
commit
c4dc376bc9
@ -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<TextComponent<'a>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
uuid,
|
||||
uuid: UUID(uuid),
|
||||
url,
|
||||
hash,
|
||||
forced,
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@ -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<Server>, _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<Server>, 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
|
||||
|
||||
94
pumpkin/src/server/bikeshed_server_listing.rs
Normal file
94
pumpkin/src/server/bikeshed_server_listing.rs
Normal file
@ -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<u8>,
|
||||
}
|
||||
|
||||
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<u8> {
|
||||
let brand = "Pumpkin";
|
||||
let mut buf = vec![];
|
||||
let _ = VarInt(brand.len() as i32).encode(&mut buf);
|
||||
buf.extend_from_slice(brand.as_bytes());
|
||||
buf
|
||||
}
|
||||
}
|
||||
@ -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<CommandDispatcher<'static>>,
|
||||
|
||||
pub worlds: Vec<Arc<World>>,
|
||||
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<u8>,
|
||||
|
||||
/// Cache the registry so we don't have to parse it every time a player joins
|
||||
pub cached_registry: Vec<Registry>,
|
||||
@ -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<u8> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user