Move more things into Config

This commit is contained in:
Garen Tyler 2022-04-21 09:14:55 -06:00
parent b4ed1572e9
commit 182bf76835
No known key found for this signature in database
GPG Key ID: E3BF83D66394FD92
5 changed files with 197 additions and 95 deletions

View File

@ -1,5 +1,6 @@
favicon = "server-icon.png" log_level = "debug"
max_players = 20 max_players = 20
motd = "Hello world!" motd = "Hello world!"
ping_game_version = "1.18.1"
port = 25565 port = 25565
log_level = "debug" server_icon = "server-icon.png"

View File

@ -1,12 +1,23 @@
use crate::prelude::*; use crate::prelude::*;
use std::{fs::File, path::Path};
fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
let mut data = vec![];
let mut file = File::open(path)?;
file.read_to_end(&mut data)?;
Ok(data)
}
pub struct Config { pub struct Config {
pub port: u16, pub port: u16,
pub max_players: usize, pub max_players: usize,
pub motd: String, pub motd: String,
pub favicon: String, pub server_icon: String,
pub server_icon_bytes: Vec<u8>,
pub server_string: String, pub server_string: String,
pub log_level: log::LevelFilter, pub log_level: log::LevelFilter,
pub protocol_version: i32,
pub game_version: String,
pub server_version: String, pub server_version: String,
} }
impl Default for Config { impl Default for Config {
@ -20,33 +31,22 @@ impl Default for Config {
port: 25565, port: 25565,
max_players: 20, max_players: 20,
motd: "Hello world!".to_owned(), motd: "Hello world!".to_owned(),
favicon: "server-icon.png".to_owned(), server_icon: "server-icon.png".to_owned(),
server_icon_bytes: include_bytes!("../server-icon.png").to_vec(),
server_string: server_version.clone(), server_string: server_version.clone(),
log_level: if cfg!(debug_assertions) { log_level: if cfg!(debug_assertions) {
log::LevelFilter::Debug log::LevelFilter::Debug
} else { } else {
log::LevelFilter::Info log::LevelFilter::Info
}, },
protocol_version: 756,
game_version: "1.18.1".to_owned(),
server_version, server_version,
} }
} }
} }
impl Config { impl Config {
pub fn from_file(filename: &str) -> Config { pub fn from_toml(cfg: toml::Value) -> Config {
let read_file = |filename: &str| -> std::io::Result<String> {
use std::{fs::File, io::prelude::*};
let mut data = String::new();
let mut file = File::open(filename)?;
file.read_to_string(&mut data)?;
Ok(data)
};
if let Ok(config) = read_file(filename) {
Config::parse(&config)
} else {
Config::default()
}
}
pub fn parse(data: &str) -> Config {
let mut config = Config::default(); let mut config = Config::default();
let get_string = |cfg: &toml::Value, field: &str, default: &str, error: &str| -> String { let get_string = |cfg: &toml::Value, field: &str, default: &str, error: &str| -> String {
@ -62,77 +62,185 @@ impl Config {
default.to_owned() default.to_owned()
}; };
if let Ok(cfg) = data.parse::<toml::Value>() { if let Some(&toml::Value::Integer(port)) = cfg.get("port") {
if let Some(&toml::Value::Integer(port)) = cfg.get("port") { if port < u16::MIN as i64 || port > u16::MAX as i64 {
if port < u16::MIN as i64 || port > u16::MAX as i64 {
warn!("Config port must be an integer in the range of {}-{}, using default port: {}", u16::MIN, u16::MAX, config.port);
} else {
config.port = port as u16;
}
} else {
warn!( warn!(
"Config port must be an integer in the range of {}-{}, using default port: {}", "Config port must be an integer in the range of {}-{}, using default port: {}",
u16::MIN, u16::MIN,
u16::MAX, u16::MAX,
config.port config.port
); );
} else {
config.port = port as u16;
} }
} else {
warn!(
"Config port must be an integer in the range of {}-{}, using default port: {}",
u16::MIN,
u16::MAX,
config.port
);
}
if let Some(&toml::Value::Integer(max_players)) = cfg.get("max_players") { if let Some(&toml::Value::Integer(max_players)) = cfg.get("max_players") {
if max_players < 0 { if max_players < 0 {
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players); warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players);
} else {
config.max_players = max_players as usize;
}
} else {
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players);
}
config.motd = get_string(
&cfg,
"motd",
&config.motd,
&format!(
"Config motd must be a string, using default motd: \"{}\"",
config.motd
),
);
config.game_version = get_string(
&cfg,
"ping_game_version",
&config.game_version,
&format!(
"Config ping_game_version must be a string, using default ping_game_version: \"{}\"",
config.game_version
),
);
config.server_icon = get_string(
&cfg,
"server_icon",
&config.server_icon,
&format!(
"Config server_icon must be a string, using default server_icon: \"{}\"",
config.server_icon
),
);
let default_log_level = format!("{}", config.log_level).to_ascii_lowercase();
config.log_level = match &get_string(
&cfg,
"log_level",
&default_log_level,
&format!(
"Config log_level must be a string, using default log_level: {}",
default_log_level
),
)[..]
{
"off" => log::LevelFilter::Off,
"error" => log::LevelFilter::Error,
"warn" => log::LevelFilter::Warn,
"info" => log::LevelFilter::Info,
"debug" => log::LevelFilter::Debug,
"trace" => log::LevelFilter::Trace,
_ => {
warn!("Config log_level must be one of the predefined levels: off, error, warn, info, debug, trace");
config.log_level
}
};
config
}
pub fn load() -> Config {
let mut config = Config::default();
// Load the config
let config_path = Path::new("composition.toml");
if config_path.exists() {
if let Ok(cfg) = read_file(config_path) {
let cfg = String::from_utf8_lossy(&cfg);
if let Ok(cfg) = cfg.parse::<toml::Value>() {
config = Config::from_toml(cfg);
} else { } else {
config.max_players = max_players as usize; error!("Could not parse configuration file");
std::process::exit(1);
} }
} else { } else {
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players); warn!(
} "Could not read configuration file, creating {}",
config_path.to_str().unwrap_or("")
config.motd = get_string( );
&cfg, if config.write(config_path).is_err() {
"motd", error!("Could not write configuration file");
&config.motd, std::process::exit(1);
&format!(
"Config motd must be a string, using default motd: \"{}\"",
config.motd
),
);
config.favicon = get_string(
&cfg,
"favicon",
&config.favicon,
&format!(
"Config favicon must be a string, using default favicon: \"{}\"",
config.favicon
),
);
let default_log_level = format!("{}", config.log_level).to_ascii_lowercase();
config.log_level = match &get_string(
&cfg,
"log_level",
&default_log_level,
&format!(
"Config log_level must be a string, using default log_level: {}",
default_log_level
),
)[..]
{
"off" => log::LevelFilter::Off,
"error" => log::LevelFilter::Error,
"warn" => log::LevelFilter::Warn,
"info" => log::LevelFilter::Info,
"debug" => log::LevelFilter::Debug,
"trace" => log::LevelFilter::Trace,
_ => {
warn!("Config log_level must be one of the predefined levels: off, error, warn, info, debug, trace");
config.log_level
} }
}; }
config
} else { } else {
warn!("Could not parse configuration file, using default"); warn!(
config "Configuration file does not exist, creating {}",
config_path.to_str().unwrap_or("")
);
if config.write(config_path).is_err() {
error!("Could not write configuration file");
std::process::exit(1);
}
} }
// Load the server icon
let server_icon_path = Path::new(&config.server_icon);
if server_icon_path.exists() {
if let Ok(server_icon_bytes) = read_file(server_icon_path) {
config.server_icon_bytes = server_icon_bytes;
} else {
warn!(
"Could not read server icon file, creating {}",
server_icon_path.to_str().unwrap_or("")
);
if config.write_server_icon(server_icon_path).is_err() {
error!("Could not write server icon file");
std::process::exit(1);
}
}
} else {
warn!(
"Server icon file does not exist, creating {}",
server_icon_path.to_str().unwrap_or("")
);
if config.write_server_icon(server_icon_path).is_err() {
error!("Could not write server icon file");
std::process::exit(1);
}
}
config
}
pub fn write(&self, path: &Path) -> std::io::Result<()> {
use toml::{map::Map, Value};
let config = Value::Table({
let mut m = Map::new();
m.insert(
"server_icon".to_owned(),
Value::String(self.server_icon.clone()),
);
m.insert(
"log_level".to_owned(),
Value::String(format!("{}", self.log_level).to_ascii_lowercase()),
);
m.insert("max_players".to_owned(), Value::Integer(20));
m.insert("motd".to_owned(), Value::String(self.motd.clone()));
m.insert(
"ping_game_version".to_owned(),
Value::String(self.game_version.clone()),
);
m.insert("port".to_owned(), Value::Integer(25565));
m
});
if path.exists() {
std::fs::remove_file(path)?;
}
let mut file = File::create(path)?;
file.write_all(&toml::ser::to_vec(&config).unwrap())?;
Ok(())
}
pub fn write_server_icon(&self, path: &Path) -> std::io::Result<()> {
if path.exists() {
std::fs::remove_file(path)?;
}
let mut file = File::create(path)?;
file.write_all(&self.server_icon_bytes)?;
Ok(())
} }
} }

