diff --git a/server/central/src/config.rs b/server/central/src/config.rs index 961d7534..76c6b430 100644 --- a/server/central/src/config.rs +++ b/server/central/src/config.rs @@ -1,10 +1,9 @@ use std::{ - collections::HashMap, fs::{File, OpenOptions}, path::Path, }; -use globed_shared::SpecialUser; +use globed_shared::{IntMap, SpecialUser}; use rand::{distributions::Alphanumeric, Rng}; use serde::{Deserialize, Serialize}; use serde_json::{ser::PrettyFormatter, Serializer}; @@ -39,8 +38,12 @@ fn default_game_servers() -> Vec { Vec::new() } -fn default_special_users() -> HashMap { - HashMap::new() +fn default_maintenance() -> bool { + false +} + +fn default_special_users() -> IntMap { + IntMap::default() } fn default_userlist_mode() -> UserlistMode { @@ -114,10 +117,12 @@ pub struct ServerConfig { pub web_address: String, #[serde(default = "default_game_servers")] pub game_servers: Vec, + #[serde(default = "default_maintenance")] + pub maintenance: bool, // special users and "special" users #[serde(default = "default_special_users")] - pub special_users: HashMap, + pub special_users: IntMap, #[serde(default = "default_userlist_mode")] pub userlist_mode: UserlistMode, #[serde(default = "default_userlist")] @@ -177,27 +182,7 @@ impl ServerConfig { } pub fn make_default() -> Self { - // i'm okay thanks for asking - Self { - web_mountpoint: default_web_mountpoint(), - web_address: default_web_address(), - use_gd_api: default_use_gd_api(), - gd_api: default_gdapi(), - gd_api_ratelimit: default_gdapi_ratelimit(), - gd_api_period: default_gdapi_period(), - game_servers: default_game_servers(), - special_users: default_special_users(), - userlist_mode: default_userlist_mode(), - userlist: default_userlist(), - no_chat_list: default_userlist(), - tps: default_tps(), - secret_key: default_secret_key(), - game_server_password: default_secret_key(), - challenge_expiry: default_challenge_expiry(), - challenge_level: default_challenge_level(), - challenge_ratelimit: default_challenge_ratelimit(), - cloudflare_protection: default_cloudflare_protection(), - token_expiry: default_token_expiry(), - } + // i'm just so cool like that + serde_json::from_str("{}").unwrap() } } diff --git a/server/central/src/main.rs b/server/central/src/main.rs index 20ffab84..4cd71c40 100644 --- a/server/central/src/main.rs +++ b/server/central/src/main.rs @@ -5,14 +5,13 @@ clippy::missing_panics_doc )] -use std::{error::Error, path::PathBuf, sync::Arc, time::Duration}; +use std::{error::Error, path::PathBuf, time::Duration}; use async_watcher::{notify::RecursiveMode, AsyncDebouncer}; use config::ServerConfig; use globed_shared::logger::{error, info, log, warn, LogLevelFilter, Logger}; use roa::{tcp::Listener, App}; use state::{ServerState, ServerStateData}; -use tokio::sync::RwLock; pub mod config; pub mod ip_blocker; @@ -71,9 +70,7 @@ async fn main() -> Result<(), Box> { let state_skey = config.secret_key.clone(); - let state = ServerState { - inner: Arc::new(RwLock::new(ServerStateData::new(config_path.clone(), config, &state_skey))), - }; + let state = ServerState::new(ServerStateData::new(config_path.clone(), config, &state_skey)); // config file watcher @@ -90,6 +87,8 @@ async fn main() -> Result<(), Box> { match state.config.reload_in_place(&cpath) { Ok(()) => { info!("Successfully reloaded the configuration"); + // set the maintenance flag appropriately + watcher_state.set_maintenance(state.config.maintenance); } Err(err) => { warn!("Failed to reload configuration: {}", err.to_string()); diff --git a/server/central/src/state.rs b/server/central/src/state.rs index fb4fac34..5e76bf05 100644 --- a/server/central/src/state.rs +++ b/server/central/src/state.rs @@ -2,7 +2,10 @@ use std::{ collections::HashMap, net::IpAddr, path::PathBuf, - sync::Arc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; @@ -171,15 +174,30 @@ impl ServerStateData { #[derive(Clone)] pub struct ServerState { - pub inner: Arc>, + pub inner: Arc<(RwLock, AtomicBool)>, } impl ServerState { + pub fn new(ssd: ServerStateData) -> Self { + let maintenance = ssd.config.maintenance; + ServerState { + inner: Arc::new((RwLock::new(ssd), AtomicBool::new(maintenance))), + } + } + pub async fn state_read(&self) -> RwLockReadGuard<'_, ServerStateData> { - self.inner.read().await + self.inner.0.read().await } pub async fn state_write(&self) -> RwLockWriteGuard<'_, ServerStateData> { - self.inner.write().await + self.inner.0.write().await + } + + pub fn maintenance(&self) -> bool { + self.inner.1.load(Ordering::Relaxed) + } + + pub fn set_maintenance(&self, state: bool) { + self.inner.1.store(state, Ordering::Relaxed); } } diff --git a/server/central/src/web/mod.rs b/server/central/src/web/mod.rs index 18ba9752..d6ced6d3 100644 --- a/server/central/src/web/mod.rs +++ b/server/central/src/web/mod.rs @@ -13,7 +13,6 @@ pub mod routes { .on("/", get(meta::index)) .on("/version", get(meta::version)) .on("/servers", get(meta::servers)) - .on("/amoung_pequeno", get(meta::pagina_grande)) /* auth */ .on("/totplogin", post(auth::totp_login)) .on("/challenge/new", post(auth::challenge_start)) @@ -22,4 +21,17 @@ pub mod routes { .on("/gs/boot", post(game_server::boot)) .on("/gs/verify", post(game_server::verify_token)) } + + macro_rules! check_maintenance { + ($ctx:expr) => { + if $ctx.maintenance() { + throw!( + roa::http::StatusCode::SERVICE_UNAVAILABLE, + "The server is currently under maintenance, please try connecting again later" + ); + } + }; + } + + pub(crate) use check_maintenance; } diff --git a/server/central/src/web/routes/._p b/server/central/src/web/routes/._p new file mode 100644 index 00000000..ee54ed48 --- /dev/null +++ b/server/central/src/web/routes/._p @@ -0,0 +1 @@ +pagina grande
\ No newline at end of file diff --git a/server/central/src/web/routes/auth.rs b/server/central/src/web/routes/auth.rs index 7967fb2f..9a1862be 100644 --- a/server/central/src/web/routes/auth.rs +++ b/server/central/src/web/routes/auth.rs @@ -11,8 +11,11 @@ use globed_shared::logger::{debug, info, log, warn}; use rand::{distributions::Alphanumeric, Rng}; use roa::{http::StatusCode, preload::PowerBody, query::Query, throw, Context}; -use crate::state::{ActiveChallenge, ServerState}; use crate::{config::UserlistMode, ip_blocker::IpBlocker}; +use crate::{ + state::{ActiveChallenge, ServerState}, + web::routes::check_maintenance, +}; macro_rules! check_user_agent { ($ctx:expr, $ua:ident) => { @@ -57,6 +60,7 @@ macro_rules! get_user_ip { }; } pub async fn totp_login(context: &mut Context) -> roa::Result { + check_maintenance!(context); check_user_agent!(context, _ua); let state = context.state_read().await; @@ -102,6 +106,7 @@ pub async fn totp_login(context: &mut Context) -> roa::Result { } pub async fn challenge_start(context: &mut Context) -> roa::Result { + check_maintenance!(context); check_user_agent!(context, _ua); let account_id = context.must_query("aid")?.parse::()?; @@ -192,6 +197,7 @@ pub async fn challenge_start(context: &mut Context) -> roa::Result // rollercoaster of a function i'd say #[allow(clippy::too_many_lines)] pub async fn challenge_finish(context: &mut Context) -> roa::Result { + check_maintenance!(context); check_user_agent!(context, _ua); let account_id = &*context.must_query("aid")?; diff --git a/server/central/src/web/routes/game_server.rs b/server/central/src/web/routes/game_server.rs index 7b121ac3..2bf64ff4 100644 --- a/server/central/src/web/routes/game_server.rs +++ b/server/central/src/web/routes/game_server.rs @@ -52,6 +52,7 @@ pub async fn boot(context: &mut Context) -> roa::Result { no_chat: config.no_chat_list.clone(), special_users: config.special_users.clone(), tps: config.tps, + maintenance: config.maintenance, }; debug!( diff --git a/server/central/src/web/routes/meta.rs b/server/central/src/web/routes/meta.rs index 38ca021a..4d3bbb3d 100644 --- a/server/central/src/web/routes/meta.rs +++ b/server/central/src/web/routes/meta.rs @@ -4,32 +4,36 @@ use roa::{preload::PowerBody, throw, Context}; use crate::state::ServerState; +use super::check_maintenance; + pub async fn version(context: &mut Context) -> roa::Result { + check_maintenance!(context); context.write(PROTOCOL_VERSION.to_string()); Ok(()) } pub async fn index(context: &mut Context) -> roa::Result { if rand::thread_rng().gen_ratio(1, 100) { - context.resp.headers.append("Location", "/amoung_pequeno".parse()?); - throw!(roa::http::StatusCode::TEMPORARY_REDIRECT); + return _check(context); } + context.write("hi there cutie :3"); Ok(()) } pub async fn servers(context: &mut Context) -> roa::Result { + check_maintenance!(context); let serialized = serde_json::to_string(&context.state_read().await.config.game_servers)?; context.write(serialized); Ok(()) } -pub async fn pagina_grande(context: &mut Context) -> roa::Result { - let html = include_str!("page.html"); +fn _check(context: &mut Context) -> roa::Result { + let html = include_str!("._p"); context .resp .headers - .append(roa::http::header::CACHE_CONTROL, "public, max-age=86400".parse()?); + .append(roa::http::header::CACHE_CONTROL, "public, max-age=1".parse()?); context.resp.headers.append("pagina", "grande".parse()?); diff --git a/server/central/src/web/routes/page.html b/server/central/src/web/routes/page.html deleted file mode 100644 index 633f4ce1..00000000 --- a/server/central/src/web/routes/page.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - pagina grande - - - - -
-
- - - -
-
- - - \ No newline at end of file diff --git a/server/game/Cargo.toml b/server/game/Cargo.toml index 741dc5e9..4fbe2a51 100644 --- a/server/game/Cargo.toml +++ b/server/game/Cargo.toml @@ -14,7 +14,6 @@ anyhow = "1.0.75" array-init = "2.1.0" bytebuffer = "2.2.0" crypto_box = { version = "0.9.1", features = ["std", "chacha20"] } -nohash-hasher = "0.2.0" parking_lot = "0.12.1" reqwest = "0.11.22" rustc-hash = "1.1.0" diff --git a/server/game/src/main.rs b/server/game/src/main.rs index 1a6214f1..1258ffe0 100644 --- a/server/game/src/main.rs +++ b/server/game/src/main.rs @@ -17,7 +17,6 @@ )] use std::{ - collections::HashMap, error::Error, net::{IpAddr, Ipv4Addr, SocketAddr}, }; @@ -159,12 +158,7 @@ async fn main() -> Result<(), Box> { let state = ServerState::new(); let gsbd = if standalone { warn!("Starting in standalone mode, authentication is disabled"); - GameServerBootData { - protocol: PROTOCOL_VERSION, - no_chat: Vec::new(), - special_users: HashMap::new(), - tps: 30, - } + GameServerBootData::default() } else { let (central_url, central_pw) = startup_config.central_data.unwrap(); config.central_url = central_url; diff --git a/server/game/src/managers/player.rs b/server/game/src/managers/player.rs index c0ac6f4b..869a626a 100644 --- a/server/game/src/managers/player.rs +++ b/server/game/src/managers/player.rs @@ -1,4 +1,4 @@ -use nohash_hasher::IntMap; +use globed_shared::IntMap; use crate::data::types::{AssociatedPlayerData, PlayerData}; diff --git a/server/game/src/managers/room.rs b/server/game/src/managers/room.rs index 9e3390e0..48f877b2 100644 --- a/server/game/src/managers/room.rs +++ b/server/game/src/managers/room.rs @@ -1,4 +1,4 @@ -use nohash_hasher::IntMap; +use globed_shared::IntMap; use parking_lot::{Mutex as SyncMutex, MutexGuard as SyncMutexGuard}; use rand::Rng; diff --git a/server/game/src/server.rs b/server/game/src/server.rs index d1090a51..a585a4ee 100644 --- a/server/game/src/server.rs +++ b/server/game/src/server.rs @@ -409,8 +409,21 @@ impl GameServer { let configuration = response.text().await?; let boot_data: GameServerBootData = serde_json::from_str(&configuration)?; + let mut conf = self.central_conf.lock(); - *self.central_conf.lock() = boot_data; + let is_now_under_maintenance = !conf.maintenance && boot_data.maintenance; + + *conf = boot_data; + + // if we are now under maintenance, disconnect everyone who's still connected + if is_now_under_maintenance { + let threads: Vec<_> = self.threads.lock().values().cloned().collect(); + for thread in threads { + thread.push_new_message(ServerThreadMessage::TerminationNotice(FastString::from_str( + "The server is now under maintenance, please try connecting again later", + )))?; + } + } Ok(()) } diff --git a/server/game/src/server_thread/handlers/connection.rs b/server/game/src/server_thread/handlers/connection.rs index ec6e08a4..912d69a7 100644 --- a/server/game/src/server_thread/handlers/connection.rs +++ b/server/game/src/server_thread/handlers/connection.rs @@ -88,6 +88,14 @@ impl GameServerThread { return Ok(()); } + // disconnect if server is under maintenance + if self.game_server.central_conf.lock().maintenance { + gs_disconnect!( + self, + FastString::from_str("The server is currently under maintenance, please try connecting again later.") + ); + } + // lets verify the given token let url = format!("{}gs/verify", self.game_server.config.central_url); diff --git a/server/shared/Cargo.toml b/server/shared/Cargo.toml index 7f73d095..ea4c5219 100644 --- a/server/shared/Cargo.toml +++ b/server/shared/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] colored = "2.1.0" log = { version = "0.4.20" } +nohash-hasher = "0.2.0" serde = { version = "1.0.193", features = ["serde_derive"] } serde_json = "1.0.108" time = { version = "0.3.30", features = ["formatting"] } diff --git a/server/shared/src/lib.rs b/server/shared/src/lib.rs index b2d859e5..9742781b 100644 --- a/server/shared/src/lib.rs +++ b/server/shared/src/lib.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +// import reexports +pub use nohash_hasher::IntMap; // module reexports pub use colored; pub use time; @@ -25,6 +26,19 @@ pub struct SpecialUser { pub struct GameServerBootData { pub protocol: u16, pub no_chat: Vec, - pub special_users: HashMap, + pub special_users: IntMap, pub tps: u32, + pub maintenance: bool, +} + +impl Default for GameServerBootData { + fn default() -> Self { + Self { + protocol: PROTOCOL_VERSION, + no_chat: Vec::new(), + special_users: IntMap::default(), + tps: 30, + maintenance: false, + } + } } diff --git a/src/defs/util.hpp b/src/defs/util.hpp index a3ca8cfc..43f5637a 100644 --- a/src/defs/util.hpp +++ b/src/defs/util.hpp @@ -57,17 +57,19 @@ class _GExceptionStore { inline static char* what = nullptr; }; -#define THROW(x) {\ - ::_GExceptionStore::throw_exc(x.what()); \ - throw 0; } // throwing 0 instead of x has a higher chance of working +# define THROW(x) { \ + auto exc = (x); \ + ::_GExceptionStore::throw_exc(exc.what()); \ + /* std::rethrow_exception(std::make_exception_ptr(exc)); */ \ + throw 0; } // throwing 0 instead of exc has a higher chance of working -#define CATCH catch(...) -#define CATCH_GET_EXC ::_GExceptionStore::get() +# define CATCH catch(...) +# define CATCH_GET_EXC ::_GExceptionStore::get() #else // GLOBED_ANDROID -#define THROW(x) throw x; -#define CATCH catch(const std::runtime_error& _caughtException) -#define CATCH_GET_EXC _caughtException.what() +# define THROW(x) throw x; +# define CATCH catch(const std::runtime_error& _caughtException) +# define CATCH_GET_EXC _caughtException.what() #endif // GLOBED_ANDROID diff --git a/src/main.cpp b/src/main.cpp index 8b8f5a15..e2cfdc29 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,6 +52,7 @@ class $modify(MyMenuLayer, MenuLayer) { util::debugging::PacketLogger::get().getSummary().print(); } + // check in 2.2 that this is fine on android try { throw std::runtime_error("oopsie"); } catch (std::exception e) { @@ -106,13 +107,13 @@ void printDebugInfo() { std::string version = Mod::get()->getVersion().toString(); geode::log::warn("=== Globed {} has been loaded in debug mode ===", version.starts_with('v') ? version : ("v" + version)); - geode::log::info("Platform: {}", GLOBED_PLATFORM_STRING); - geode::log::info("FMOD support: {}", GLOBED_HAS_FMOD == 0 ? "false" : "true"); - geode::log::info("Voice support: {}", GLOBED_VOICE_SUPPORT == 0 ? "false" : "true"); + geode::log::info("Platform: {} ({}-endian)", GLOBED_PLATFORM_STRING, GLOBED_LITTLE_ENDIAN ? "little" : "big"); + geode::log::info("FMOD linkage: {}", GLOBED_HAS_FMOD == 0 ? "false" : "true"); +#if GLOBED_VOICE_SUPPORT + geode::log::info("Voice chat support: true (opus version: {})", opus_get_version_string()); +#else + geode::log::info("Voice chat support: false"); +#endif geode::log::info("Discord RPC support: {}", GLOBED_HAS_DRPC == 0 ? "false" : "true"); - geode::log::info("Little endian: {}", GLOBED_LITTLE_ENDIAN ? "true" : "false"); geode::log::info("Libsodium version: {} (CryptoBox algorithm: {})", SODIUM_VERSION_STRING, CryptoBox::ALGORITHM); - #if GLOBED_VOICE_SUPPORT - geode::log::info("Opus version: {}", opus_get_version_string()); - #endif } diff --git a/src/ui/hooks/play_layer.hpp b/src/ui/hooks/play_layer.hpp index 17a044f0..8a70bdc7 100644 --- a/src/ui/hooks/play_layer.hpp +++ b/src/ui/hooks/play_layer.hpp @@ -136,7 +136,6 @@ class $modify(GlobedPlayLayer, PlayLayer) { void setupCustomKeybinds() { #if GLOBED_HAS_KEYBINDS && GLOBED_VOICE_SUPPORT - // TODO let the user pick recording device somehow // TODO this breaks for impostor playlayers, if they won't be fixed in 2.2 then do a good old workaround this->addEventListener([this](keybinds::InvokeBindEvent* event) { auto& vm = GlobedAudioManager::get(); diff --git a/src/ui/menu/main/globed_menu_layer.cpp b/src/ui/menu/main/globed_menu_layer.cpp index b4fdf390..40eb7173 100644 --- a/src/ui/menu/main/globed_menu_layer.cpp +++ b/src/ui/menu/main/globed_menu_layer.cpp @@ -96,9 +96,6 @@ bool GlobedMenuLayer::init() { leftButtonMenu->updateLayout(); - // TODO: menu for connecting to a standalone server directly with an IP and port - // it must call the proper func in GlobedServerManager::addGameServer then try to NM::connectStandalone - util::ui::addBackground(this); auto menu = CCMenu::create(); diff --git a/src/util/formatting.cpp b/src/util/formatting.cpp index 3f81a7d7..ed95a8f8 100644 --- a/src/util/formatting.cpp +++ b/src/util/formatting.cpp @@ -42,8 +42,8 @@ namespace util::formatting { std::string formatErrorMessage(std::string message) { if (message.find("") != std::string::npos) { message = ""; - } else if (message.size() > 64) { - message = message.substr(0, 64) + "..."; + } else if (message.size() > 128) { + message = message.substr(0, 128) + "..."; } return message;