Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix server icons + add server icon to config #143

Merged
merged 5 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes
16 changes: 16 additions & 0 deletions docs/config/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ The server's description displayed on the status screen.
motd=true
```

## Use favicon

Whether to use a server favicon or not

```toml
use_favicon=true
```

## Favicon path

The path to the server's favicon

```toml
favicon_path=./icon.png
```

## Default gamemode

The default game mode for players
Expand Down
8 changes: 8 additions & 0 deletions pumpkin-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ pub struct BasicConfiguration {
/// Whether to remove IPs from logs or not
#[serde_inline_default(true)]
pub scrub_ips: bool,
/// Whether to use a server favicon
#[serde_inline_default(true)]
pub use_favicon: bool,
/// Path to server favicon
#[serde_inline_default("icon.png".to_string())]
pub favicon_path: String,
}

fn default_server_address() -> SocketAddr {
Expand All @@ -119,6 +125,8 @@ impl Default for BasicConfiguration {
motd: "A Blazing fast Pumpkin Server!".to_string(),
default_gamemode: GameMode::Survival,
scrub_ips: true,
use_favicon: true,
favicon_path: "icon.png".to_string(),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pumpkin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ thiserror = "1.0"

# icon loading
base64 = "0.22.1"
png = "0.17.14"
png = "0.17.14"

# logging
simple_logger = { version = "5.0.0", features = ["threads"] }
Expand Down
65 changes: 44 additions & 21 deletions pumpkin/src/server/connection_cache.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
use std::{fs::File, path::Path};
use core::error;
use std::{
fs::File,
io::{Cursor, Read},
path::Path,
sync::LazyLock,
};

use base64::{engine::general_purpose, Engine as _};
use pumpkin_config::{BasicConfiguration, BASIC_CONFIG};
Expand All @@ -9,6 +15,29 @@ use pumpkin_protocol::{

use super::CURRENT_MC_VERSION;

static DEFAULT_ICON: LazyLock<&[u8]> =
LazyLock::new(|| include_bytes!("../../../assets/default_icon.png"));

fn load_icon_from_file<P: AsRef<Path>>(path: P) -> Result<String, Box<dyn error::Error>> {
let mut icon_file = File::open(path)?;
let mut buf = Vec::new();
icon_file.read_to_end(&mut buf)?;
load_icon_from_bytes(&buf)
}

fn load_icon_from_bytes(png_data: &[u8]) -> Result<String, Box<dyn error::Error>> {
let icon = png::Decoder::new(Cursor::new(&png_data));
let reader = icon.read_info()?;
let info = reader.info();
assert!(info.width == 64, "Icon width must be 64");
assert!(info.height == 64, "Icon height must be 64");

// Reader consumes the image. Once we verify dimensions, we want to encode the entire raw image
let mut result = "data:image/png;base64,".to_owned();
general_purpose::STANDARD.encode_string(png_data, &mut result);
Ok(result)
}

pub struct CachedStatus {
_status_response: StatusResponse,
// We cache the json response here so we don't parse it every time someone makes a Status request.
Expand Down Expand Up @@ -57,10 +86,21 @@ impl CachedStatus {
}

pub fn build_response(config: &BasicConfiguration) -> StatusResponse {
let icon_path = "/icon.png";
let icon = if Path::new(icon_path).exists() {
Some(Self::load_icon(icon_path))
let icon = if config.use_favicon {
let icon_path = &config.favicon_path;
log::info!("Loading server favicon from '{}'", icon_path);
match load_icon_from_file(icon_path).or_else(|err| {
log::warn!("Failed to load icon from '{}': {}", icon_path, err);
load_icon_from_bytes(DEFAULT_ICON.as_ref())
}) {
Ok(result) => Some(result),
Err(err) => {
log::warn!("Failed to load default icon: {}", err);
None
}
}
} else {
log::info!("Not using a server favicon");
None
};

Expand All @@ -82,21 +122,4 @@ impl CachedStatus {
enforce_secure_chat: false,
}
}

fn load_icon<P: AsRef<Path>>(path: P) -> String {
let icon = png::Decoder::new(File::open(path).expect("Failed to load icon"));
let mut reader = icon.read_info().unwrap();
let info = reader.info();
assert!(info.width == 64, "Icon width must be 64");
assert!(info.height == 64, "Icon height must be 64");
// Allocate the output buffer.
let mut buf = vec![0; reader.output_buffer_size()];
// Read the next frame. An APNG might contain multiple frames.
let info = reader.next_frame(&mut buf).unwrap();
// Grab the bytes of the image.
let bytes = &buf[..info.buffer_size()];
let mut result = "data:image/png;base64,".to_owned();
general_purpose::STANDARD.encode_string(bytes, &mut result);
result
}
}