From 7885d4e1c7a49257378134ca9f142e89af75edd9 Mon Sep 17 00:00:00 2001 From: dank_meme01 <42031238+dankmeme01@users.noreply.github.com> Date: Sat, 2 Dec 2023 16:46:34 +0100 Subject: [PATCH] DERIVE MACROS LETS GOOOO --- server/Cargo.toml | 2 +- server/central/Cargo.toml | 3 - server/central/src/allowed_ranges.txt | 3 +- server/central/src/config.rs | 12 +- server/central/src/logger.rs | 57 ------ server/central/src/main.rs | 24 ++- server/central/src/web/routes/auth.rs | 2 +- server/central/src/web/routes/game_server.rs | 4 +- server/game-derives/Cargo.toml | 15 ++ server/game-derives/src/lib.rs | 190 ++++++++++++++++++ server/game/Cargo.toml | 4 +- server/game/src/data/bytebufferext.rs | 131 +----------- server/game/src/data/mod.rs | 2 + .../src/data/packets/client/connection.rs | 76 +++---- server/game/src/data/packets/client/game.rs | 131 +++++------- server/game/src/data/packets/mod.rs | 70 ------- .../src/data/packets/server/connection.rs | 129 ++++-------- server/game/src/data/packets/server/game.rs | 74 +++---- server/game/src/data/types/audio_frame.rs | 14 +- server/game/src/data/types/cocos.rs | 53 +---- server/game/src/data/types/common.rs | 189 +++++++++++++++++ server/game/src/data/types/crypto.rs | 23 --- server/game/src/data/types/gd.rs | 136 +------------ server/game/src/data/types/mod.rs | 4 +- server/game/src/main.rs | 70 ++++--- server/game/src/server.rs | 22 +- server/game/src/server_thread/error.rs | 2 + .../src/server_thread/handlers/connection.rs | 15 +- .../game/src/server_thread/handlers/game.rs | 3 +- server/game/src/server_thread/handlers/mod.rs | 8 +- server/game/src/server_thread/mod.rs | 27 ++- server/game/src/util/mod.rs | 2 - server/game/src/util/rate_limiter.rs | 29 ++- server/shared/Cargo.toml | 3 + server/shared/src/lib.rs | 9 +- .../{game/src/util => shared/src}/logger.rs | 27 ++- src/data/packets/server/connection.hpp | 6 +- src/managers/server_manager.hpp | 8 +- src/managers/settings.cpp | 2 +- src/managers/settings.hpp | 6 +- src/net/network_manager.cpp | 1 + src/net/network_manager.hpp | 3 + src/ui/hooks/play_layer.hpp | 44 ++-- src/util/time.hpp | 10 + 44 files changed, 786 insertions(+), 859 deletions(-) delete mode 100644 server/central/src/logger.rs create mode 100644 server/game-derives/Cargo.toml create mode 100644 server/game-derives/src/lib.rs create mode 100644 server/game/src/data/types/common.rs delete mode 100644 server/game/src/data/types/crypto.rs rename server/{game/src/util => shared/src}/logger.rs (59%) diff --git a/server/Cargo.toml b/server/Cargo.toml index 318e0695..110a4d9c 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["central", "game", "shared"] +members = ["central", "game", "game-derives", "shared"] resolver = "2" [profile.release] diff --git a/server/central/Cargo.toml b/server/central/Cargo.toml index 51f06f10..e1f82bf0 100644 --- a/server/central/Cargo.toml +++ b/server/central/Cargo.toml @@ -13,17 +13,14 @@ async-rate-limit = "0.0.3" async-watcher = "0.2.0" base64 = "0.21.5" blake2 = "0.10.6" -colored = "2.0.4" digest = "0.10.7" hmac = "0.12.1" -log = { version = "0.4.20" } rand = "0.8.5" reqwest = "0.11.22" roa = { version = "0.6.1", features = ["router"] } serde = { version = "1.0.192", features = ["serde_derive"] } serde_json = "1.0.108" sha2 = "0.10.8" -time = { version = "0.3.30", features = ["formatting"] } tokio = { version = "1.34.0", features = ["full"] } totp-rs = "5.4.0" iprange = "0.6.7" diff --git a/server/central/src/allowed_ranges.txt b/server/central/src/allowed_ranges.txt index f7910bed..03e9c812 100644 --- a/server/central/src/allowed_ranges.txt +++ b/server/central/src/allowed_ranges.txt @@ -3,7 +3,8 @@ # that are not originated from one of these IP ranges. # In debug or with the "cloudflare_protection" option disabled this file has no impact on anything, no IP addresses will be blocked. # -# Use hashtags for comments and separate ranges by newlines. Start with 'v4' or 'v6' depending if it's IPv4 or IPv6 +# Use hashtags for comments and separate ranges by newlines. Start with 'v4' or 'v6' depending if it's IPv4 or IPv6. +# NOTE: these are included in the executable at compile time. If you want to change anything here, you have to recompile the server. # List of cloudflare IP ranges (from https://www.cloudflare.com/ips/): # Updated 2023-11-17 diff --git a/server/central/src/config.rs b/server/central/src/config.rs index fe7e6672..b71c935e 100644 --- a/server/central/src/config.rs +++ b/server/central/src/config.rs @@ -51,6 +51,10 @@ fn default_userlist() -> HashSet { HashSet::new() } +fn default_tps() -> u32 { + 30 +} + fn default_secret_key() -> String { let rand_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -121,6 +125,10 @@ pub struct ServerConfig { #[serde(default = "default_userlist")] pub no_chat_list: HashSet, + // game stuff + #[serde(default = "default_tps")] + pub tps: u32, + // security #[serde(default = "default_use_gd_api")] pub use_gd_api: bool, @@ -155,7 +163,8 @@ impl ServerConfig { let writer = OpenOptions::new().write(true).create(true).open(dest)?; // i hate 2 spaces i hate 2 spaces i hate 2 spaces - let mut serializer = Serializer::with_formatter(writer, PrettyFormatter::with_indent(b" ")); + let formatter = PrettyFormatter::with_indent(b" "); + let mut serializer = Serializer::with_formatter(writer, formatter); self.serialize(&mut serializer)?; Ok(()) @@ -181,6 +190,7 @@ impl ServerConfig { 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(), diff --git a/server/central/src/logger.rs b/server/central/src/logger.rs deleted file mode 100644 index ca35b051..00000000 --- a/server/central/src/logger.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::{sync::OnceLock, time::SystemTime}; - -use colored::Colorize; -use log::Level; -use time::{format_description, OffsetDateTime}; - -pub struct Logger { - format_desc: Vec>, -} - -const TIME_FORMAT: &str = "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]"; - -impl Logger { - pub fn instance() -> &'static Self { - static INSTANCE: OnceLock = OnceLock::new(); - INSTANCE.get_or_init(|| Logger { - format_desc: format_description::parse(TIME_FORMAT).unwrap(), - }) - } -} - -impl log::Log for Logger { - fn enabled(&self, metadata: &log::Metadata) -> bool { - if metadata.target().starts_with("globed_central_server") { - true - } else { - metadata.level() <= Level::Warn - } - } - - fn log(&self, record: &log::Record) { - if !self.enabled(record.metadata()) { - return; - } - - let now: OffsetDateTime = SystemTime::now().into(); - let formatted_time = now.format(&self.format_desc).unwrap(); - - let (level, args) = match record.level() { - Level::Error => ( - record.level().to_string().bright_red(), - record.args().to_string().bright_red(), - ), - Level::Warn => ( - record.level().to_string().bright_yellow(), - record.args().to_string().bright_yellow(), - ), - Level::Info => (record.level().to_string().cyan(), record.args().to_string().cyan()), - Level::Debug => (record.level().to_string().normal(), record.args().to_string().normal()), - Level::Trace => (record.level().to_string().black(), record.args().to_string().black()), - }; - - println!("[{formatted_time}] [{level}] - {args}"); - } - - fn flush(&self) {} -} diff --git a/server/central/src/main.rs b/server/central/src/main.rs index 5186aca1..20ffab84 100644 --- a/server/central/src/main.rs +++ b/server/central/src/main.rs @@ -9,29 +9,32 @@ use std::{error::Error, path::PathBuf, sync::Arc, time::Duration}; use async_watcher::{notify::RecursiveMode, AsyncDebouncer}; use config::ServerConfig; -use log::{error, info, warn, LevelFilter}; -use logger::Logger; +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; -pub mod logger; pub mod state; pub mod web; +fn abort_misconfig() -> ! { + error!("aborting launch due to misconfiguration."); + std::process::exit(1); +} + #[tokio::main] async fn main() -> Result<(), Box> { - log::set_logger(Logger::instance()).unwrap(); + log::set_logger(Logger::instance("globed_central_server")).unwrap(); if std::env::var("GLOBED_LESS_LOG").unwrap_or("0".to_string()) == "1" { - log::set_max_level(LevelFilter::Warn); + log::set_max_level(LogLevelFilter::Warn); } else { log::set_max_level(if cfg!(debug_assertions) { - LevelFilter::Trace + LogLevelFilter::Trace } else { - LevelFilter::Info + LogLevelFilter::Info }); } @@ -48,9 +51,10 @@ async fn main() -> Result<(), Box> { match ServerConfig::load(&config_path) { Ok(x) => x, Err(err) => { - error!("Failed to open configuration file: {}", err.to_string()); - error!("Please fix the mistakes in the file or delete it for a new template to be created."); - panic!("aborting due to broken config"); + error!("failed to open/parse configuration file: {err}"); + warn!("hint: if you don't have anything important there, delete the file for a new template to be created."); + warn!("hint: the faulty configuration resides at: {config_path:?}"); + abort_misconfig(); } } } else { diff --git a/server/central/src/web/routes/auth.rs b/server/central/src/web/routes/auth.rs index 1aa382c8..7967fb2f 100644 --- a/server/central/src/web/routes/auth.rs +++ b/server/central/src/web/routes/auth.rs @@ -7,7 +7,7 @@ use std::{ use anyhow::anyhow; use async_rate_limit::limiters::VariableCostRateLimiter; use base64::{engine::general_purpose as b64e, Engine as _}; -use log::{debug, info, warn}; +use globed_shared::logger::{debug, info, log, warn}; use rand::{distributions::Alphanumeric, Rng}; use roa::{http::StatusCode, preload::PowerBody, query::Query, throw, Context}; diff --git a/server/central/src/web/routes/game_server.rs b/server/central/src/web/routes/game_server.rs index 37080905..7b121ac3 100644 --- a/server/central/src/web/routes/game_server.rs +++ b/server/central/src/web/routes/game_server.rs @@ -1,5 +1,4 @@ -use globed_shared::{GameServerBootData, PROTOCOL_VERSION}; -use log::debug; +use globed_shared::{logger::debug, GameServerBootData, PROTOCOL_VERSION}; use reqwest::StatusCode; use roa::{preload::PowerBody, query::Query, throw, Context}; @@ -52,6 +51,7 @@ pub async fn boot(context: &mut Context) -> roa::Result { protocol: PROTOCOL_VERSION, no_chat: config.no_chat_list.clone(), special_users: config.special_users.clone(), + tps: config.tps, }; debug!( diff --git a/server/game-derives/Cargo.toml b/server/game-derives/Cargo.toml new file mode 100644 index 00000000..b17f30fd --- /dev/null +++ b/server/game-derives/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "globed-derives" +version = "1.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +darling = "0.20.3" +proc-macro2 = "1.0.70" +quote = "1.0.33" +syn = { version = "2.0.39", features = ["full"] } diff --git a/server/game-derives/src/lib.rs b/server/game-derives/src/lib.rs new file mode 100644 index 00000000..083c3280 --- /dev/null +++ b/server/game-derives/src/lib.rs @@ -0,0 +1,190 @@ +/* +* proc macros for Encodable, Decodable, EncodableWithKnownSize, and Packet traits. +* Using the first three is as simple as `#[derive(Encodable, ...)]`, Packet is a bit more complex: +* #[derive(Packet)] +* #[packet(id = 10000, encrypted = false)] +* pub struct MyPacket {} +*/ + +#![allow(clippy::missing_panics_doc)] + +use darling::FromDeriveInput; +use proc_macro::{self, TokenStream}; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput}; + +/* Encodable, EncodableWithKnownSize, and Decodable derive macros */ + +#[proc_macro_derive(Encodable)] +pub fn derive_encodable(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let struct_name = &input.ident; + + let gen = match &input.data { + Data::Struct(data) => { + let encode_fields: Vec<_> = data + .fields + .iter() + .map(|field| { + let ident = field.ident.as_ref().unwrap(); + quote! { + buf.write_value(&self.#ident); + } + }) + .collect(); + + quote! { + impl Encodable for #struct_name { + fn encode(&self, buf: &mut bytebuffer::ByteBuffer) { + #(#encode_fields)* + } + + fn encode_fast(&self, buf: &mut crate::data::FastByteBuffer) { + #(#encode_fields)* + } + } + } + } + _ => { + return TokenStream::from(quote! { + compile_error!("Encodable can only be derived for structs"); + }) + } + }; + + gen.into() +} + +#[proc_macro_derive(EncodableWithKnownSize)] +pub fn derive_known_size(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let struct_name = &input.ident; + + let encoded_size = match &input.data { + Data::Struct(data) if data.fields.is_empty() => { + // If the struct has no fields, encoded size is 0 + quote! { 0 } + } + Data::Struct(data) => { + let field_types: Vec<_> = data.fields.iter().map(|field| &field.ty).collect(); + + quote! { + size_of_types!(#(#field_types),*) + } + } + _ => { + return TokenStream::from(quote! { + compile_error!("EncodableWithKnownSize can only be derived for structs"); + }); + } + }; + + let gen = quote! { + impl EncodableWithKnownSize for #struct_name { + const ENCODED_SIZE: usize = #encoded_size; + } + }; + + // Return the generated implementation as a TokenStream + gen.into() +} + +#[proc_macro_derive(Decodable)] +pub fn derive_decodable(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let struct_name = &input.ident; + + let gen = match &input.data { + Data::Struct(data) => { + let field_names = data.fields.iter().map(|field| field.ident.as_ref().unwrap()); + + let decode_fields = field_names + .clone() + .map(|ident| { + quote! { + let #ident = buf.read()?; + } + }) + .collect::>(); + + let field_names: Vec<_> = field_names.collect(); + + quote! { + impl Decodable for #struct_name { + fn decode(buf: &mut bytebuffer::ByteBuffer) -> anyhow::Result { + #(#decode_fields)* + Ok(Self { + #( + #field_names, + )* + }) + } + + fn decode_from_reader(buf: &mut bytebuffer::ByteReader) -> anyhow::Result { + #(#decode_fields)* + Ok(Self { + #( + #field_names, + )* + }) + } + } + } + } + _ => { + return TokenStream::from(quote! { + compile_error!("Decodable can only be derived for structs"); + }) + } + }; + + gen.into() +} + +/* #[packet()] implementation */ + +#[derive(FromDeriveInput)] +#[darling(attributes(packet))] +struct PacketAttributes { + id: u16, + encrypted: bool, +} + +#[proc_macro_derive(Packet, attributes(packet))] +pub fn packet(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input); + let opts = PacketAttributes::from_derive_input(&input).expect("wrong value passed into #[packet] derive macro"); + let DeriveInput { ident, .. } = input; + + let id = opts.id; + let enc = opts.encrypted; + + let output = quote! { + impl PacketMetadata for #ident { + const PACKET_ID: crate::data::packets::PacketId = #id; + const ENCRYPTED: bool = #enc; + const NAME: &'static str = stringify!(#ident); + } + + impl Packet for #ident { + fn get_packet_id(&self) -> crate::data::packets::PacketId { + #id + } + + fn get_encrypted(&self) -> bool { + #enc + } + } + + impl #ident { + pub const fn header() -> crate::data::packets::PacketHeader { + crate::data::packets::PacketHeader::from_packet::() + } + } + }; + + output.into() +} diff --git a/server/game/Cargo.toml b/server/game/Cargo.toml index 9dd4c72d..c5465057 100644 --- a/server/game/Cargo.toml +++ b/server/game/Cargo.toml @@ -7,19 +7,17 @@ edition = "2021" [dependencies] globed-shared = { path = "../shared" } +globed-derives = { path = "../game-derives" } alloca = "0.4.0" anyhow = "1.0.75" array-init = "2.1.0" bytebuffer = "2.2.0" -colored = "2.0.4" crypto_box = { version = "0.9.1", features = ["std", "chacha20"] } -log = { version = "0.4.20" } num_enum = "0.7.1" parking_lot = "0.12.1" reqwest = "0.11.22" rustc-hash = "1.1.0" serde = { version = "1.0.192", features = ["serde_derive"] } serde_json = "1.0.108" -time = { version = "0.3.30", features = ["formatting"] } tokio = { version = "1.34.0", features = ["full"] } diff --git a/server/game/src/data/bytebufferext.rs b/server/game/src/data/bytebufferext.rs index 912d7611..f8772acd 100644 --- a/server/game/src/data/bytebufferext.rs +++ b/server/game/src/data/bytebufferext.rs @@ -6,18 +6,14 @@ use anyhow::{anyhow, Result}; use bytebuffer::{ByteBuffer, ByteReader}; pub trait Encodable { - /// write `Self` into the given buffer fn encode(&self, buf: &mut ByteBuffer); - /// write `Self` into the given buffer, except blazingly fast this time fn encode_fast(&self, buf: &mut FastByteBuffer); } pub trait Decodable { - /// read `Self` from the given `ByteBuffer` fn decode(buf: &mut ByteBuffer) -> Result where Self: Sized; - /// read `Self` from the given `ByteReader` fn decode_from_reader(buf: &mut ByteReader) -> Result where Self: Sized; @@ -64,27 +60,6 @@ macro_rules! decode_impl { }; } -/// Implements `Decodable::decode` and `Decodable::decode_from_reader` to panic when called -macro_rules! decode_unimpl { - ($typ:ty) => { - impl crate::data::bytebufferext::Decodable for $typ { - fn decode(_: &mut bytebuffer::ByteBuffer) -> anyhow::Result { - panic!( - "Tried to call {}::decode when Decodable was not implemented for this type", - stringify!($typ) - ); - } - - fn decode_from_reader(_: &mut bytebuffer::ByteReader) -> anyhow::Result { - panic!( - "Tried to call {}::decode_from_reader when Decodable was not implemented for this type", - stringify!($typ) - ); - } - } - }; -} - /// Simple and compact way of implementing `Encodable::encode` and `Encodable::encode_fast`. /// /// Example usage: @@ -111,27 +86,6 @@ macro_rules! encode_impl { }; } -/// Implements `Encodable::encode` and `Encodable::encode_fast` to panic when called -macro_rules! encode_unimpl { - ($typ:ty) => { - impl crate::data::bytebufferext::Encodable for $typ { - fn encode(&self, _: &mut bytebuffer::ByteBuffer) { - panic!( - "Tried to call {}::encode when Encodable was not implemented for this type", - stringify!($typ) - ); - } - - fn encode_fast(&self, _: &mut crate::data::bytebufferext::FastByteBuffer) { - panic!( - "Tried to call {}::encode_fast when Encodable was not implemented for this type", - stringify!($typ) - ); - } - } - }; -} - /// Simple and compact way of implementing `EncodableWithKnownSize`. /// /// Example usage: @@ -175,22 +129,12 @@ macro_rules! size_of_types { } pub(crate) use decode_impl; -pub(crate) use decode_unimpl; pub(crate) use encode_impl; -pub(crate) use encode_unimpl; pub(crate) use size_calc_impl; pub(crate) use size_of_primitives; pub(crate) use size_of_types; -/* ByteBuffer extensions -* -* With great power comes great responsibility. -* Just because you can use .write(T) and .read() for every single type, -* even primitives (as they impl Encodable/Decodable), doesn't mean you should. -* -* Notable exception is Option, `write` will accept &Option while `write_optional_value` will accept Option<&T> -* so feel free to use whichever method suits you more when dealing with those. -*/ +/* ByteBuffer extensions */ pub trait ByteBufferExt { fn with_capacity(capacity: usize) -> Self; @@ -513,76 +457,3 @@ impl ByteBufferExtRead for ByteBuffer { impl<'a> ByteBufferExtRead for ByteReader<'a> { impl_extread!(decode_from_reader); } - -/* Encodable/Decodable implementations for common types */ - -macro_rules! impl_primitive { - ($typ:ty,$read:ident,$write:ident) => { - encode_impl!($typ, buf, self, { - buf.$write(*self); - }); - - decode_impl!($typ, buf, { buf.$read().map_err(|e| e.into()) }); - - size_calc_impl!($typ, size_of_primitives!(Self)); - }; -} - -impl_primitive!(bool, read_bool, write_bool); -impl_primitive!(u8, read_u8, write_u8); -impl_primitive!(u16, read_u16, write_u16); -impl_primitive!(u32, read_u32, write_u32); -impl_primitive!(u64, read_u64, write_u64); -impl_primitive!(i8, read_i8, write_i8); -impl_primitive!(i16, read_i16, write_i16); -impl_primitive!(i32, read_i32, write_i32); -impl_primitive!(i64, read_i64, write_i64); -impl_primitive!(f32, read_f32, write_f32); -impl_primitive!(f64, read_f64, write_f64); - -encode_impl!(Vec, buf, self, buf.write_byte_array(self)); -decode_impl!(Vec, buf, buf.read_byte_array()); - -encode_impl!(String, buf, self, buf.write_string(self)); -decode_impl!(String, buf, Ok(buf.read_string()?)); - -encode_impl!(&str, buf, self, buf.write_string(self)); - -impl Encodable for Option -where - T: Encodable, -{ - fn encode(&self, buf: &mut ByteBuffer) { - buf.write_optional_value(self.as_ref()); - } - - fn encode_fast(&self, buf: &mut FastByteBuffer) { - buf.write_optional_value(self.as_ref()); - } -} - -impl EncodableWithKnownSize for Option -where - T: EncodableWithKnownSize, -{ - const ENCODED_SIZE: usize = size_of_types!(bool, T); -} - -impl Decodable for Option -where - T: Decodable, -{ - fn decode(buf: &mut ByteBuffer) -> Result - where - Self: Sized, - { - buf.read_optional_value() - } - - fn decode_from_reader(buf: &mut ByteReader) -> Result - where - Self: Sized, - { - buf.read_optional_value() - } -} diff --git a/server/game/src/data/mod.rs b/server/game/src/data/mod.rs index e5b12629..711c5ed0 100644 --- a/server/game/src/data/mod.rs +++ b/server/game/src/data/mod.rs @@ -2,6 +2,8 @@ pub mod bytebufferext; pub mod packets; pub mod types; +/* re-export all important types, packets and macros */ pub use bytebufferext::*; +pub use globed_derives::*; pub use packets::*; pub use types::*; diff --git a/server/game/src/data/packets/client/connection.rs b/server/game/src/data/packets/client/connection.rs index 87c5fe79..d9a75272 100644 --- a/server/game/src/data/packets/client/connection.rs +++ b/server/game/src/data/packets/client/connection.rs @@ -1,53 +1,31 @@ use crate::data::*; -/* PingPacket - 10000 */ - -packet!(PingPacket, 10000, false, { - id: u32, -}); - -encode_unimpl!(PingPacket); - -decode_impl!(PingPacket, buf, Ok(Self { id: buf.read_u32()? })); - -/* CryptoHandshakeStartPacket - 10001 */ - -packet!(CryptoHandshakeStartPacket, 10001, false, { - protocol: u16, - key: CryptoPublicKey, -}); - -encode_unimpl!(CryptoHandshakeStartPacket); - -decode_impl!(CryptoHandshakeStartPacket, buf, { - let protocol = buf.read_u16()?; - let key = buf.read()?; - Ok(Self { protocol, key }) -}); - -/* KeepalivePacket - 10002 */ - -empty_client_packet!(KeepalivePacket, 10002); - -/* LoginPacket - 10003 */ +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 10000, encrypted = false)] +pub struct PingPacket { + pub id: u32, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 10001, encrypted = false)] +pub struct CryptoHandshakeStartPacket { + pub protocol: u16, + pub key: PublicKey, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 10002, encrypted = false)] +pub struct KeepalivePacket {} pub const MAX_TOKEN_SIZE: usize = 164; - -packet!(LoginPacket, 10003, true, { - account_id: i32, - name: FastString, - token: FastString, -}); - -encode_unimpl!(LoginPacket); - -decode_impl!(LoginPacket, buf, { - let account_id = buf.read_i32()?; - let name = buf.read()?; - let token = buf.read()?; - Ok(Self { account_id, name, token }) -}); - -/* DisconnectPacket - 10004 */ - -empty_client_packet!(DisconnectPacket, 10004); +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 10003, encrypted = true)] +pub struct LoginPacket { + pub account_id: i32, + pub name: FastString, + pub token: FastString, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 10004, encrypted = false)] +pub struct DisconnectPacket {} diff --git a/server/game/src/data/packets/client/game.rs b/server/game/src/data/packets/client/game.rs index 128023fb..0eedcb51 100644 --- a/server/game/src/data/packets/client/game.rs +++ b/server/game/src/data/packets/client/game.rs @@ -1,87 +1,52 @@ use crate::data::*; -/* SyncIconsPacket - 11000 */ - -packet!(SyncIconsPacket, 11000, false, { - icons: PlayerIconData, -}); - -encode_unimpl!(SyncIconsPacket); - -decode_impl!(SyncIconsPacket, buf, Ok(Self { icons: buf.read()? })); - -/* RequestProfilesPacket - 11001 */ - -packet!(RequestProfilesPacket, 11001, false, { - ids: [i32; MAX_PROFILES_REQUESTED] -}); - -encode_unimpl!(RequestProfilesPacket); - -decode_impl!(RequestProfilesPacket, buf, { - Ok(Self { - ids: buf.read_value_array()?, - }) -}); - -/* LevelJoinPacket - 11002 */ - -packet!(LevelJoinPacket, 11002, false, { - level_id: i32 -}); - -encode_unimpl!(LevelJoinPacket); - -decode_impl!(LevelJoinPacket, buf, { - Ok(Self { - level_id: buf.read_i32()?, - }) -}); - -/* LevelLeavePacket - 11003 */ - -empty_client_packet!(LevelLeavePacket, 11003); - -/* PlayerDataPacket - 11004 */ - -packet!(PlayerDataPacket, 11004, false, { - data: PlayerData -}); - -encode_unimpl!(PlayerDataPacket); - -decode_impl!(PlayerDataPacket, buf, Ok(Self { data: buf.read()? })); - -/* RequestPlayerListPacket - 11005 */ - -empty_client_packet!(RequestPlayerListPacket, 11005); - -/* SyncPlayerMetadataPacket - 11006 */ - -packet!(SyncPlayerMetadataPacket, 11006, false, { - data: PlayerMetadata -}); - -encode_unimpl!(SyncPlayerMetadataPacket); - -decode_impl!(SyncPlayerMetadataPacket, buf, Ok(Self { data: buf.read()? })); - -/* VoicePacket - 11010 */ - -packet!(VoicePacket, 11010, true, { - data: FastEncodedAudioFrame, -}); - -encode_unimpl!(VoicePacket); - -decode_impl!(VoicePacket, buf, Ok(Self { data: buf.read()? })); +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 11000, encrypted = false)] +pub struct SyncIconsPacket { + pub icons: PlayerIconData, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 11001, encrypted = false)] +pub struct RequestProfilesPacket { + pub ids: [i32; MAX_PROFILES_REQUESTED], +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 11002, encrypted = false)] +pub struct LevelJoinPacket { + pub level_id: i32, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 11003, encrypted = false)] +pub struct LevelLeavePacket {} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 11004, encrypted = false)] +pub struct PlayerDataPacket { + pub data: PlayerData, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 11005, encrypted = false)] +pub struct RequestPlayerListPacket {} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 11006, encrypted = false)] +pub struct SyncPlayerMetadataPacket { + pub data: PlayerMetadata, +} + +#[derive(Packet, Encodable, Decodable)] +#[packet(id = 11010, encrypted = true)] +pub struct VoicePacket { + pub data: FastEncodedAudioFrame, +} /* ChatMessagePacket - 11011 */ - -packet!(ChatMessagePacket, 11011, true, { - message: FastString, -}); - -encode_unimpl!(ChatMessagePacket); - -decode_impl!(ChatMessagePacket, buf, Ok(Self { message: buf.read()? })); +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 11011, encrypted = true)] +pub struct ChatMessagePacket { + pub message: FastString, +} diff --git a/server/game/src/data/packets/mod.rs b/server/game/src/data/packets/mod.rs index 41867872..ff3f99a6 100644 --- a/server/game/src/data/packets/mod.rs +++ b/server/game/src/data/packets/mod.rs @@ -8,76 +8,6 @@ use crate::data::bytebufferext::*; type PacketId = u16; -/* -* for creating a new packet, the basic structure is either: -* 1. packet!(PacketName, id, enc, { struct body }) -* 2. struct PacketName {} -* packet!(PacketName, id, enc) -* -* followed by packet_encode! and packet_decode! or their _unimpl versions -*/ - -macro_rules! packet { - ($packet_type:ty, $packet_id:expr, $encrypted:expr) => { - impl crate::data::packets::Packet for $packet_type { - fn get_packet_id(&self) -> crate::data::packets::PacketId { - $packet_id - } - - fn get_encrypted(&self) -> bool { - $encrypted - } - } - - impl crate::data::packets::PacketMetadata for $packet_type { - const PACKET_ID: crate::data::packets::PacketId = $packet_id; - const ENCRYPTED: bool = $encrypted; - const NAME: &'static str = stringify!($packet_type); - } - - impl $packet_type { - pub const fn header() -> crate::data::packets::PacketHeader { - crate::data::packets::PacketHeader::from_packet::() - } - } - }; - - ($packet_type:ident, $packet_id:expr, $encrypted:expr, { $($field:ident: $field_type:ty),* $(,)? }) => { - #[derive(Clone)] - pub struct $packet_type { - $(pub $field: $field_type),* - } - - packet!($packet_type, $packet_id, $encrypted); - }; -} - -macro_rules! empty_server_packet { - ($packet_type:ident, $packet_id:expr) => { - packet!($packet_type, $packet_id, false, {}); - - encode_impl!($packet_type, _buf, self, {}); - - decode_unimpl!($packet_type); - - size_calc_impl!($packet_type, 0); - }; -} - -macro_rules! empty_client_packet { - ($packet_type:ident, $packet_id:expr) => { - packet!($packet_type, $packet_id, false, {}); - - encode_unimpl!($packet_type); - - decode_impl!($packet_type, _buf, Ok(Self {})); - }; -} - -pub(crate) use empty_client_packet; -pub(crate) use empty_server_packet; -pub(crate) use packet; - pub trait Packet: Encodable + Decodable + Send + Sync + PacketMetadata { fn get_packet_id(&self) -> PacketId; fn get_encrypted(&self) -> bool; diff --git a/server/game/src/data/packets/server/connection.rs b/server/game/src/data/packets/server/connection.rs index 1e69ac6e..11775d98 100644 --- a/server/game/src/data/packets/server/connection.rs +++ b/server/game/src/data/packets/server/connection.rs @@ -1,92 +1,45 @@ use crate::data::*; -/* PingResponsePacket - 20000 */ +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 20000, encrypted = false)] +pub struct PingResponsePacket { + pub id: u32, + pub player_count: u32, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 20001, encrypted = false)] +pub struct CryptoHandshakeResponsePacket { + pub key: PublicKey, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 20002, encrypted = false)] +pub struct KeepaliveResponsePacket { + pub player_count: u32, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 20003, encrypted = false)] +pub struct ServerDisconnectPacket { + pub message: FastString, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 20004, encrypted = false)] +pub struct LoggedInPacket { + pub tps: u32, +} + +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 20005, encrypted = false)] +pub struct LoginFailedPacket { + pub message: FastString, +} -packet!(PingResponsePacket, 20000, false, { - id: u32, - player_count: u32 -}); - -encode_impl!(PingResponsePacket, buf, self, { - buf.write_u32(self.id); - buf.write_u32(self.player_count); -}); - -decode_unimpl!(PingResponsePacket); - -size_calc_impl!(PingResponsePacket, size_of_types!(u32, u32)); - -/* CryptoHandshakeResponsePacket - 20001 */ - -packet!(CryptoHandshakeResponsePacket, 20001, false, { - key: CryptoPublicKey, -}); - -encode_impl!(CryptoHandshakeResponsePacket, buf, self, { - buf.write(&self.key); -}); - -decode_unimpl!(CryptoHandshakeResponsePacket); - -size_calc_impl!(CryptoHandshakeResponsePacket, size_of_types!(CryptoPublicKey)); - -/* KeepaliveResponsePacket - 20002 */ - -packet!(KeepaliveResponsePacket, 20002, false, { - player_count: u32 -}); - -encode_impl!(KeepaliveResponsePacket, buf, self, { - buf.write_u32(self.player_count); -}); - -decode_unimpl!(KeepaliveResponsePacket); - -size_calc_impl!(KeepaliveResponsePacket, size_of_types!(u32)); - -/* ServerDisconnectPacket - 20003 */ - -packet!(ServerDisconnectPacket, 20003, false, { - message: FastString -}); - -encode_impl!(ServerDisconnectPacket, buf, self, { - buf.write(&self.message); -}); - -decode_unimpl!(ServerDisconnectPacket); - -size_calc_impl!(ServerDisconnectPacket, size_of_types!(FastString)); - -/* LoggedInPacket - 20004 */ - -empty_server_packet!(LoggedInPacket, 20004); - -/* LoginFailedPacket - 20005 */ - -packet!(LoginFailedPacket, 20005, false, { - message: FastString -}); - -encode_impl!(LoginFailedPacket, buf, self, { - buf.write(&self.message); -}); - -decode_unimpl!(LoginFailedPacket); - -size_calc_impl!(LoginFailedPacket, size_of_types!(FastString)); - -/* ServerNoticePacket - 20006 */ // used to communicate a simple message to the user - -packet!(ServerNoticePacket, 20006, false, { - message: FastString -}); - -encode_impl!(ServerNoticePacket, buf, self, { - buf.write(&self.message); -}); - -decode_unimpl!(ServerNoticePacket); - -size_calc_impl!(ServerNoticePacket, size_of_types!(FastString)); +#[derive(Packet, Encodable, Decodable, EncodableWithKnownSize)] +#[packet(id = 20006, encrypted = false)] +pub struct ServerNoticePacket { + pub message: FastString, +} diff --git a/server/game/src/data/packets/server/game.rs b/server/game/src/data/packets/server/game.rs index 77710973..db76cf92 100644 --- a/server/game/src/data/packets/server/game.rs +++ b/server/game/src/data/packets/server/game.rs @@ -1,16 +1,10 @@ use crate::data::*; -/* PlayerProfilesPacket - 21000 */ - -packet!(PlayerProfilesPacket, 21000, false, { - profiles: Vec, -}); - -encode_impl!(PlayerProfilesPacket, buf, self, { - buf.write_value_vec(&self.profiles); -}); - -decode_unimpl!(PlayerProfilesPacket); +#[derive(Packet, Encodable, Decodable)] +#[packet(id = 21000, encrypted = false)] +pub struct PlayerProfilesPacket { + pub message: Vec, +} /* LevelDataPacket - 21001 * PlayerListPacket - 21002 @@ -20,36 +14,28 @@ decode_unimpl!(PlayerProfilesPacket); * They are encoded inline in the packet handlers in server_thread/handlers/game.rs. */ -empty_server_packet!(LevelDataPacket, 21001); -empty_server_packet!(PlayerListPacket, 21002); -empty_server_packet!(PlayerMetadataPacket, 21003); - -/* VoiceBroadcastPacket - 21010 */ - -packet!(VoiceBroadcastPacket, 21010, true, { - player_id: i32, - data: FastEncodedAudioFrame, -}); - -encode_impl!(VoiceBroadcastPacket, buf, self, { - buf.write_i32(self.player_id); - buf.write(&self.data); -}); - -decode_unimpl!(VoiceBroadcastPacket); - -/* ChatMessageBroadcastPacket - 21011 */ - -packet!(ChatMessageBroadcastPacket, 21011, true, { - player_id: i32, - message: FastString, -}); - -encode_impl!(ChatMessageBroadcastPacket, buf, self, { - buf.write_i32(self.player_id); - buf.write(&self.message); -}); - -decode_unimpl!(ChatMessageBroadcastPacket); - -size_calc_impl!(ChatMessageBroadcastPacket, size_of_types!(i32, FastString)); +#[derive(Packet, Encodable, Decodable)] +#[packet(id = 21001, encrypted = false)] +pub struct LevelDataPacket {} + +#[derive(Packet, Encodable, Decodable)] +#[packet(id = 21002, encrypted = false)] +pub struct PlayerListPacket {} + +#[derive(Packet, Encodable, Decodable)] +#[packet(id = 21003, encrypted = false)] +pub struct PlayerMetadataPacket {} + +#[derive(Packet, Encodable, Decodable)] +#[packet(id = 21010, encrypted = true)] +pub struct VoiceBroadcastPacket { + pub player_id: i32, + pub data: FastEncodedAudioFrame, +} + +#[derive(Clone, Packet, Encodable, EncodableWithKnownSize, Decodable)] +#[packet(id = 21011, encrypted = true)] +pub struct ChatMessageBroadcastPacket { + pub player_id: i32, + pub message: FastString, +} diff --git a/server/game/src/data/types/audio_frame.rs b/server/game/src/data/types/audio_frame.rs index 7b57c1b0..65dbced0 100644 --- a/server/game/src/data/types/audio_frame.rs +++ b/server/game/src/data/types/audio_frame.rs @@ -1,24 +1,14 @@ -use crate::data::bytebufferext::*; +use crate::data::*; const VOICE_MAX_FRAMES_IN_AUDIO_FRAME: usize = 10; type EncodedOpusData = Vec; -#[derive(Clone)] +#[derive(Clone, Encodable)] pub struct EncodedAudioFrame { pub opus_frames: [Option; VOICE_MAX_FRAMES_IN_AUDIO_FRAME], } -encode_impl!(EncodedAudioFrame, buf, self, { - buf.write_value_array(&self.opus_frames); -}); - -decode_impl!(EncodedAudioFrame, buf, { - Ok(Self { - opus_frames: buf.read_value_array()?, - }) -}); - /// `FastEncodedAudioFrame` requires just one heap allocation as opposed to 10. #[derive(Clone)] pub struct FastEncodedAudioFrame { diff --git a/server/game/src/data/types/cocos.rs b/server/game/src/data/types/cocos.rs index adbffba7..f3197986 100644 --- a/server/game/src/data/types/cocos.rs +++ b/server/game/src/data/types/cocos.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, num::ParseIntError, str::FromStr}; -use crate::data::bytebufferext::*; +use crate::data::*; pub enum ColorParseError { InvalidLength, @@ -26,28 +26,13 @@ impl From for ColorParseError { } } -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Default, Encodable, Decodable, EncodableWithKnownSize)] pub struct Color3B { pub r: u8, pub g: u8, pub b: u8, } -encode_impl!(Color3B, buf, self, { - buf.write_u8(self.r); - buf.write_u8(self.g); - buf.write_u8(self.b); -}); - -decode_impl!(Color3B, buf, { - let r = buf.read_u8()?; - let g = buf.read_u8()?; - let b = buf.read_u8()?; - Ok(Self { r, g, b }) -}); - -size_calc_impl!(Color3B, size_of_types!(u8, u8, u8)); - impl FromStr for Color3B { type Err = ColorParseError; @@ -68,7 +53,7 @@ impl FromStr for Color3B { } } -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] pub struct Color4B { pub r: u8, pub g: u8, @@ -76,23 +61,6 @@ pub struct Color4B { pub a: u8, } -encode_impl!(Color4B, buf, self, { - buf.write_u8(self.r); - buf.write_u8(self.g); - buf.write_u8(self.b); - buf.write_u8(self.a); -}); - -decode_impl!(Color4B, buf, { - let r = buf.read_u8()?; - let g = buf.read_u8()?; - let b = buf.read_u8()?; - let a = buf.read_u8()?; - Ok(Self { r, g, b, a }) -}); - -size_calc_impl!(Color4B, size_of_types!(u8, u8, u8, u8)); - impl FromStr for Color4B { type Err = ColorParseError; @@ -118,21 +86,8 @@ impl FromStr for Color4B { } } -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] pub struct Point { pub x: f32, pub y: f32, } - -encode_impl!(Point, buf, self, { - buf.write_f32(self.x); - buf.write_f32(self.y); -}); - -decode_impl!(Point, buf, { - let x = buf.read_f32()?; - let y = buf.read_f32()?; - Ok(Self { x, y }) -}); - -size_calc_impl!(Point, size_of_types!(f32, f32)); diff --git a/server/game/src/data/types/common.rs b/server/game/src/data/types/common.rs new file mode 100644 index 00000000..10768506 --- /dev/null +++ b/server/game/src/data/types/common.rs @@ -0,0 +1,189 @@ +use anyhow::Result; +use std::io::Read; + +use bytebuffer::{ByteBuffer, ByteReader}; +pub use crypto_box::{PublicKey, KEY_SIZE}; + +use crate::data::bytebufferext::*; + +/* Encodable/Decodable implementations for common types */ + +macro_rules! impl_primitive { + ($typ:ty,$read:ident,$write:ident) => { + encode_impl!($typ, buf, self, { + buf.$write(*self); + }); + + decode_impl!($typ, buf, { buf.$read().map_err(|e| e.into()) }); + + size_calc_impl!($typ, size_of_primitives!(Self)); + }; +} + +impl_primitive!(bool, read_bool, write_bool); +impl_primitive!(u8, read_u8, write_u8); +impl_primitive!(u16, read_u16, write_u16); +impl_primitive!(u32, read_u32, write_u32); +impl_primitive!(u64, read_u64, write_u64); +impl_primitive!(i8, read_i8, write_i8); +impl_primitive!(i16, read_i16, write_i16); +impl_primitive!(i32, read_i32, write_i32); +impl_primitive!(i64, read_i64, write_i64); +impl_primitive!(f32, read_f32, write_f32); +impl_primitive!(f64, read_f64, write_f64); + +encode_impl!(String, buf, self, buf.write_string(self)); +decode_impl!(String, buf, Ok(buf.read_string()?)); + +encode_impl!(&str, buf, self, buf.write_string(self)); + +/* Option */ + +impl Encodable for Option +where + T: Encodable, +{ + fn encode(&self, buf: &mut ByteBuffer) { + buf.write_optional_value(self.as_ref()); + } + + fn encode_fast(&self, buf: &mut FastByteBuffer) { + buf.write_optional_value(self.as_ref()); + } +} + +impl EncodableWithKnownSize for Option +where + T: EncodableWithKnownSize, +{ + const ENCODED_SIZE: usize = size_of_types!(bool, T); +} + +impl Decodable for Option +where + T: Decodable, +{ + fn decode(buf: &mut ByteBuffer) -> Result + where + Self: Sized, + { + buf.read_optional_value() + } + + fn decode_from_reader(buf: &mut ByteReader) -> Result + where + Self: Sized, + { + buf.read_optional_value() + } +} + +/* [T; N] */ + +impl Encodable for [T; N] +where + T: Encodable, +{ + fn encode(&self, buf: &mut ByteBuffer) { + buf.write_value_array(self); + } + + fn encode_fast(&self, buf: &mut FastByteBuffer) { + buf.write_value_array(self); + } +} + +impl EncodableWithKnownSize for [T; N] +where + T: EncodableWithKnownSize, +{ + const ENCODED_SIZE: usize = size_of_types!(T) * N; +} + +impl Decodable for [T; N] +where + T: Decodable, +{ + fn decode(buf: &mut ByteBuffer) -> Result + where + Self: Sized, + { + buf.read_value_array() + } + + fn decode_from_reader(buf: &mut ByteReader) -> Result + where + Self: Sized, + { + buf.read_value_array() + } +} + +/* Vec */ + +impl Encodable for Vec +where + T: Encodable, +{ + fn encode(&self, buf: &mut ByteBuffer) { + buf.write_value_vec(self); + } + + fn encode_fast(&self, buf: &mut FastByteBuffer) { + buf.write_value_vec(self); + } +} + +impl Decodable for Vec +where + T: Decodable, +{ + fn decode(buf: &mut ByteBuffer) -> Result + where + Self: Sized, + { + buf.read_value_vec() + } + + fn decode_from_reader(buf: &mut ByteReader) -> Result + where + Self: Sized, + { + buf.read_value_vec() + } +} + +/* crypto_box::PublicKey */ + +impl Encodable for PublicKey { + fn encode(&self, buf: &mut bytebuffer::ByteBuffer) { + buf.write_bytes(self.as_bytes()); + } + + fn encode_fast(&self, buf: &mut FastByteBuffer) { + buf.write_bytes(self.as_bytes()); + } +} + +impl EncodableWithKnownSize for PublicKey { + const ENCODED_SIZE: usize = KEY_SIZE; +} + +impl Decodable for PublicKey { + fn decode(buf: &mut bytebuffer::ByteBuffer) -> anyhow::Result + where + Self: Sized, + { + Self::decode_from_reader(&mut ByteReader::from_bytes(buf.as_bytes())) + } + + fn decode_from_reader(buf: &mut ByteReader) -> anyhow::Result + where + Self: Sized, + { + let mut key = [0u8; KEY_SIZE]; + buf.read_exact(&mut key)?; + + Ok(Self::from_bytes(key)) + } +} diff --git a/server/game/src/data/types/crypto.rs b/server/game/src/data/types/crypto.rs deleted file mode 100644 index eda96a40..00000000 --- a/server/game/src/data/types/crypto.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::io::Read; - -use crypto_box::{PublicKey, KEY_SIZE}; - -use crate::data::bytebufferext::*; - -#[derive(Clone)] -pub struct CryptoPublicKey { - pub pubkey: PublicKey, -} - -encode_impl!(CryptoPublicKey, buf, self, buf.write_bytes(self.pubkey.as_bytes())); - -decode_impl!(CryptoPublicKey, buf, { - let mut key = [0u8; KEY_SIZE]; - buf.read_exact(&mut key)?; - - Ok(Self { - pubkey: PublicKey::from_bytes(key), - }) -}); - -size_calc_impl!(CryptoPublicKey, KEY_SIZE); diff --git a/server/game/src/data/types/gd.rs b/server/game/src/data/types/gd.rs index 88a5e071..1db2be2a 100644 --- a/server/game/src/data/types/gd.rs +++ b/server/game/src/data/types/gd.rs @@ -1,10 +1,10 @@ use globed_shared::SpecialUser; -use crate::data::{bytebufferext::*, FastString}; +use crate::data::*; use super::{Color3B, ColorParseError}; -#[derive(Clone)] +#[derive(Clone, Encodable, EncodableWithKnownSize, Decodable)] pub struct PlayerIconData { pub cube: i16, pub ship: i16, @@ -39,53 +39,6 @@ impl Default for PlayerIconData { } } -encode_impl!(PlayerIconData, buf, self, { - buf.write_i16(self.cube); - buf.write_i16(self.ship); - buf.write_i16(self.ball); - buf.write_i16(self.ufo); - buf.write_i16(self.wave); - buf.write_i16(self.robot); - buf.write_i16(self.spider); - buf.write_i16(self.swing); - buf.write_i16(self.jetpack); - - buf.write_i16(self.death_effect); - buf.write_i16(self.color1); - buf.write_i16(self.color2); -}); - -decode_impl!(PlayerIconData, buf, { - let cube = buf.read_i16()?; - let ship = buf.read_i16()?; - let ball = buf.read_i16()?; - let ufo = buf.read_i16()?; - let wave = buf.read_i16()?; - let robot = buf.read_i16()?; - let spider = buf.read_i16()?; - let swing = buf.read_i16()?; - let jetpack = buf.read_i16()?; - let death_effect = buf.read_i16()?; - let color1 = buf.read_i16()?; - let color2 = buf.read_i16()?; - Ok(Self { - cube, - ship, - ball, - ufo, - wave, - robot, - spider, - swing, - jetpack, - death_effect, - color1, - color2, - }) -}); - -size_calc_impl!(PlayerIconData, size_of_types!(i16) * 12); - impl PlayerIconData { pub fn is_valid(&self) -> bool { // TODO icon ids validation and stuff.. or not? @@ -95,7 +48,7 @@ impl PlayerIconData { /* SpecialUserData */ -#[derive(Clone)] +#[derive(Clone, Encodable, EncodableWithKnownSize, Decodable)] pub struct SpecialUserData { pub name_color: Color3B, } @@ -108,14 +61,6 @@ impl Default for SpecialUserData { } } -encode_impl!(SpecialUserData, buf, self, { - buf.write(&self.name_color); -}); - -decode_unimpl!(SpecialUserData); - -size_calc_impl!(SpecialUserData, size_of_types!(Color3B)); - impl TryFrom for SpecialUserData { type Error = ColorParseError; fn try_from(value: SpecialUser) -> Result { @@ -127,7 +72,7 @@ impl TryFrom for SpecialUserData { /* PlayerAccountData */ -#[derive(Clone, Default)] +#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] pub struct PlayerAccountData { pub account_id: i32, pub name: FastString, @@ -135,20 +80,6 @@ pub struct PlayerAccountData { pub special_user_data: Option, } -encode_impl!(PlayerAccountData, buf, self, { - buf.write_i32(self.account_id); - buf.write(&self.name); - buf.write(&self.icons); - buf.write(&self.special_user_data); -}); - -decode_unimpl!(PlayerAccountData); - -size_calc_impl!( - PlayerAccountData, - size_of_types!(i32, FastString, PlayerIconData, Option) -); - impl PlayerAccountData { pub fn make_preview(&self, level_id: i32) -> PlayerPreviewAccountData { PlayerPreviewAccountData { @@ -164,7 +95,7 @@ impl PlayerAccountData { /* PlayerPreviewAccountData - like PlayerAccountData but more limited, for the total player list */ -#[derive(Clone, Default)] +#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] pub struct PlayerPreviewAccountData { pub account_id: i32, pub name: FastString, @@ -174,80 +105,31 @@ pub struct PlayerPreviewAccountData { pub level_id: i32, } -encode_impl!(PlayerPreviewAccountData, buf, self, { - buf.write_i32(self.account_id); - buf.write(&self.name); - buf.write_i16(self.cube); - buf.write_i16(self.color1); - buf.write_i16(self.color2); - buf.write_i32(self.level_id); -}); - -decode_unimpl!(PlayerPreviewAccountData); - -size_calc_impl!( - PlayerPreviewAccountData, - size_of_types!(i32, FastString, i16, i16, i16) -); - /* PlayerData (data in a level) */ -#[derive(Clone, Default)] +#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] pub struct PlayerData {} -encode_impl!(PlayerData, _buf, self, {}); - -decode_impl!(PlayerData, _buf, Ok(Self {})); - -size_calc_impl!(PlayerData, 0); - /* AssociatedPlayerData */ -#[derive(Clone, Default)] +#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] pub struct AssociatedPlayerData { pub account_id: i32, pub data: PlayerData, } -encode_impl!(AssociatedPlayerData, buf, self, { - buf.write_i32(self.account_id); - buf.write(&self.data); -}); - -size_calc_impl!(AssociatedPlayerData, size_of_types!(i32, PlayerData)); - /* PlayerMetadata (things like your percentage in a level, attempt count) */ -#[derive(Clone, Default)] +#[derive(Clone, Default, Encodable, EncodableWithKnownSize, Decodable)] pub struct PlayerMetadata { percentage: u16, attempts: i32, } -encode_impl!(PlayerMetadata, buf, self, { - buf.write_u16(self.percentage); - buf.write_i32(self.attempts); -}); - -decode_impl!(PlayerMetadata, buf, { - let percentage = buf.read_u16()?; - let attempts = buf.read_i32()?; - Ok(Self { percentage, attempts }) -}); - -size_calc_impl!(PlayerMetadata, size_of_types!(u16, i32)); - /* AssociatedPlayerMetadata */ -#[derive(Clone, Default)] +#[derive(Clone, Default, Encodable, EncodableWithKnownSize)] pub struct AssociatedPlayerMetadata { pub account_id: i32, pub data: PlayerMetadata, } - -encode_impl!(AssociatedPlayerMetadata, buf, self, { - buf.write_i32(self.account_id); - buf.write(&self.data); -}); - -size_calc_impl!(AssociatedPlayerMetadata, size_of_types!(i32, PlayerMetadata)); diff --git a/server/game/src/data/types/mod.rs b/server/game/src/data/types/mod.rs index 51ad01ac..75375a5d 100644 --- a/server/game/src/data/types/mod.rs +++ b/server/game/src/data/types/mod.rs @@ -1,11 +1,11 @@ pub mod audio_frame; pub mod cocos; -pub mod crypto; +pub mod common; pub mod fast_string; pub mod gd; pub use audio_frame::*; pub use cocos::*; -pub use crypto::*; +pub use common::*; pub use fast_string::*; pub use gd::*; diff --git a/server/game/src/main.rs b/server/game/src/main.rs index b39ebbbc..18482c87 100644 --- a/server/game/src/main.rs +++ b/server/game/src/main.rs @@ -4,6 +4,7 @@ * Everything you see here is the exact definition of over-engineered and over-optimized. * Good luck. */ + #![feature(sync_unsafe_cell)] #![allow( clippy::must_use_candidate, @@ -11,22 +12,21 @@ clippy::cast_possible_truncation, clippy::missing_errors_doc, clippy::missing_panics_doc, - clippy::wildcard_imports + clippy::wildcard_imports, + clippy::missing_safety_doc )] use std::{ collections::{HashMap, HashSet}, error::Error, - net::SocketAddr, + net::{IpAddr, Ipv4Addr, SocketAddr}, }; -use globed_shared::{GameServerBootData, PROTOCOL_VERSION}; -use log::{error, info, warn, LevelFilter}; +use globed_shared::*; use reqwest::StatusCode; use server::GameServerConfiguration; use state::ServerState; use tokio::net::UdpSocket; -use util::Logger; use server::GameServer; @@ -42,6 +42,11 @@ struct StartupConfiguration { central_data: Option<(String, String)>, } +fn abort_misconfig() -> ! { + error!("aborting launch due to misconfiguration."); + std::process::exit(1); +} + fn parse_configuration() -> StartupConfiguration { let mut args = std::env::args(); let exe_name = args.next().unwrap(); // skip executable @@ -63,11 +68,17 @@ fn parse_configuration() -> StartupConfiguration { let bind_address = match bind_address.parse::() { Ok(x) => x, - Err(e) => { - error!("failed to parse the given IP address ({bind_address}): {e}"); - warn!("hint: you have to pass a valid IPv4 address with a port number, for example 0.0.0.0:41000"); - error!("aborting launch due to misconfiguration"); - std::process::exit(1); + Err(_) => { + // try to parse it as an ip addr and use a default port + match bind_address.parse::() { + Ok(x) => SocketAddr::new(IpAddr::V4(x), 41001), + Err(e) => { + error!("failed to parse the given IP address ({bind_address}): {e}"); + warn!("hint: you have to provide a valid IPv4 address with an optional port number"); + warn!("hint: for example \"0.0.0.0\" or \"0.0.0.0:41001\""); + abort_misconfig(); + } + } } }; @@ -104,8 +115,7 @@ fn parse_configuration() -> StartupConfiguration { error!("correct usage: \"{exe_name}
\""); } warn!("hint: you must specify the password for connecting to the central server, see the server readme."); - error!("aborting launch due to misconfiguration"); - std::process::exit(1); + abort_misconfig(); } let central_pw = arg.unwrap(); @@ -117,17 +127,18 @@ fn parse_configuration() -> StartupConfiguration { } } +#[allow(clippy::too_many_lines)] #[tokio::main] async fn main() -> Result<(), Box> { - log::set_logger(Logger::instance()).unwrap(); + log::set_logger(Logger::instance("globed_game_server")).unwrap(); if std::env::var("GLOBED_GS_LESS_LOG").unwrap_or("0".to_string()) == "1" { - log::set_max_level(LevelFilter::Warn); + log::set_max_level(LogLevelFilter::Warn); } else { log::set_max_level(if cfg!(debug_assertions) { - LevelFilter::Trace + LogLevelFilter::Trace } else { - LevelFilter::Info + LogLevelFilter::Info }); } @@ -152,6 +163,7 @@ async fn main() -> Result<(), Box> { protocol: PROTOCOL_VERSION, no_chat: HashSet::new(), special_users: HashMap::new(), + tps: 30, } } else { let (central_url, central_pw) = startup_config.central_data.unwrap(); @@ -173,15 +185,15 @@ async fn main() -> Result<(), Box> { error!("the central server returned an error: {err}"); if err.status().unwrap_or(StatusCode::OK) == StatusCode::UNAUTHORIZED { warn!("hint: there is a high chance that you have supplied a wrong password"); + warn!("hint: see the server readme if you don't know what password you need to use"); } - error!("aborting launch due to misconfiguration"); - std::process::exit(1); + abort_misconfig(); } }, Err(err) => { error!("failed to make a request to the central server: {err}"); - error!("aborting launch due to misconfiguration"); - std::process::exit(1); + warn!("hint: make sure the URL you passed is a valid Globed central server URL."); + abort_misconfig(); } }; @@ -190,8 +202,8 @@ async fn main() -> Result<(), Box> { Ok(x) => x, Err(err) => { error!("failed to parse the data sent by the central server: {err}"); - error!("aborting launch due to misconfiguration"); - std::process::exit(1); + warn!("hint: make sure the URL you passed is a valid Globed central server URL."); + abort_misconfig(); } }; @@ -201,8 +213,15 @@ async fn main() -> Result<(), Box> { "this game server is on v{PROTOCOL_VERSION}, while the central server uses v{}", boot_data.protocol ); - error!("aborting launch due to incompatible protocol versions"); - std::process::exit(1); + if boot_data.protocol > PROTOCOL_VERSION { + warn!( + "hint: you are running an old version of the Globed game server (v{}), please update to the latest one.", + env!("CARGO_PKG_VERSION") + ); + } else { + warn!("hint: the central server you are using is outdated, or the game server is using a development build that is too new."); + } + abort_misconfig(); } boot_data @@ -219,8 +238,7 @@ async fn main() -> Result<(), Box> { warn!("hint: ports below 1024 are commonly privileged and you can't use them as a regular user"); warn!("hint: pick a higher port number or use port 0 to get a randomly generated port"); } - error!("aborting launch due to an unexpected error"); - std::process::exit(1); + abort_misconfig(); } }; diff --git a/server/game/src/server.rs b/server/game/src/server.rs index 6449d9f4..ba3b8618 100644 --- a/server/game/src/server.rs +++ b/server/game/src/server.rs @@ -8,13 +8,12 @@ use parking_lot::Mutex as SyncMutex; use anyhow::{anyhow, bail}; use crypto_box::{aead::OsRng, SecretKey}; -use globed_shared::GameServerBootData; +use globed_shared::{logger::*, GameServerBootData}; use rustc_hash::FxHashMap; #[allow(unused_imports)] use tokio::sync::oneshot; // no way -use log::{debug, error, info, warn}; use tokio::net::UdpSocket; use crate::{ @@ -212,7 +211,7 @@ impl GameServer { // they won't be removed from levels or the player count and that person has to restart the game to connect again. // so try to avoid panics please.. thread.run().await; - log::trace!("removing client: {}", peer); + trace!("removing client: {}", peer); self.post_disconnect_cleanup(&thread, peer); }); @@ -222,17 +221,14 @@ impl GameServer { // check if the client is sending too many packets // safety: `thread.rate_limiter` is only used here and never accessed anywhere else. - // it is also guaranteed to not be a nullptr, as per `UnsafeCell::get`. - unsafe { - let rate_limiter = thread.rate_limiter.get(); - if !rate_limiter.as_mut().unwrap_unchecked().try_tick() { - if cfg!(debug_assertions) { - bail!("{peer} is ratelimited"); - } - - // silently reject the packet in release mode - return Ok(()); + let allowed = unsafe { thread.rate_limiter.try_tick() }; + if !allowed { + if cfg!(debug_assertions) { + bail!("{peer} is ratelimited"); } + + // silently reject the packet in release mode + return Ok(()); } // don't heap allocate for small packets diff --git a/server/game/src/server_thread/error.rs b/server/game/src/server_thread/error.rs index 6deaf928..21a6c2a6 100644 --- a/server/game/src/server_thread/error.rs +++ b/server/game/src/server_thread/error.rs @@ -11,6 +11,7 @@ pub enum PacketHandlingError { MalformedMessage, // packet is missing a header MalformedLoginAttempt, // LoginPacket with cleartext credentials MalformedCiphertext, // missing nonce/mac in the encrypted ciphertext + MalformedPacketStructure, // failed to decode the packet NoHandler(u16), // no handler found for this packet ID WebRequestError(reqwest::Error), // error making a web request to the central server UnexpectedPlayerData, // client sent PlayerDataPacket or SyncPlayerMetadataPacket outside of a level @@ -66,6 +67,7 @@ impl Display for PacketHandlingError { Self::MalformedCiphertext => f.write_str("malformed ciphertext in an encrypted packet"), Self::MalformedMessage => f.write_str("malformed message structure"), Self::MalformedLoginAttempt => f.write_str("malformed login attempt"), + Self::MalformedPacketStructure => f.write_str("malformed packet structure, could not decode it"), Self::NoHandler(id) => f.write_fmt(format_args!("no packet handler for packet ID {id}")), Self::WebRequestError(msg) => f.write_fmt(format_args!("web request error: {msg}")), Self::UnexpectedPlayerData => { diff --git a/server/game/src/server_thread/handlers/connection.rs b/server/game/src/server_thread/handlers/connection.rs index e631ec55..84acc411 100644 --- a/server/game/src/server_thread/handlers/connection.rs +++ b/server/game/src/server_thread/handlers/connection.rs @@ -1,10 +1,9 @@ use std::sync::atomic::Ordering; use crypto_box::ChaChaBox; -use globed_shared::PROTOCOL_VERSION; +use globed_shared::{logger::*, PROTOCOL_VERSION}; use crate::server_thread::{GameServerThread, PacketHandlingError}; -use log::debug; use super::*; use crate::data::*; @@ -53,13 +52,11 @@ impl GameServerThread { } self.crypto_box - .get_or_init(|| ChaChaBox::new(&packet.key.pubkey, &self.game_server.secret_key)); + .get_or_init(|| ChaChaBox::new(&packet.key, &self.game_server.secret_key)); } self.send_packet_fast(&CryptoHandshakeResponsePacket { - key: CryptoPublicKey { - pubkey: self.game_server.secret_key.public_key().clone(), - }, + key: self.game_server.secret_key.public_key().clone(), }) .await }); @@ -85,7 +82,8 @@ impl GameServerThread { account_data.account_id = packet.account_id; account_data.name = packet.name; } - self.send_packet_fast(&LoggedInPacket {}).await?; + let tps = self.game_server.central_conf.lock().tps; + self.send_packet_fast(&LoggedInPacket { tps }).await?; return Ok(()); } @@ -149,7 +147,8 @@ impl GameServerThread { debug!("Login successful from {player_name} ({})", packet.account_id); - self.send_packet_fast(&LoggedInPacket {}).await?; + let tps = self.game_server.central_conf.lock().tps; + self.send_packet_fast(&LoggedInPacket { tps }).await?; Ok(()) }); diff --git a/server/game/src/server_thread/handlers/game.rs b/server/game/src/server_thread/handlers/game.rs index 760489e5..7a35a06c 100644 --- a/server/game/src/server_thread/handlers/game.rs +++ b/server/game/src/server_thread/handlers/game.rs @@ -7,7 +7,8 @@ use crate::{ data::packets::PacketHeader, server_thread::{GameServerThread, PacketHandlingError, Result}, }; -use log::warn; + +use globed_shared::logger::*; use super::*; use crate::data::*; diff --git a/server/game/src/server_thread/handlers/mod.rs b/server/game/src/server_thread/handlers/mod.rs index 978d7350..3d629bc2 100644 --- a/server/game/src/server_thread/handlers/mod.rs +++ b/server/game/src/server_thread/handlers/mod.rs @@ -7,9 +7,9 @@ pub use game::MAX_VOICE_PACKET_SIZE; macro_rules! gs_handler { ($self:ident,$name:ident,$pktty:ty,$pkt:ident,$code:expr) => { pub(crate) async fn $name(&$self, buf: &mut bytebuffer::ByteReader<'_>) -> crate::server_thread::Result<()> { - let $pkt = <$pktty>::decode_from_reader(buf)?; + let $pkt = <$pktty>::decode_from_reader(buf).map_err(|_| crate::server_thread::error::PacketHandlingError::MalformedPacketStructure)?; #[cfg(debug_assertions)] - log::debug!( + globed_shared::logger::debug!( "[{} @ {}] Handling packet {}", $self.account_id.load(Ordering::Relaxed), $self.peer, @@ -24,9 +24,9 @@ macro_rules! gs_handler { macro_rules! gs_handler_sync { ($self:ident,$name:ident,$pktty:ty,$pkt:ident,$code:expr) => { pub(crate) fn $name(&$self, buf: &mut bytebuffer::ByteReader<'_>) -> crate::server_thread::Result<()> { - let $pkt = <$pktty>::decode_from_reader(buf)?; + let $pkt = <$pktty>::decode_from_reader(buf).map_err(|_| crate::server_thread::error::PacketHandlingError::MalformedPacketStructure)?; #[cfg(debug_assertions)] - log::debug!( + globed_shared::logger::debug!( "[{} @ {}] Handling packet {}", $self.account_id.load(Ordering::Relaxed), $self.peer, diff --git a/server/game/src/server_thread/mod.rs b/server/game/src/server_thread/mod.rs index 550a09c8..e442c52a 100644 --- a/server/game/src/server_thread/mod.rs +++ b/server/game/src/server_thread/mod.rs @@ -1,5 +1,4 @@ use std::{ - cell::SyncUnsafeCell, net::SocketAddrV4, sync::{ atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering}, @@ -15,10 +14,15 @@ use crypto_box::{ aead::{Aead, AeadCore, AeadInPlace, OsRng}, ChaChaBox, }; -use log::{debug, warn}; +use globed_shared::logger::*; use tokio::sync::{mpsc, Mutex}; -use crate::{data::*, server::GameServer, server_thread::handlers::*, util::SimpleRateLimiter}; +use crate::{ + data::*, + server::GameServer, + server_thread::handlers::*, + util::{rate_limiter::UnsafeRateLimiter, SimpleRateLimiter}, +}; mod error; mod handlers; @@ -32,7 +36,6 @@ pub const SMALL_PACKET_LIMIT: usize = 164; const CHANNEL_BUFFER_SIZE: usize = 8; const NONCE_SIZE: usize = 24; const MAC_SIZE: usize = 16; -const SERVER_TPS: usize = 30; // configured amount of PlayerData per second that can be received #[derive(Clone)] pub enum ServerThreadMessage { @@ -58,7 +61,7 @@ pub struct GameServerThread { pub account_data: SyncMutex, last_voice_packet: AtomicU64, - pub rate_limiter: SyncUnsafeCell, // do NOT interact with this field oustide of GameServer. + pub rate_limiter: UnsafeRateLimiter, // do NOT interact with this field oustide of GameServer. } impl GameServerThread { @@ -66,8 +69,9 @@ impl GameServerThread { pub fn new(peer: SocketAddrV4, game_server: &'static GameServer) -> Self { let (tx, rx) = mpsc::channel::(CHANNEL_BUFFER_SIZE); - let rate_limiter = SimpleRateLimiter::new(SERVER_TPS + 5, Duration::from_millis(950)); - let rate_limiter = SyncUnsafeCell::new(rate_limiter); + let rl_request_limit = (game_server.central_conf.lock().tps + 5) as usize; + let rate_limiter = SimpleRateLimiter::new(rl_request_limit, Duration::from_millis(950)); + let rate_limiter = UnsafeRateLimiter::new(rate_limiter); Self { tx, rx: Mutex::new(rx), @@ -114,7 +118,7 @@ impl GameServerThread { | PacketHandlingError::EncryptionError | PacketHandlingError::DecryptionError | PacketHandlingError::IOError(_) => { - log::warn!("[{} @ {}] err: {}", self.account_id.load(Ordering::Relaxed), self.peer, error); + warn!("[{} @ {}] err: {}", self.account_id.load(Ordering::Relaxed), self.peer, error); } // these are either our fault or a fatal error somewhere PacketHandlingError::SocketSendFailed(_) @@ -123,12 +127,13 @@ impl GameServerThread { | PacketHandlingError::SystemTimeError(_) | PacketHandlingError::WebRequestError(_) | PacketHandlingError::DangerousAllocation(_) => { - log::error!("[{} @ {}] err: {}", self.account_id.load(Ordering::Relaxed), self.peer, error); + error!("[{} @ {}] err: {}", self.account_id.load(Ordering::Relaxed), self.peer, error); } // these can likely never happen unless network corruption or someone is pentesting, so ignore in release PacketHandlingError::MalformedMessage | PacketHandlingError::MalformedCiphertext | PacketHandlingError::MalformedLoginAttempt + | PacketHandlingError::MalformedPacketStructure | PacketHandlingError::NoHandler(_) | PacketHandlingError::SocketWouldBlock | PacketHandlingError::Ratelimited @@ -160,7 +165,7 @@ impl GameServerThread { #[allow(dead_code)] async fn send_packet(&self, packet: &P) -> Result<()> { #[cfg(debug_assertions)] - log::debug!( + debug!( "[{} @ {}] Sending packet {} (normal)", self.account_id.load(Ordering::Relaxed), self.peer, @@ -184,7 +189,7 @@ impl GameServerThread { /// you have to provide a rough estimate of the packet size, if the packet doesn't fit, the function panics. async fn send_packet_fast_rough(&self, packet: &P, packet_size: usize) -> Result<()> { #[cfg(debug_assertions)] - log::debug!( + debug!( "[{} @ {}] Sending packet {} ({})", self.account_id.load(Ordering::Relaxed), self.peer, diff --git a/server/game/src/util/mod.rs b/server/game/src/util/mod.rs index 55d08d43..36f9842b 100644 --- a/server/game/src/util/mod.rs +++ b/server/game/src/util/mod.rs @@ -1,5 +1,3 @@ -pub mod logger; pub mod rate_limiter; -pub use logger::Logger; pub use rate_limiter::SimpleRateLimiter; diff --git a/server/game/src/util/rate_limiter.rs b/server/game/src/util/rate_limiter.rs index dd2e02df..734c24bb 100644 --- a/server/game/src/util/rate_limiter.rs +++ b/server/game/src/util/rate_limiter.rs @@ -1,5 +1,9 @@ -use std::time::{Duration, Instant}; +use std::{ + cell::SyncUnsafeCell, + time::{Duration, Instant}, +}; +/// Naive rate limiter implementation, cannot sleep and is not thread safe on its own. pub struct SimpleRateLimiter { limit: usize, count: usize, @@ -34,3 +38,26 @@ impl SimpleRateLimiter { } } } + +/// A ratelimiter wrapped in a `SyncUnsafeCell`, does not need to be mutable to use. +/// If two different threads attempt to call `try_tick()` on the same instance at the same time, +/// the behavior is undefined. +#[repr(transparent)] +pub struct UnsafeRateLimiter { + limiter: SyncUnsafeCell, +} + +impl UnsafeRateLimiter { + pub fn new(limiter: SimpleRateLimiter) -> Self { + Self { + limiter: SyncUnsafeCell::new(limiter), + } + } + + /// Returns `true` if we are not ratelimited, `false` if we are. + /// Calling this from multiple threads at the same time is **undefined behavior**. + pub unsafe fn try_tick(&self) -> bool { + // UnsafeCell can never be nullptr, so the unwrap is safe. + self.limiter.get().as_mut().unwrap_unchecked().try_tick() + } +} diff --git a/server/shared/Cargo.toml b/server/shared/Cargo.toml index 9d2157b2..1ee3f0a7 100644 --- a/server/shared/Cargo.toml +++ b/server/shared/Cargo.toml @@ -6,5 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +colored = "2.0.4" +log = { version = "0.4.20" } serde = { version = "1.0.192", 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 ff232aa0..299c1376 100644 --- a/server/shared/src/lib.rs +++ b/server/shared/src/lib.rs @@ -1,6 +1,12 @@ +use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; -use serde::{Deserialize, Serialize}; +// module reexports +pub use colored; +pub use time; +// our reexports +pub use logger::*; +pub mod logger; pub const PROTOCOL_VERSION: u16 = 1; @@ -20,4 +26,5 @@ pub struct GameServerBootData { pub protocol: u16, pub no_chat: HashSet, pub special_users: HashMap, + pub tps: u32, } diff --git a/server/game/src/util/logger.rs b/server/shared/src/logger.rs similarity index 59% rename from server/game/src/util/logger.rs rename to server/shared/src/logger.rs index ca117822..f652ec25 100644 --- a/server/game/src/util/logger.rs +++ b/server/shared/src/logger.rs @@ -1,30 +1,35 @@ use std::{sync::OnceLock, time::SystemTime}; use colored::Colorize; -use log::Level; use time::{format_description, OffsetDateTime}; +pub use log; +pub use log::{debug, error, info, trace, warn, Level as LogLevel, LevelFilter as LogLevelFilter}; + pub struct Logger { format_desc: Vec>, + self_crate_name: &'static str, } const TIME_FORMAT: &str = "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]"; impl Logger { - pub fn instance() -> &'static Self { + #[allow(clippy::missing_panics_doc)] + pub fn instance(self_crate_name: &'static str) -> &'static Self { static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| Logger { - format_desc: format_description::parse(TIME_FORMAT).unwrap(), + format_desc: format_description::parse_borrowed::<2>(TIME_FORMAT).unwrap(), + self_crate_name, }) } } impl log::Log for Logger { fn enabled(&self, metadata: &log::Metadata) -> bool { - if metadata.target().starts_with("globed_game_server") { + if metadata.target().starts_with(self.self_crate_name) { true } else { - metadata.level() <= Level::Warn + metadata.level() <= LogLevel::Warn } } @@ -37,20 +42,20 @@ impl log::Log for Logger { let formatted_time = now.format(&self.format_desc).unwrap(); let (level, args) = match record.level() { - Level::Error => ( + LogLevel::Error => ( record.level().to_string().bright_red(), record.args().to_string().bright_red(), ), - Level::Warn => ( + LogLevel::Warn => ( record.level().to_string().bright_yellow(), record.args().to_string().bright_yellow(), ), - Level::Info => (record.level().to_string().cyan(), record.args().to_string().cyan()), - Level::Debug => (record.level().to_string().normal(), record.args().to_string().normal()), - Level::Trace => (record.level().to_string().black(), record.args().to_string().black()), + LogLevel::Info => (record.level().to_string().cyan(), record.args().to_string().cyan()), + LogLevel::Debug => (record.level().to_string().normal(), record.args().to_string().normal()), + LogLevel::Trace => (record.level().to_string().black(), record.args().to_string().black()), }; - if record.level() == Level::Error { + if record.level() == LogLevel::Error { eprintln!("[{formatted_time}] [{level}] - {args}"); } else { println!("[{formatted_time}] [{level}] - {args}"); diff --git a/src/data/packets/server/connection.hpp b/src/data/packets/server/connection.hpp index 51e91d22..9d9df142 100644 --- a/src/data/packets/server/connection.hpp +++ b/src/data/packets/server/connection.hpp @@ -25,7 +25,7 @@ class CryptoHandshakeResponsePacket : public Packet { class KeepaliveResponsePacket : public Packet { GLOBED_PACKET(20002, false) - + GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { playerCount = buf.readU32(); } @@ -45,7 +45,9 @@ class LoggedInPacket : public Packet { GLOBED_PACKET(20004, false) GLOBED_PACKET_ENCODE_UNIMPL - GLOBED_PACKET_DECODE {} + GLOBED_PACKET_DECODE { tps = buf.readU32(); } + + uint32_t tps; }; class LoginFailedPacket : public Packet { diff --git a/src/managers/server_manager.hpp b/src/managers/server_manager.hpp index 31e351d9..15d8507f 100644 --- a/src/managers/server_manager.hpp +++ b/src/managers/server_manager.hpp @@ -54,14 +54,16 @@ class GlobedServerManager { size_t gameServerCount(); - uint32_t pingStart(const std::string& serverId); // start a ping on the active game server void pingStartActive(); - - void pingFinish(uint32_t pingId, uint32_t playerCount); // finish a ping on the active game server void pingFinishActive(uint32_t playerCount); + // start a ping on a server given an id + uint32_t pingStart(const std::string& serverId); + // finish a ping on a server given an id + void pingFinish(uint32_t pingId, uint32_t playerCount); + GameServerView getGameServer(const std::string& serverId); std::vector getPingHistory(const std::string& serverId); diff --git a/src/managers/settings.cpp b/src/managers/settings.cpp index ad6987c1..15f53106 100644 --- a/src/managers/settings.cpp +++ b/src/managers/settings.cpp @@ -16,7 +16,7 @@ CachedSettings GlobedSettings::getCached() { void GlobedSettings::refreshCache() { auto cache = _cache.lock(); - cache->test = this->get("test"); + cache->serverTps = this->get("server-tps"); } bool GlobedSettings::getFlag(const std::string& key) { diff --git a/src/managers/settings.hpp b/src/managers/settings.hpp index db2c8726..e4567d01 100644 --- a/src/managers/settings.hpp +++ b/src/managers/settings.hpp @@ -3,12 +3,12 @@ #include struct CachedSettings { - bool test; + uint32_t serverTps; }; #define MAKE_DEFAULT(_key, value) if (key == (_key)) return (value); -// Besides `getCached()`, this class is not thread safe (reason: getSavedValue/setSavedValue) +// Besides `getCached()`, this class is not thread safe (reason: Mod::getSavedValue/Mod::setSavedValue) class GlobedSettings { GLOBED_SINGLETON(GlobedSettings); GlobedSettings(); @@ -41,7 +41,7 @@ class GlobedSettings { if (this->getFlag("_gset_-" + key)) { return geode::Mod::get()->getSavedValue("gsetting-" + key); } else { - MAKE_DEFAULT("test", true) + MAKE_DEFAULT("server-tps", (uint32_t)30) return T{}; } diff --git a/src/net/network_manager.cpp b/src/net/network_manager.cpp index 29fb8b71..517c4ac7 100644 --- a/src/net/network_manager.cpp +++ b/src/net/network_manager.cpp @@ -39,6 +39,7 @@ NetworkManager::NetworkManager() { addBuiltinListener([this](auto packet) { log::info("Successfully logged into the server!"); + connectedTps = packet->tps; _loggedin = true; }); diff --git a/src/net/network_manager.hpp b/src/net/network_manager.hpp index 37fa53c5..47fbb195 100644 --- a/src/net/network_manager.hpp +++ b/src/net/network_manager.hpp @@ -35,6 +35,9 @@ class NetworkManager { static constexpr uint8_t PROTOCOL_VERSION = 1; GLOBED_SINGLETON(NetworkManager) + + AtomicU32 connectedTps; // if `authenticated() == true`, this is the TPS of the current server, otherwise undefined. + NetworkManager(); ~NetworkManager(); diff --git a/src/ui/hooks/play_layer.hpp b/src/ui/hooks/play_layer.hpp index 118fbac3..fe85075f 100644 --- a/src/ui/hooks/play_layer.hpp +++ b/src/ui/hooks/play_layer.hpp @@ -6,24 +6,24 @@ #if GLOBED_HAS_KEYBINDS #include -#endif // GLOBED_CUSTOM_KEYBINDS +#endif // GLOBED_HAS_KEYBINDS #include