From 6b7d6249de6cad1a14ee956e713dcc2b120b2862 Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 18 Oct 2024 15:58:12 -0400 Subject: [PATCH] fix png encoding, default to ./icon.png, use built-in default --- pumpkin/icon.png => assets/default_icon.png | Bin pumpkin-macros/Cargo.toml | 4 ++ pumpkin-macros/src/icon.rs | 30 ++++++++++ pumpkin-macros/src/lib.rs | 7 +++ pumpkin/Cargo.toml | 3 +- pumpkin/src/server/connection_cache.rs | 61 +++++++++++++++----- 6 files changed, 90 insertions(+), 15 deletions(-) rename pumpkin/icon.png => assets/default_icon.png (100%) create mode 100644 pumpkin-macros/src/icon.rs diff --git a/pumpkin/icon.png b/assets/default_icon.png similarity index 100% rename from pumpkin/icon.png rename to assets/default_icon.png diff --git a/pumpkin-macros/Cargo.toml b/pumpkin-macros/Cargo.toml index 5dca73b94..0e351b0d7 100644 --- a/pumpkin-macros/Cargo.toml +++ b/pumpkin-macros/Cargo.toml @@ -13,3 +13,7 @@ syn = "2.0" serde.workspace = true itertools.workspace = true serde_json = "1.0.128" + +# icon loading +base64 = "0.22.1" +png = "0.17.14" diff --git a/pumpkin-macros/src/icon.rs b/pumpkin-macros/src/icon.rs new file mode 100644 index 000000000..73d3b6fde --- /dev/null +++ b/pumpkin-macros/src/icon.rs @@ -0,0 +1,30 @@ +use std::{io::Cursor, sync::LazyLock}; + +use base64::{engine::general_purpose, Engine as _}; +use proc_macro::TokenStream; +use quote::quote; + +// TODO: This is the same as pumpkin/src/server/connection_cache.rs, but we cannot reference that in +// this crate +fn load_icon(data: &[u8]) -> String { + let icon = png::Decoder::new(Cursor::new(data)); + let 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"); + + // Once we validate the dimensions, we can encode the image as-is + let mut result = "data:image/png;base64,".to_owned(); + general_purpose::STANDARD.encode_string(data, &mut result); + result +} + +static ICON: LazyLock<&[u8]> = LazyLock::new(|| include_bytes!("../../assets/default_icon.png")); + +pub fn create_icon_impl() -> TokenStream { + let encoded_icon = load_icon(&ICON); + quote! { + #encoded_icon + } + .into() +} diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index 3c27f91a2..a4fced0fa 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -40,3 +40,10 @@ pub fn blocks_enum(_item: TokenStream) -> TokenStream { pub fn block_categories_enum(_item: TokenStream) -> TokenStream { block_state::block_type_enum_impl() } + +mod icon; +#[proc_macro] +/// Creates the default server icon +pub fn create_icon(_item: TokenStream) -> TokenStream { + icon::create_icon_impl() +} diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 8e737ab63..7808c0df0 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] # pumpkin pumpkin-core = { path = "../pumpkin-core" } +pumpkin-macros = { path = "../pumpkin-macros" } pumpkin-config = { path = "../pumpkin-config" } pumpkin-inventory = { path = "../pumpkin-inventory" } pumpkin-world = { path = "../pumpkin-world" } @@ -53,7 +54,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"] } diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index da972d102..03d462c72 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -1,4 +1,9 @@ -use std::{fs::File, path::Path}; +use core::error; +use std::{ + fs::File, + io::{Cursor, Read}, + path::Path, +}; use base64::{engine::general_purpose, Engine as _}; use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; @@ -59,9 +64,37 @@ impl CachedStatus { pub fn build_response(config: &BasicConfiguration) -> StatusResponse { let icon_path = &config.favicon_path; - let icon = if !icon_path.is_empty() && Path::new(icon_path).exists() { - Some(Self::load_icon(icon_path)) + let icon = if icon_path.is_empty() { + // See if an icon exists at ./icon.png + let default_local_path = "./icon.png"; + if Path::new(default_local_path).exists() { + log::info!("Loading server icon from {}", default_local_path); + let maybe_icon = Self::load_icon(default_local_path); + match maybe_icon { + Ok(result) => Some(result), + Err(e) => { + log::warn!("Failed to load icon: {:?}", e); + None + } + } + } else { + log::info!("Using default server icon"); + Some(pumpkin_macros::create_icon!().to_string()) + } + } else if Path::new(icon_path).exists() { + log::info!("Loading server icon from {}", icon_path); + let maybe_icon = Self::load_icon(icon_path); + match maybe_icon { + Ok(result) => Some(result), + Err(e) => { + log::warn!("Failed to load icon: {:?}", e); + None + } + } } else { + // TODO: Add definitive option to have no icon? + // Currently can just use a bad path + log::warn!("Failed to load server icon at path {}", icon_path); None }; @@ -84,20 +117,20 @@ impl CachedStatus { } } - fn load_icon>(path: P) -> String { - let icon = png::Decoder::new(File::open(path).expect("Failed to load icon")); - let mut reader = icon.read_info().unwrap(); + fn load_icon>(path: P) -> Result> { + let mut icon_file = File::open(path).expect("Failed to load icon"); + let mut buf = Vec::new(); + icon_file.read_to_end(&mut buf)?; + + let icon = png::Decoder::new(Cursor::new(&buf)); + 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"); - // 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()]; + + // 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(bytes, &mut result); - result + general_purpose::STANDARD.encode_string(&buf, &mut result); + Ok(result) } }