View File

@ -8,22 +8,13 @@ pub mod server;
use crate::prelude::*; use crate::prelude::*;
use std::sync::mpsc::{self, Receiver}; use std::sync::mpsc::{self, Receiver};
pub static PROTOCOL_VERSION: i32 = 757;
lazy_static! { lazy_static! {
pub static ref CONFIG: Config = Config::from_file("composition.toml"); pub static ref CONFIG: Config = Config::load();
pub static ref FAVICON: std::io::Result<Vec<u8>> = {
use std::{fs::File, io::prelude::*};
let mut data = vec![];
let mut file = File::open(CONFIG.favicon.clone())?;
file.read_to_end(&mut data)?;
Ok(data)
};
pub static ref START_TIME: std::time::Instant = std::time::Instant::now(); pub static ref START_TIME: std::time::Instant = std::time::Instant::now();
} }
/// Set up logging, read the config file, etc. /// Set up logging, read the config file, etc.
pub fn init() -> Receiver<()> { pub fn init() -> Receiver<()> {
// Load the START_TIME static - lazy_static lazy loads the value when first needed.
let _ = START_TIME.elapsed(); let _ = START_TIME.elapsed();
// Set up fern logging. // Set up fern logging.
fern::Dispatch::new() fern::Dispatch::new()
@ -57,7 +48,7 @@ pub async fn start_server() -> server::Server {
} }
pub mod prelude { pub mod prelude {
pub use crate::{config::Config, CONFIG, FAVICON, PROTOCOL_VERSION, START_TIME}; pub use crate::{config::Config, CONFIG, START_TIME};
pub use log::*; pub use log::*;
pub use serde::{Deserialize, Serialize}; pub use serde::{Deserialize, Serialize};
pub use serde_json::json; pub use serde_json::json;
@ -65,8 +56,9 @@ pub mod prelude {
pub type JSON = serde_json::Value; pub type JSON = serde_json::Value;
pub type NBT = quartz_nbt::NbtCompound; pub type NBT = quartz_nbt::NbtCompound;
pub use std::collections::VecDeque; pub use std::collections::VecDeque;
pub use std::io::{Read, Write};
pub use substring::Substring; pub use substring::Substring;
pub use tokio::io::{AsyncReadExt, AsyncWriteExt}; pub use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ParseError { pub enum ParseError {
NotEnoughData, NotEnoughData,

View File

@ -222,8 +222,7 @@ impl Packet {
"online": current_players, "online": current_players,
}, },
"description": description, "description": description,
// TODO: Add base64 favicon "favicon": format!("data:image/png;base64,{}", radix64::STD_NO_PAD.encode(&CONFIG.server_icon_bytes)),
"favicon": format!("data:image/png;base64,{}", radix64::STD_NO_PAD.encode(FAVICON.as_ref().unwrap())),
})), })),
), ),
CS01Pong { payload } => (0x01, serialize_long(*payload).to_vec()), CS01Pong { payload } => (0x01, serialize_long(*payload).to_vec()),

View File

@ -108,10 +108,12 @@ impl Server {
server_port: _, server_port: _,
next_state, next_state,
} => { } => {
if protocol_version != PROTOCOL_VERSION { if protocol_version != CONFIG.protocol_version
&& next_state == NetworkClientState::Login
{
debug!( debug!(
"Disconnecting client {} for mismatched protocols: {} (expected {})", "Disconnecting client {} for mismatched protocols: {} (expected {})",
client.id, protocol_version, PROTOCOL_VERSION client.id, protocol_version, CONFIG.protocol_version
); );
client.disconnect(None).await; client.disconnect(None).await;
return Err(()); return Err(());
@ -121,8 +123,8 @@ impl Server {
SS00Request => { SS00Request => {
let _ = client let _ = client
.send_packet(CS00Response { .send_packet(CS00Response {
version_name: "1.18.1".to_owned(), version_name: CONFIG.game_version.clone(),
protocol_version: PROTOCOL_VERSION, protocol_version: CONFIG.protocol_version,
max_players: CONFIG.max_players, max_players: CONFIG.max_players,
current_players, current_players,
description: json!({ description: json!({