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> {
|
impl<'a> CConfigAddResourcePack<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
uuid: UUID,
|
uuid: uuid::Uuid,
|
||||||
url: &'a str,
|
url: &'a str,
|
||||||
hash: &'a str,
|
hash: &'a str,
|
||||||
forced: bool,
|
forced: bool,
|
||||||
prompt_message: Option<TextComponent<'a>>,
|
prompt_message: Option<TextComponent<'a>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uuid,
|
uuid: UUID(uuid),
|
||||||
url,
|
url,
|
||||||
hash,
|
hash,
|
||||||
forced,
|
forced,
|
||||||
|
|||||||
@ -13,3 +13,9 @@ impl Serialize for UUID {
|
|||||||
serializer.serialize_bytes(self.0.as_bytes())
|
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 {
|
for texture in textures.textures {
|
||||||
let url =
|
let url =
|
||||||
Url::parse(&texture.1.url).map_err(|e| TextureError::InvalidURL(e.to_string()))?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use pumpkin_protocol::{
|
|||||||
client::{
|
client::{
|
||||||
config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData},
|
config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData},
|
||||||
login::{CLoginSuccess, CSetCompression},
|
login::{CLoginSuccess, CSetCompression},
|
||||||
status::{CPingResponse, CStatusResponse},
|
status::CPingResponse,
|
||||||
},
|
},
|
||||||
server::{
|
server::{
|
||||||
config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage},
|
config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage},
|
||||||
@ -17,6 +17,7 @@ use pumpkin_protocol::{
|
|||||||
},
|
},
|
||||||
ConnectionState, KnownPack, CURRENT_MC_PROTOCOL,
|
ConnectionState, KnownPack, CURRENT_MC_PROTOCOL,
|
||||||
};
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::authentication::{self, GameProfile},
|
client::authentication::{self, GameProfile},
|
||||||
@ -54,7 +55,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_status_request(&self, server: &Arc<Server>, _status_request: SStatusRequest) {
|
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) {
|
pub fn handle_ping_request(&self, _server: &Arc<Server>, ping_request: SStatusPingRequest) {
|
||||||
@ -185,25 +186,26 @@ impl Client {
|
|||||||
_login_acknowledged: SLoginAcknowledged,
|
_login_acknowledged: SLoginAcknowledged,
|
||||||
) {
|
) {
|
||||||
self.connection_state.store(ConnectionState::Config);
|
self.connection_state.store(ConnectionState::Config);
|
||||||
server.send_brand(self);
|
self.send_packet(&server.get_branding());
|
||||||
|
|
||||||
let resource_config = &ADVANCED_CONFIG.resource_pack;
|
let resource_config = &ADVANCED_CONFIG.resource_pack;
|
||||||
if resource_config.enabled {
|
if resource_config.enabled {
|
||||||
let prompt_message = if resource_config.prompt_message.is_empty() {
|
let resource_pack = CConfigAddResourcePack::new(
|
||||||
None
|
Uuid::new_v3(
|
||||||
} else {
|
|
||||||
Some(TextComponent::text(&resource_config.prompt_message))
|
|
||||||
};
|
|
||||||
self.send_packet(&CConfigAddResourcePack::new(
|
|
||||||
pumpkin_protocol::uuid::UUID(uuid::Uuid::new_v3(
|
|
||||||
&uuid::Uuid::NAMESPACE_DNS,
|
&uuid::Uuid::NAMESPACE_DNS,
|
||||||
resource_config.resource_pack_url.as_bytes(),
|
resource_config.resource_pack_url.as_bytes(),
|
||||||
)),
|
),
|
||||||
&resource_config.resource_pack_url,
|
&resource_config.resource_pack_url,
|
||||||
&resource_config.resource_pack_sha1,
|
&resource_config.resource_pack_sha1,
|
||||||
resource_config.force,
|
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
|
// 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 bikeshed_key_store::BikeShedKeyStore;
|
||||||
use image::GenericImageView;
|
use bikeshed_server_listing::BikeShedServerListing;
|
||||||
use mio::Token;
|
use mio::Token;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use pumpkin_config::{BasicConfiguration, BASIC_CONFIG};
|
use pumpkin_config::BASIC_CONFIG;
|
||||||
use pumpkin_core::GameMode;
|
use pumpkin_core::GameMode;
|
||||||
use pumpkin_entity::EntityId;
|
use pumpkin_entity::EntityId;
|
||||||
use pumpkin_plugin::PluginLoader;
|
use pumpkin_plugin::PluginLoader;
|
||||||
use pumpkin_protocol::client::login::CEncryptionRequest;
|
use pumpkin_protocol::client::login::CEncryptionRequest;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::client::status::CStatusResponse;
|
||||||
client::config::CPluginMessage, ClientPacket, Players, Sample, StatusResponse, VarInt, Version,
|
use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket};
|
||||||
CURRENT_MC_PROTOCOL,
|
|
||||||
};
|
|
||||||
use pumpkin_world::dimension::Dimension;
|
use pumpkin_world::dimension::Dimension;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::{
|
use std::{
|
||||||
io::Cursor,
|
|
||||||
path::Path,
|
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicI32, Ordering},
|
atomic::{AtomicI32, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
@ -37,22 +32,16 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod bikeshed_key_store;
|
mod bikeshed_key_store;
|
||||||
|
mod bikeshed_server_listing;
|
||||||
pub const CURRENT_MC_VERSION: &str = "1.21.1";
|
pub const CURRENT_MC_VERSION: &str = "1.21.1";
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
key_store: BikeShedKeyStore,
|
key_store: BikeShedKeyStore,
|
||||||
|
server_listing: BikeShedServerListing,
|
||||||
pub plugin_loader: PluginLoader,
|
pub plugin_loader: PluginLoader,
|
||||||
|
|
||||||
pub command_dispatcher: Arc<CommandDispatcher<'static>>,
|
pub command_dispatcher: Arc<CommandDispatcher<'static>>,
|
||||||
|
|
||||||
pub worlds: Vec<Arc<World>>,
|
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
|
/// Cache the registry so we don't have to parse it every time a player joins
|
||||||
pub cached_registry: Vec<Registry>,
|
pub cached_registry: Vec<Registry>,
|
||||||
@ -68,13 +57,9 @@ pub struct Server {
|
|||||||
impl Server {
|
impl Server {
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
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
|
// TODO: only create when needed
|
||||||
let key_store = BikeShedKeyStore::new();
|
let key_store = BikeShedKeyStore::new();
|
||||||
|
let server_listing = BikeShedServerListing::new();
|
||||||
let auth_client = if BASIC_CONFIG.online_mode {
|
let auth_client = if BASIC_CONFIG.online_mode {
|
||||||
Some(
|
Some(
|
||||||
reqwest::Client::builder()
|
reqwest::Client::builder()
|
||||||
@ -106,9 +91,7 @@ impl Server {
|
|||||||
command_dispatcher: Arc::new(command_dispatcher),
|
command_dispatcher: Arc::new(command_dispatcher),
|
||||||
auth_client,
|
auth_client,
|
||||||
key_store,
|
key_store,
|
||||||
status_response,
|
server_listing,
|
||||||
status_response_json,
|
|
||||||
cached_server_brand,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +101,7 @@ impl Server {
|
|||||||
GameMode::Undefined => GameMode::Survival,
|
GameMode::Undefined => GameMode::Survival,
|
||||||
game_mode => game_mode,
|
game_mode => game_mode,
|
||||||
};
|
};
|
||||||
// Basicly the default world
|
// Basically the default world
|
||||||
// TODO: select default from config
|
// TODO: select default from config
|
||||||
let world = self.worlds[0].clone();
|
let world = self.worlds[0].clone();
|
||||||
|
|
||||||
@ -165,6 +148,14 @@ impl Server {
|
|||||||
self.entity_id.fetch_add(1, Ordering::SeqCst)
|
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>(
|
pub fn encryption_request<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
verification_token: &'a [u8; 4],
|
verification_token: &'a [u8; 4],
|
||||||
@ -181,64 +172,4 @@ impl Server {
|
|||||||
pub fn digest_secret(&self, secret: &[u8]) -> String {
|
pub fn digest_secret(&self, secret: &[u8]) -> String {
|
||||||
self.key_store.get_digest(secret)
|
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