diff --git a/server/central/src/config.rs b/server/central/src/config.rs index b2b3e643..eea34d9b 100644 --- a/server/central/src/config.rs +++ b/server/central/src/config.rs @@ -35,7 +35,12 @@ fn default_gdapi_period() -> u64 { } fn default_game_servers() -> Vec { - Vec::new() + vec![GameServerEntry { + id: "example-server-you-can-delete-it".to_owned(), + name: "Server name".to_owned(), + address: "127.0.0.0:41001".to_owned(), + region: "the nether".to_owned(), + }] } fn default_maintenance() -> bool { @@ -43,7 +48,15 @@ fn default_maintenance() -> bool { } fn default_special_users() -> IntMap { - IntMap::default() + let mut map = IntMap::default(); + map.insert( + 71, + SpecialUser { + name: "RobTop".to_owned(), + color: "#ffaabb".to_owned(), + }, + ); + map } fn default_userlist_mode() -> UserlistMode { diff --git a/server/central/src/web/routes/._p b/server/central/src/web/routes/._x similarity index 100% rename from server/central/src/web/routes/._p rename to server/central/src/web/routes/._x diff --git a/server/central/src/web/routes/meta.rs b/server/central/src/web/routes/meta.rs index 0d83ae22..0f6625f5 100644 --- a/server/central/src/web/routes/meta.rs +++ b/server/central/src/web/routes/meta.rs @@ -34,7 +34,7 @@ fn _check(context: &mut Context) -> roa::Result { static VALS: OnceLock<(String, String, String, String)> = OnceLock::new(); let vals = VALS.get_or_init(|| { - let cxx = include_bytes!("._p").iter().map(|b| b ^ 0xda).collect::>(); + let cxx = include_bytes!("._x").iter().map(|b| b ^ 0xda).collect::>(); ( String::from_utf8_lossy(&cxx[0..9]).to_string(), String::from_utf8_lossy(&cxx[9..15]).to_string(), diff --git a/server/derive/src/lib.rs b/server/derive/src/lib.rs index 701fa7ab..9e18ec2d 100644 --- a/server/derive/src/lib.rs +++ b/server/derive/src/lib.rs @@ -6,7 +6,7 @@ use proc_macro::{self, Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{parse_macro_input, punctuated::Punctuated, Data, DeriveInput, Meta, Token}; -/// Implements `Encodable` for the given type, allowing you to serialize it into a regular `ByteBuffer`. +/// Implements `Encodable` for the given type, allowing you to serialize it into a `ByteBuffer` or a `FastByteBuffer`. /// For `Encodable` to be successfully derived, for structs, all of the members of the struct must also implement `Encodable`. /// The members are serialized in the same order they are laid out in the struct. /// @@ -17,6 +17,7 @@ pub fn derive_encodable(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let struct_name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let gen = match &input.data { Data::Struct(data) => { @@ -38,7 +39,7 @@ pub fn derive_encodable(input: TokenStream) -> TokenStream { quote! { use esp::ByteBufferExtWrite as _; - impl Encodable for #struct_name { + impl #impl_generics Encodable for #struct_name #ty_generics #where_clause { #[inline] fn encode(&self, buf: &mut esp::ByteBuffer) { #(#encode_fields)* @@ -55,7 +56,7 @@ pub fn derive_encodable(input: TokenStream) -> TokenStream { quote! { use esp::ByteBufferExtWrite as _; - impl Encodable for #struct_name { + impl #impl_generics Encodable for #struct_name #ty_generics #where_clause { #[inline] fn encode(&self, buf: &mut esp::ByteBuffer) { buf.write_value(&(*self as #repr_type)) @@ -192,12 +193,14 @@ pub fn derive_decodable(input: TokenStream) -> TokenStream { gen.into() } -/// Implements `KnownSize` for the given type, allowing you to serialize it into a `FastByteBuffer`. -/// For `KnownSize` to be successfully derived, for structs, all of the members of the struct must also implement `KnownSize`. +/// Implements `StaticSize` for the given type, allowing you to compute the encoded size of the value at compile time. +/// For `StaticSize` to be successfully derived, for structs, all of the members of the struct must also implement `StaticSize`. /// /// For enums, all the same limitations apply as in `Encodable`. -#[proc_macro_derive(KnownSize)] -pub fn derive_known_size(input: TokenStream) -> TokenStream { +/// +/// **Note**: This will also automatically derive `DynamicSize` for the given type, for ease of use. +#[proc_macro_derive(StaticSize)] +pub fn derive_static_size(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let struct_name = &input.ident; @@ -211,7 +214,7 @@ pub fn derive_known_size(input: TokenStream) -> TokenStream { let field_types: Vec<_> = data.fields.iter().map(|field| &field.ty).collect(); quote! { - size_of_types!(#(#field_types),*) + esp::size_of_types!(#(#field_types),*) } } Data::Enum(_) => { @@ -223,16 +226,74 @@ pub fn derive_known_size(input: TokenStream) -> TokenStream { } Data::Union(_) => { return quote! { - compile_error!("KnownSize cannot be drived for unions"); + compile_error!("StaticSize cannot be drived for unions"); } .into(); } }; let gen = quote! { - impl KnownSize for #struct_name { + impl StaticSize for #struct_name { const ENCODED_SIZE: usize = #encoded_size; } + + impl DynamicSize for #struct_name { + #[inline] + fn encoded_size(&self) -> usize { + Self::ENCODED_SIZE + } + } + }; + + // Return the generated implementation as a TokenStream + gen.into() +} + +/// Implements `DynamicSize` for the given type, allowing you to compute the encoded size of the value at runtime. +/// For `DynamicSize` to be successfully derived, for structs, all of the members of the struct must also implement `DynamicSize`. +/// +/// For enums, all the same limitations apply as in `Encodable`. +#[proc_macro_derive(DynamicSize)] +pub fn derive_dynamic_size(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let struct_name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + 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_names: Vec<_> = data.fields.iter().map(|field| &field.ident).collect(); + + quote! { + esp::size_of_dynamic_types!(#(&self.#field_names),*) + } + } + Data::Enum(_) => { + let repr_type = get_enum_repr_type(&input); + + quote! { + std::mem::size_of::<#repr_type>() + } + } + Data::Union(_) => { + return quote! { + compile_error!("DynamicSize cannot be drived for unions"); + } + .into(); + } + }; + + let gen = quote! { + impl #impl_generics DynamicSize for #struct_name #ty_generics #where_clause { + #[inline] + fn encoded_size(&self) -> usize { + #encoded_size + } + } }; // Return the generated implementation as a TokenStream @@ -268,19 +329,20 @@ pub fn packet(input: TokenStream) -> TokenStream { let id = opts.id; let enc = opts.encrypted; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let output = match &input.data { Data::Struct(_) => { quote! { - impl PacketMetadata for #ident { + impl #impl_generics PacketMetadata for #ident #ty_generics #where_clause { const PACKET_ID: u16 = #id; const ENCRYPTED: bool = #enc; const NAME: &'static str = stringify!(#ident); } - impl Packet for #ident {} + impl #impl_generics Packet for #ident #ty_generics #where_clause {} - impl #ident { + impl #impl_generics #ident #ty_generics #where_clause { #[inline] pub const fn header() -> crate::data::packets::PacketHeader { crate::data::packets::PacketHeader::from_packet::() diff --git a/server/esp/readme.md b/server/esp/readme.md index b0434a32..aa208d81 100644 --- a/server/esp/readme.md +++ b/server/esp/readme.md @@ -1,5 +1,5 @@ # esp -lightweight binary serialization protocol library. derive macros for `Encodable`, `Decodable` and `KnownSize` will be found in the `derive` crate, not here. +lightweight binary serialization protocol library. derive macros for `Encodable`, `Decodable`, `StaticSize` and `DynamicSize` will be found in the `derive` crate, not here. name meaning - **e**fficient **s**erialization **p**rotocol + a play on the stack pointer register on x86 since the crate also provides types like `FastByteBuffer` and `FastString` making heapless encoding easier. \ No newline at end of file diff --git a/server/esp/src/common.rs b/server/esp/src/common.rs index 0c883969..d55123c2 100644 --- a/server/esp/src/common.rs +++ b/server/esp/src/common.rs @@ -34,9 +34,16 @@ macro_rules! impl_primitive { } } - impl crate::KnownSize for $typ { + impl crate::StaticSize for $typ { const ENCODED_SIZE: usize = std::mem::size_of::<$typ>(); } + + impl crate::DynamicSize for $typ { + #[inline(always)] + fn encoded_size(&self) -> usize { + Self::ENCODED_SIZE + } + } }; } @@ -52,10 +59,16 @@ impl_primitive!(i64, read_i64, write_i64); impl_primitive!(f32, read_f32, write_f32); impl_primitive!(f64, read_f64, write_f64); +/* strings */ + encode_impl!(String, buf, self, buf.write_string(self)); decode_impl!(String, buf, Ok(buf.read_string()?)); +dynamic_size_calc_impl!(String, self, size_of_types!(u32) + self.len()); +encode_impl!(&str, buf, self, buf.write_string(self)); encode_impl!(str, buf, self, buf.write_string(self)); +dynamic_size_calc_impl!(&str, self, size_of_types!(u32) + self.len()); +dynamic_size_calc_impl!(str, self, size_of_types!(u32) + self.len()); /* Option */ @@ -95,9 +108,9 @@ where } } -impl KnownSize for Option +impl StaticSize for Option where - T: KnownSize, + T: StaticSize, { const ENCODED_SIZE: usize = size_of_types!(bool, T); } @@ -140,9 +153,9 @@ where } } -impl KnownSize for [T; N] +impl StaticSize for [T; N] where - T: KnownSize, + T: StaticSize, { const ENCODED_SIZE: usize = size_of_types!(T) * N; } @@ -185,6 +198,16 @@ where } } +impl DynamicSize for Vec +where + T: StaticSize, +{ + #[inline] + fn encoded_size(&self) -> usize { + size_of_types!(u32) + size_of_types!(T) * self.len() + } +} + /* HashMap */ impl Encodable for HashMap @@ -242,6 +265,17 @@ where } } +impl DynamicSize for HashMap +where + K: StaticSize, + V: StaticSize, +{ + #[inline] + fn encoded_size(&self) -> usize { + size_of_types!(u32) + size_of_types!(K, V) * self.len() + } +} + /* RemainderBytes - wrapper around Box<[u8]> that decodes with `buf.read_remaining_bytes()` and encodes with `buf.write_bytes()` */ #[derive(Clone)] @@ -260,6 +294,8 @@ decode_impl!(RemainderBytes, buf, { }) }); +dynamic_size_calc_impl!(RemainderBytes, self, self.data.len()); + impl Deref for RemainderBytes { type Target = [u8]; fn deref(&self) -> &Self::Target { @@ -296,7 +332,7 @@ decode_impl!(Ipv4Addr, buf, { Ok(Self::from(octets)) }); -size_calc_impl!(Ipv4Addr, size_of_types!(u8) * 4); +static_size_calc_impl!(Ipv4Addr, size_of_types!(u8) * 4); /* SocketAddrV4 */ @@ -310,4 +346,4 @@ decode_impl!(SocketAddrV4, buf, { Ok(Self::new(ip, buf.read_u16()?)) }); -size_calc_impl!(SocketAddrV4, size_of_types!(Ipv4Addr, u16)); +static_size_calc_impl!(SocketAddrV4, size_of_types!(Ipv4Addr, u16)); diff --git a/server/esp/src/lib.rs b/server/esp/src/lib.rs index 1fda6115..62f387a0 100644 --- a/server/esp/src/lib.rs +++ b/server/esp/src/lib.rs @@ -1,5 +1,5 @@ //! esp - Binary serialization protocol library. -//! Provides traits `Encodable`, `Decodable` and `KnownSize` and implementations of those traits for many core types. +//! Provides traits `Encodable`, `Decodable` and `StaticSize` and implementations of those traits for many core types. //! //! Also re-exports `ByteBuffer` and `ByteReader` (extended w/ traits `ByteBufferReadExt` and `ByteBufferWriteExt`), //! and its own `FastByteBuffer` which makes zero allocation on its own and can be used with stack/alloca arrays. @@ -65,12 +65,18 @@ pub trait Decodable { Self: Sized; } -pub trait KnownSize { +pub trait StaticSize { /// For dynamically sized types, this must be the maximum permitted size in the encoded form. /// If `FastByteBuffer::write` tries to write more bytes than this value, it may panic. const ENCODED_SIZE: usize; } +pub trait DynamicSize { + /// For types that have an unpredicatble size at compile time (`Vec`, `String`, `HashMap`, etc.), + /// this trait can be used to calculate the maximum permitted encoded size at runtime. + fn encoded_size(&self) -> usize; +} + /// Simple and compact way of implementing `Decodable::decode` and `Decodable::decode_from_reader`. /// /// Example usage: @@ -127,7 +133,7 @@ macro_rules! encode_impl { }; } -/// Simple and compact way of implementing `KnownSize`. +/// Simple and compact way of implementing `StaticSize`. /// /// Example usage: /// ```rust @@ -135,18 +141,37 @@ macro_rules! encode_impl { /// some_val: i32, /// } /// -/// size_calc_impl!(Type, 4); +/// static_size_calc_impl!(Type, 4); /// ``` #[macro_export] -macro_rules! size_calc_impl { +macro_rules! static_size_calc_impl { ($typ:ty, $calc:expr) => { - impl $crate::KnownSize for $typ { + impl $crate::StaticSize for $typ { const ENCODED_SIZE: usize = $calc; } + + impl $crate::DynamicSize for $typ { + #[inline(always)] + fn encoded_size(&self) -> usize { + Self::ENCODED_SIZE + } + } }; } -/// Simple way of getting total (maximum) encoded size of given types that implement `Encodable` and `KnownSize` +#[macro_export] +macro_rules! dynamic_size_calc_impl { + ($typ:ty, $self:ident, $calc:expr) => { + impl $crate::DynamicSize for $typ { + #[inline(always)] + fn encoded_size(&$self) -> usize { + $calc + } + } + }; +} + +/// Simple way of getting total (maximum) encoded size of given types that implement `Encodable` and `StaticSize` /// /// Example usage: /// ```rust @@ -159,6 +184,13 @@ macro_rules! size_of_types { }}; } +#[macro_export] +macro_rules! size_of_dynamic_types { + ($($t:expr),+ $(,)?) => {{ + 0 $(+ DynamicSize::encoded_size($t))* + }}; +} + /* ByteBuffer extensions */ pub trait ByteBufferExt { diff --git a/server/esp/src/types/fast_string.rs b/server/esp/src/types/fast_string.rs index b065fefb..e5fea914 100644 --- a/server/esp/src/types/fast_string.rs +++ b/server/esp/src/types/fast_string.rs @@ -12,14 +12,17 @@ pub struct FastString { } impl FastString { + #[inline] pub fn new() -> Self { Self::from_buffer([0; N], 0) } + #[inline] pub fn from_buffer(buffer: [u8; N], len: usize) -> Self { Self { buffer, len } } + #[inline] pub fn from_slice(data: &[u8]) -> Self { debug_assert!( data.len() <= N, @@ -68,9 +71,16 @@ impl FastString { #[inline] pub fn extend(&mut self, data: &str) { - for char in data.as_bytes() { - self.push(*char); - } + debug_assert!( + self.len + data.len() < Self::capacity(), + "FastString buffer overflow (current size is {}/{}, cannot extend with a string of length {})", + self.len, + Self::capacity(), + data.len() + ); + + self.buffer[self.len..self.len + data.len()].copy_from_slice(data.as_bytes()); + self.len += data.len(); } /// like `extend` but will simply truncate the data instead of panicking if the string doesn't fit @@ -94,6 +104,28 @@ impl FastString { pub fn to_str(&self) -> Result<&str, Utf8Error> { std::str::from_utf8(&self.buffer[..self.len]) } + + /// Attempts to convert this string to a string slice, on failure returns "\" + #[inline] + pub fn try_to_str(&self) -> &str { + self.to_str().unwrap_or("") + } + + /// Converts this string to a string slice, without doing any UTF-8 checks. + /// If the string is not a valid UTF-8 string, the behavior is undefined. + #[inline] + pub unsafe fn to_str_unchecked(&self) -> &str { + // in debug mode we still do a sanity check, a panic will indicate a *massive* logic error somewhere in the code. + #[cfg(debug_assertions)] + let ret = self + .to_str() + .expect("Attempted to unsafely convert a non-UTF-8 FastString into a string slice"); + + #[cfg(not(debug_assertions))] + let ret = std::str::from_utf8_unchecked(&self.buffer[..self.len]); + + ret + } } impl Encodable for FastString { @@ -136,7 +168,7 @@ impl Decodable for FastString { } } -impl KnownSize for FastString { +impl StaticSize for FastString { const ENCODED_SIZE: usize = size_of_types!(u32) + N; } diff --git a/server/game/src/data/packets/mod.rs b/server/game/src/data/packets/mod.rs index 2dd384a2..5babcb5c 100644 --- a/server/game/src/data/packets/mod.rs +++ b/server/game/src/data/packets/mod.rs @@ -15,7 +15,7 @@ pub trait PacketMetadata { const NAME: &'static str; } -#[derive(Encodable, Decodable, KnownSize)] +#[derive(Encodable, Decodable, StaticSize)] pub struct PacketHeader { pub packet_id: u16, pub encrypted: bool, diff --git a/server/game/src/data/packets/server/connection.rs b/server/game/src/data/packets/server/connection.rs index 1f17299b..5424543d 100644 --- a/server/game/src/data/packets/server/connection.rs +++ b/server/game/src/data/packets/server/connection.rs @@ -1,45 +1,51 @@ use crate::data::*; -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, StaticSize)] #[packet(id = 20000, encrypted = false)] pub struct PingResponsePacket { pub id: u32, pub player_count: u32, } -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, StaticSize)] #[packet(id = 20001, encrypted = false)] pub struct CryptoHandshakeResponsePacket { pub key: CryptoPublicKey, } -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, StaticSize)] #[packet(id = 20002, encrypted = false)] pub struct KeepaliveResponsePacket { pub player_count: u32, } -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, DynamicSize)] #[packet(id = 20003, encrypted = false)] -pub struct ServerDisconnectPacket { - pub message: FastString, +pub struct ServerDisconnectPacket<'a> { + pub message: &'a str, } -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, StaticSize)] #[packet(id = 20004, encrypted = false)] pub struct LoggedInPacket { pub tps: u32, } -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, DynamicSize)] #[packet(id = 20005, encrypted = false)] -pub struct LoginFailedPacket { - pub message: FastString, +pub struct LoginFailedPacket<'a> { + pub message: &'a str, } // used to communicate a simple message to the user -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, DynamicSize)] #[packet(id = 20006, encrypted = false)] -pub struct ServerNoticePacket { - pub message: FastString, +pub struct ServerNoticePacket<'a> { + pub message: &'a str, +} + +#[derive(Packet, Encodable, StaticSize)] +#[packet(id = 20007, encrypted = false)] +pub struct ProtocolMismatchPacket { + pub protocol: u16, } diff --git a/server/game/src/data/packets/server/game.rs b/server/game/src/data/packets/server/game.rs index 3a265dd6..e2202b4a 100644 --- a/server/game/src/data/packets/server/game.rs +++ b/server/game/src/data/packets/server/game.rs @@ -12,14 +12,14 @@ pub struct PlayerProfilesPacket; #[packet(id = 22001, encrypted = false)] pub struct LevelDataPacket; -#[derive(Packet, Encodable)] +#[derive(Packet, Encodable, DynamicSize)] #[packet(id = 22010, encrypted = true)] pub struct VoiceBroadcastPacket { pub player_id: i32, pub data: FastEncodedAudioFrame, } -#[derive(Clone, Packet, Encodable, KnownSize)] +#[derive(Clone, Packet, Encodable, StaticSize)] #[packet(id = 22011, encrypted = true)] pub struct ChatMessageBroadcastPacket { pub player_id: i32, diff --git a/server/game/src/data/packets/server/general.rs b/server/game/src/data/packets/server/general.rs index de74e638..0e3f2851 100644 --- a/server/game/src/data/packets/server/general.rs +++ b/server/game/src/data/packets/server/general.rs @@ -9,17 +9,17 @@ use crate::data::*; #[packet(id = 21000, encrypted = false)] pub struct GlobalPlayerListPacket; -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, StaticSize)] #[packet(id = 21001, encrypted = false)] pub struct RoomCreatedPacket { pub room_id: u32, } -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, StaticSize)] #[packet(id = 21002, encrypted = false)] pub struct RoomJoinedPacket; -#[derive(Packet, Encodable, KnownSize)] +#[derive(Packet, Encodable, StaticSize)] #[packet(id = 21003, encrypted = false)] pub struct RoomJoinFailedPacket; diff --git a/server/game/src/data/types/audio_frame.rs b/server/game/src/data/types/audio_frame.rs index c4e50d48..a2c761c8 100644 --- a/server/game/src/data/types/audio_frame.rs +++ b/server/game/src/data/types/audio_frame.rs @@ -10,7 +10,7 @@ pub struct EncodedAudioFrame { } /// `FastEncodedAudioFrame` requires just one heap allocation as opposed to 10. -#[derive(Clone, Encodable, Decodable)] +#[derive(Clone, Encodable, Decodable, DynamicSize)] pub struct FastEncodedAudioFrame { pub data: RemainderBytes, } diff --git a/server/game/src/data/types/cocos.rs b/server/game/src/data/types/cocos.rs index 416e1146..c716f0c2 100644 --- a/server/game/src/data/types/cocos.rs +++ b/server/game/src/data/types/cocos.rs @@ -26,7 +26,7 @@ impl From for ColorParseError { } } -#[derive(Copy, Clone, Default, Encodable, Decodable, KnownSize)] +#[derive(Copy, Clone, Default, Encodable, Decodable, StaticSize)] pub struct Color3B { pub r: u8, pub g: u8, @@ -53,7 +53,7 @@ impl FromStr for Color3B { } } -#[derive(Copy, Clone, Default, Encodable, KnownSize, Decodable)] +#[derive(Copy, Clone, Default, Encodable, StaticSize, Decodable)] pub struct Color4B { pub r: u8, pub g: u8, @@ -86,7 +86,7 @@ impl FromStr for Color4B { } } -#[derive(Copy, Clone, Default, Encodable, KnownSize, Decodable)] +#[derive(Copy, Clone, Default, Encodable, StaticSize, Decodable)] pub struct Point { pub x: f32, pub y: f32, diff --git a/server/game/src/data/types/crypto.rs b/server/game/src/data/types/crypto.rs index 0afe95c6..8c3b0b31 100644 --- a/server/game/src/data/types/crypto.rs +++ b/server/game/src/data/types/crypto.rs @@ -15,7 +15,7 @@ encode_impl!(CryptoPublicKey, buf, self, { buf.write_bytes(self.0.as_bytes()); }); -size_calc_impl!(CryptoPublicKey, KEY_SIZE); +static_size_calc_impl!(CryptoPublicKey, KEY_SIZE); decode_impl!(CryptoPublicKey, buf, { let mut key = [0u8; KEY_SIZE]; diff --git a/server/game/src/data/types/gd.rs b/server/game/src/data/types/gd.rs index f24acff0..451b6ba4 100644 --- a/server/game/src/data/types/gd.rs +++ b/server/game/src/data/types/gd.rs @@ -4,7 +4,7 @@ use crate::data::*; use super::{Color3B, ColorParseError}; -#[derive(Clone, Encodable, KnownSize, Decodable)] +#[derive(Clone, Encodable, Decodable, StaticSize)] pub struct PlayerIconData { pub cube: i16, pub ship: i16, @@ -48,7 +48,7 @@ impl PlayerIconData { /* SpecialUserData */ -#[derive(Clone, Encodable, KnownSize, Decodable)] +#[derive(Clone, Encodable, Decodable, StaticSize)] pub struct SpecialUserData { pub name_color: Color3B, } @@ -72,7 +72,7 @@ impl TryFrom for SpecialUserData { /* PlayerAccountData */ -#[derive(Clone, Default, Encodable, KnownSize, Decodable)] +#[derive(Clone, Default, Encodable, Decodable, StaticSize)] pub struct PlayerAccountData { pub account_id: i32, pub name: FastString, @@ -105,7 +105,7 @@ impl PlayerAccountData { /* PlayerPreviewAccountData - like PlayerAccountData but more limited, for the total player list */ -#[derive(Clone, Default, Encodable, KnownSize, Decodable)] +#[derive(Clone, Default, Encodable, Decodable, StaticSize)] pub struct PlayerPreviewAccountData { pub account_id: i32, pub name: FastString, @@ -116,7 +116,7 @@ pub struct PlayerPreviewAccountData { /* PlayerRoomPreviewAccountData - similar to previous one but for rooms, the only difference is that it includes a level ID */ -#[derive(Clone, Default, Encodable, KnownSize, Decodable)] +#[derive(Clone, Default, Encodable, Decodable, StaticSize)] pub struct PlayerRoomPreviewAccountData { pub account_id: i32, pub name: FastString, @@ -128,7 +128,7 @@ pub struct PlayerRoomPreviewAccountData { /* IconType */ -#[derive(Debug, Copy, Clone, Encodable, Decodable, KnownSize)] +#[derive(Debug, Copy, Clone, Encodable, Decodable, StaticSize)] #[repr(u8)] pub enum IconType { Unknown = 0, @@ -145,7 +145,7 @@ pub enum IconType { /* PlayerData (data in a level) */ -#[derive(Clone, Default, Encodable, KnownSize, Decodable)] +#[derive(Clone, Default, Encodable, Decodable, StaticSize)] pub struct PlayerData { pub percentage: u16, pub attempts: i32, @@ -153,7 +153,7 @@ pub struct PlayerData { /* AssociatedPlayerData */ -#[derive(Clone, Default, Encodable, KnownSize, Decodable)] +#[derive(Clone, Default, Encodable, Decodable, StaticSize)] pub struct AssociatedPlayerData { pub account_id: i32, pub data: PlayerData, diff --git a/server/game/src/server_thread/handlers/connection.rs b/server/game/src/server_thread/handlers/connection.rs index 70ed723d..702acbba 100644 --- a/server/game/src/server_thread/handlers/connection.rs +++ b/server/game/src/server_thread/handlers/connection.rs @@ -9,7 +9,7 @@ use crate::data::*; impl GameServerThread { gs_handler!(self, handle_ping, PingPacket, packet, { - self.send_packet_fast(&PingResponsePacket { + self.send_packet_static(&PingResponsePacket { id: packet.id, player_count: self.game_server.state.player_count.load(Ordering::Relaxed), }) @@ -17,25 +17,13 @@ impl GameServerThread { }); gs_handler!(self, handle_crypto_handshake, CryptoHandshakeStartPacket, packet, { - match packet.protocol { - p if p > PROTOCOL_VERSION => { - gs_disconnect!( - self, - format!( - "Outdated server! You are running protocol v{p} while the server is still on v{PROTOCOL_VERSION}.", - ) - .try_into()? - ); - } - p if p < PROTOCOL_VERSION => { - gs_disconnect!( - self, - format!( - "Outdated client! Please update the mod in order to connect to the server. Client protocol version: v{p}, server: v{PROTOCOL_VERSION}", - ).try_into()? - ); - } - _ => {} + if packet.protocol != PROTOCOL_VERSION { + self.terminate(); + self.send_packet_static(&ProtocolMismatchPacket { + protocol: PROTOCOL_VERSION, + }) + .await?; + return Ok(()); } { @@ -43,10 +31,8 @@ impl GameServerThread { // erroring here is not a concern, even if the user's game crashes without a disconnect packet, // they would have a new randomized port when they restart and this would never fail. if self.crypto_box.get().is_some() { - self.disconnect(FastString::from_str( - "attempting to perform a second handshake in one session", - )) - .await?; + self.disconnect("attempting to perform a second handshake in one session") + .await?; return Err(PacketHandlingError::WrongCryptoBoxState); } @@ -54,7 +40,7 @@ impl GameServerThread { .get_or_init(|| ChaChaBox::new(&packet.key.0, &self.game_server.secret_key)); } - self.send_packet_fast(&CryptoHandshakeResponsePacket { + self.send_packet_static(&CryptoHandshakeResponsePacket { key: self.game_server.public_key.clone().into(), }) .await @@ -63,7 +49,7 @@ impl GameServerThread { gs_handler!(self, handle_keepalive, KeepalivePacket, _packet, { let _ = gs_needauth!(self); - self.send_packet_fast(&KeepaliveResponsePacket { + self.send_packet_static(&KeepaliveResponsePacket { player_count: self.game_server.state.player_count.load(Ordering::Relaxed), }) .await @@ -74,7 +60,7 @@ impl GameServerThread { 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.") + "The server is currently under maintenance, please try connecting again later." ); } @@ -92,10 +78,15 @@ impl GameServerThread { Ok(x) => FastString::from_str(&x), Err(err) => { self.terminate(); - let mut message = FastString::from_str("authentication failed: "); + + let mut message = FastString::<80>::from_str("authentication failed: "); message.extend(err.error_message()); // no need to use extend_safe as the messages are pretty short - self.send_packet_fast(&LoginFailedPacket { message }).await?; + self.send_packet_dynamic(&LoginFailedPacket { + // safety: we have created the string ourselves, we know for certain it is valid UTF-8. + message: unsafe { message.to_str_unchecked() }, + }) + .await?; return Ok(()); } } @@ -139,7 +130,7 @@ impl GameServerThread { .create_player(packet.account_id); let tps = self.game_server.central_conf.lock().tps; - self.send_packet_fast(&LoggedInPacket { tps }).await?; + self.send_packet_static(&LoggedInPacket { tps }).await?; Ok(()) }); diff --git a/server/game/src/server_thread/handlers/general.rs b/server/game/src/server_thread/handlers/general.rs index 05beee79..ca34e1f2 100644 --- a/server/game/src/server_thread/handlers/general.rs +++ b/server/game/src/server_thread/handlers/general.rs @@ -61,14 +61,14 @@ impl GameServerThread { self.room_id.store(room_id, Ordering::Relaxed); } - self.send_packet_fast(&RoomCreatedPacket { room_id }).await + self.send_packet_static(&RoomCreatedPacket { room_id }).await }); gs_handler!(self, handle_join_room, JoinRoomPacket, packet, { let account_id = gs_needauth!(self); if !self.game_server.state.room_manager.is_valid_room(packet.room_id) { - return self.send_packet_fast(&RoomJoinFailedPacket).await; + return self.send_packet_static(&RoomJoinFailedPacket).await; } let old_room_id = self.room_id.swap(packet.room_id, Ordering::Relaxed); @@ -86,7 +86,7 @@ impl GameServerThread { pm.create_player(account_id); }); - self.send_packet_fast(&RoomJoinedPacket).await + self.send_packet_static(&RoomJoinedPacket).await }); gs_handler!(self, handle_leave_room, LeaveRoomPacket, _packet, { diff --git a/server/game/src/server_thread/handlers/mod.rs b/server/game/src/server_thread/handlers/mod.rs index f747f860..c1add2d9 100644 --- a/server/game/src/server_thread/handlers/mod.rs +++ b/server/game/src/server_thread/handlers/mod.rs @@ -43,7 +43,9 @@ macro_rules! gs_disconnect { /// send a `ServerNoticePacket` to the client with the given message macro_rules! gs_notice { ($self:expr, $msg:expr) => { - $self.send_packet_fast(&ServerNoticePacket { message: $msg }).await?; + $self + .send_packet_fast_dynamic(&ServerNoticePacket { message: $msg }) + .await?; }; } @@ -53,7 +55,7 @@ macro_rules! gs_needauth { let account_id = $self.account_id.load(Ordering::Relaxed); if account_id == 0 { - gs_disconnect!($self, FastString::from_str("unauthorized, please try connecting again")); + gs_disconnect!($self, "unauthorized, please try connecting again"); } account_id diff --git a/server/game/src/server_thread/mod.rs b/server/game/src/server_thread/mod.rs index d9d95520..ec94bdf2 100644 --- a/server/game/src/server_thread/mod.rs +++ b/server/game/src/server_thread/mod.rs @@ -9,9 +9,9 @@ use std::{ use parking_lot::Mutex as SyncMutex; -use esp::{ByteBuffer, ByteReader}; +use esp::ByteReader; use globed_shared::crypto_box::{ - aead::{Aead, AeadCore, AeadInPlace, OsRng}, + aead::{AeadCore, AeadInPlace, OsRng}, ChaChaBox, }; use globed_shared::logger::*; @@ -110,7 +110,7 @@ impl GameServerThread { /* private utilities */ - /// the error printing is different in release and debug. some errors have higher severity than others. + // the error printing is different in release and debug. some errors have higher severity than others. fn print_error(&self, error: &PacketHandlingError) { if cfg!(debug_assertions) { warn!("[{} @ {}] err: {}", self.account_id.load(Ordering::Relaxed), self.peer, error); @@ -146,7 +146,6 @@ impl GameServerThread { } } - #[inline] #[cfg(debug_assertions)] fn print_packet(&self, sending: bool, ptype: Option<&str>) { trace!( @@ -169,33 +168,29 @@ impl GameServerThread { #[cfg(not(debug_assertions))] fn print_packet(&self, _sending: bool, _ptype: Option<&str>) {} - /// call `self.terminate()` and send a message to the user - async fn disconnect(&self, message: FastString) -> Result<()> { + /// call `self.terminate()` and send a message to the user with the reason + async fn disconnect<'a>(&self, message: &'a str) -> Result<()> { self.terminate(); - self.send_packet_fast(&ServerDisconnectPacket { message }).await + self.send_packet_dynamic(&ServerDisconnectPacket { message }).await } - /// send a packet normally. with zero extra optimizations. like a sane person typically would. - #[allow(dead_code)] - async fn send_packet(&self, packet: &P) -> Result<()> { - self.print_packet::

(true, None); - - let serialized = self.serialize_packet(packet)?; - self.send_buffer(serialized.as_bytes()).await + /// fast packet sending with best-case zero heap allocation. requires the packet to implement `StaticSize`. + /// if the packet size isn't known at compile time, derive/implement `DynamicSize` and use `send_packet_dynamic` instead. + async fn send_packet_static(&self, packet: &P) -> Result<()> { + // in theory, the size is known at compile time, so we could use a stack array here, instead of using alloca. + // however in practice, the performance difference is negligible, so we avoid code unnecessary code repetition. + self.send_packet_alloca(packet, P::ENCODED_SIZE).await } - /// fast packet sending with best-case zero heap allocation. - /// if the packet size isn't known at compile time, calculate it and use `send_packet_fast_rough` - async fn send_packet_fast(&self, packet: &P) -> Result<()> { - // in theory, the size is known at compile time, so we could use a stack array here, - // instead of calling `send_packet_fast_rough` which uses alloca. - // however in practice, the performance difference is negligible, so we avoid code unnecessary code repetition. - self.send_packet_fast_rough(packet, P::ENCODED_SIZE).await + /// version of `send_packet_static` that does not require the size to be known at compile time. + /// you are still required to derive/implement `DynamicSize` so the size can be computed at runtime. + async fn send_packet_dynamic(&self, packet: &P) -> Result<()> { + self.send_packet_alloca(packet, packet.encoded_size()).await } - /// like `send_packet_fast` but without the size being known at compile time. - /// 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<()> { + /// use alloca to encode the packet on the stack, and try a non-blocking send, on failure clones to a Vec and a blocking send. + /// be very careful if using this directly, miscalculating the size will cause a runtime panic. + async fn send_packet_alloca(&self, packet: &P, packet_size: usize) -> Result<()> { if P::PACKET_ID != KeepaliveResponsePacket::PACKET_ID { self.print_packet::

(true, Some(if P::ENCRYPTED { "fast + encrypted" } else { "fast" })); } @@ -283,37 +278,6 @@ impl GameServerThread { }) } - /// serialize (and potentially encrypt) a packet, returning a `ByteBuffer` with the output data - #[allow(dead_code)] - fn serialize_packet(&self, packet: &P) -> Result { - let mut buf = ByteBuffer::new(); - buf.write_packet_header::

(); - - if !P::ENCRYPTED { - buf.write_value(packet); - return Ok(buf); - } - - // this unwrap is safe, as an encrypted packet can only be sent downstream after the handshake is established. - let cbox = self.crypto_box.get().unwrap(); - - // encode the packet into a temp buf, encrypt the data and write to the initial buf - - let mut cltxtbuf = ByteBuffer::new(); - cltxtbuf.write_value(packet); - - let nonce = ChaChaBox::generate_nonce(&mut OsRng); - - let encrypted = cbox - .encrypt(&nonce, cltxtbuf.as_bytes()) - .map_err(|_| PacketHandlingError::EncryptionError)?; - - buf.write_bytes(&nonce); - buf.write_bytes(&encrypted); - - Ok(buf) - } - fn is_chat_packet_allowed(&self, voice: bool, len: usize) -> bool { let accid = self.account_id.load(Ordering::Relaxed); if accid == 0 { @@ -361,12 +325,9 @@ impl GameServerThread { match message { ServerThreadMessage::Packet(mut data) => self.handle_packet(&mut data).await?, ServerThreadMessage::SmallPacket((mut data, len)) => self.handle_packet(&mut data[..len as usize]).await?, - ServerThreadMessage::BroadcastText(text_packet) => self.send_packet_fast(&text_packet).await?, - ServerThreadMessage::BroadcastVoice(voice_packet) => { - let calc_size = voice_packet.data.data.len() + size_of_types!(u32); - self.send_packet_fast_rough(&*voice_packet, calc_size).await?; - } - ServerThreadMessage::TerminationNotice(message) => self.disconnect(message).await?, + ServerThreadMessage::BroadcastText(text_packet) => self.send_packet_static(&text_packet).await?, + ServerThreadMessage::BroadcastVoice(voice_packet) => self.send_packet_dynamic(&*voice_packet).await?, + ServerThreadMessage::TerminationNotice(message) => self.disconnect(message.try_to_str()).await?, } Ok(()) diff --git a/server/protocol.md b/server/protocol.md index febf8650..5950816d 100644 --- a/server/protocol.md +++ b/server/protocol.md @@ -4,18 +4,12 @@ if you somehow stumbled upon this file, hi! this is a brief protocol description `+` - encrypted packet. -`!` - this is just a planned packet and there is no existing implementation, at all. +`!` - this packet is unused and may be removed completely in the future -`?` - the handler needs to be looked into because some things were changed elsewhere that broke it - -`&` - structure exists but no implementation or handling - -`^` - handled server-side but no client implementation +`^` - this packet is not fully functional and work needs to be done on either the client side or the server side i will probably forget to update this very often -rn all question marks are regarding rooms - ### Client Connection related @@ -28,8 +22,8 @@ Connection related General -* 11000^ - SyncIconsPacket - store client's icons -* 11001 - RequestGlobalPlayerListPacket - request list of all people in the server (response 21000) +* 11000 - SyncIconsPacket - store client's icons +* 11001! - RequestGlobalPlayerListPacket - request list of all people in the server (response 21000) * 11002^ - CreateRoomPacket - create a room * 11003^ - JoinRoomPacket - join a room * 11004^ - LeaveRoomPacket - leave a room (no need for a response) @@ -42,7 +36,7 @@ Game related * 12002 - LevelLeavePacket - leave a level * 12003 - PlayerDataPacket - player data * 12010+ - VoicePacket - voice frame -* 12011?^+ - ChatMessagePacket - chat message +* 12011^+ - ChatMessagePacket - chat message ### Server @@ -55,15 +49,16 @@ Connection related * 20003 - ServerDisconnectPacket - server kicked you out * 20004 - LoggedInPacket - successful auth * 20005 - LoginFailedPacket - bad auth (has error message) -* 20006 - ServerNoticePacket - message popup for the user +* 20006! - ServerNoticePacket - message popup for the user +* 20007 - ProtocolMismatchPacket - protocol version mismatch General -* 21000 - GlobalPlayerListPacket - list of people in the server +* 21000! - GlobalPlayerListPacket - list of people in the server * 21001^ - RoomCreatedPacket - returns room id (returns existing one if already in a room) * 21002^ - RoomJoinedPacket - returns nothing ig?? just indicates success * 21003^ - RoomJoinFailedPacket - also nothing, the only possible error is no such room id exists -* 21004^ - RoomPlayerListPacket - list of people in the room +* 21004 - RoomPlayerListPacket - list of people in the room Game related diff --git a/server/shared/src/token_issuer.rs b/server/shared/src/token_issuer.rs index 320dcacd..faf1707b 100644 --- a/server/shared/src/token_issuer.rs +++ b/server/shared/src/token_issuer.rs @@ -21,7 +21,7 @@ pub enum TokenValidationFailure { } impl TokenValidationFailure { - pub fn error_message(&self) -> &'static str { + pub const fn error_message(&self) -> &'static str { match self { Self::Missing => "no token provided", Self::MalformedStructure => "malformed token structure", diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 986d8c9a..f605d1e5 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -4,11 +4,11 @@ #include -// this is horrible i know yes #define FMOD_ERR_CHECK(res, msg) \ - auto GEODE_CONCAT(__evcond, __LINE__) = (res); \ - if (GEODE_CONCAT(__evcond, __LINE__) != FMOD_OK) \ - GLOBED_REQUIRE(false, GlobedAudioManager::formatFmodError(GEODE_CONCAT(__evcond, __LINE__), msg)); + do { \ + auto _res = (res); \ + GLOBED_REQUIRE(_res == FMOD_OK, GlobedAudioManager::formatFmodError(_res, msg)); \ + } while (0); \ GlobedAudioManager::GlobedAudioManager() : encoder(VOICE_TARGET_SAMPLERATE, VOICE_TARGET_FRAMESIZE, VOICE_CHANNELS) { diff --git a/src/data/packets/client/connection.hpp b/src/data/packets/client/connection.hpp index f739db7b..60526d43 100644 --- a/src/data/packets/client/connection.hpp +++ b/src/data/packets/client/connection.hpp @@ -7,7 +7,6 @@ class PingPacket : public Packet { GLOBED_PACKET(10000, false) GLOBED_PACKET_ENCODE { buf.writeU32(id); } - GLOBED_PACKET_DECODE_UNIMPL PingPacket(uint32_t _id) : id(_id) {} @@ -26,8 +25,6 @@ class CryptoHandshakeStartPacket : public Packet { buf.writeValue(key); } - GLOBED_PACKET_DECODE_UNIMPL - CryptoHandshakeStartPacket(uint16_t _protocol, CryptoPublicKey _key) : protocol(_protocol), key(_key) {} static std::shared_ptr create(uint16_t protocol, CryptoPublicKey key) { @@ -42,7 +39,6 @@ class KeepalivePacket : public Packet { GLOBED_PACKET(10002, false) GLOBED_PACKET_ENCODE {} - GLOBED_PACKET_DECODE_UNIMPL KeepalivePacket() {} static std::shared_ptr create() { @@ -60,8 +56,6 @@ class LoginPacket : public Packet { buf.writeValue(icons); } - GLOBED_PACKET_DECODE_UNIMPL - LoginPacket(int32_t accid, const std::string& name, const std::string& token, const PlayerIconData& icons) : accountId(accid), name(name), token(token), icons(icons) {} @@ -79,7 +73,6 @@ class DisconnectPacket : public Packet { GLOBED_PACKET(10004, false) GLOBED_PACKET_ENCODE {} - GLOBED_PACKET_DECODE_UNIMPL DisconnectPacket() {} static std::shared_ptr create() { diff --git a/src/data/packets/client/game.hpp b/src/data/packets/client/game.hpp index 446225aa..90cc49fa 100644 --- a/src/data/packets/client/game.hpp +++ b/src/data/packets/client/game.hpp @@ -9,8 +9,6 @@ class RequestPlayerProfilesPacket : public Packet { buf.writeI32(requested); } - GLOBED_PACKET_DECODE_UNIMPL - RequestPlayerProfilesPacket(int requested) : requested(requested) {} static std::shared_ptr create(int requested) { @@ -27,8 +25,6 @@ class LevelJoinPacket : public Packet { buf.writeI32(levelId); } - GLOBED_PACKET_DECODE_UNIMPL - LevelJoinPacket(int levelId) : levelId(levelId) {} static std::shared_ptr create(int levelId) { @@ -42,7 +38,6 @@ class LevelLeavePacket : public Packet { GLOBED_PACKET(12002, false) GLOBED_PACKET_ENCODE {} - GLOBED_PACKET_DECODE_UNIMPL LevelLeavePacket() {} @@ -58,8 +53,6 @@ class PlayerDataPacket : public Packet { buf.writeValue(data); } - GLOBED_PACKET_DECODE_UNIMPL - PlayerDataPacket(const PlayerData& data) : data(data) {} static std::shared_ptr create(const PlayerData& data) { @@ -80,8 +73,6 @@ class VoicePacket : public Packet { buf.writeValue(*frame.get()); } - GLOBED_PACKET_DECODE_UNIMPL - VoicePacket(std::shared_ptr _frame) : frame(std::move(_frame)) {} static std::shared_ptr create(std::shared_ptr frame) { @@ -100,8 +91,6 @@ class ChatMessagePacket : public Packet { buf.writeString(message); } - GLOBED_PACKET_DECODE_UNIMPL - ChatMessagePacket(const std::string& message) : message(message) {} static std::shared_ptr create(const std::string& message) { diff --git a/src/data/packets/client/general.hpp b/src/data/packets/client/general.hpp index 88b40a14..d9359bc1 100644 --- a/src/data/packets/client/general.hpp +++ b/src/data/packets/client/general.hpp @@ -6,7 +6,6 @@ class SyncIconsPacket : public Packet { GLOBED_PACKET(11000, false) GLOBED_PACKET_ENCODE { buf.writeValue(icons); } - GLOBED_PACKET_DECODE_UNIMPL SyncIconsPacket(const PlayerIconData& icons) : icons(icons) {} @@ -21,7 +20,6 @@ class RequestGlobalPlayerListPacket : public Packet { GLOBED_PACKET(11001, false) GLOBED_PACKET_ENCODE {} - GLOBED_PACKET_DECODE_UNIMPL RequestGlobalPlayerListPacket() {} @@ -34,7 +32,6 @@ class CreateRoomPacket : public Packet { GLOBED_PACKET(11002, false) GLOBED_PACKET_ENCODE {} - GLOBED_PACKET_DECODE_UNIMPL CreateRoomPacket() {} @@ -47,7 +44,6 @@ class JoinRoomPacket : public Packet { GLOBED_PACKET(11003, false) GLOBED_PACKET_ENCODE { buf.writeU32(roomId); } - GLOBED_PACKET_DECODE_UNIMPL JoinRoomPacket(uint32_t roomId) : roomId(roomId) {} @@ -62,7 +58,6 @@ class LeaveRoomPacket : public Packet { GLOBED_PACKET(11004, false) GLOBED_PACKET_ENCODE {} - GLOBED_PACKET_DECODE_UNIMPL LeaveRoomPacket() {} @@ -75,7 +70,6 @@ class RequestRoomPlayerListPacket : public Packet { GLOBED_PACKET(11005, false) GLOBED_PACKET_ENCODE {} - GLOBED_PACKET_DECODE_UNIMPL RequestRoomPlayerListPacket() {} diff --git a/src/data/packets/client/misc.hpp b/src/data/packets/client/misc.hpp index 45f46ca0..0d687b1c 100644 --- a/src/data/packets/client/misc.hpp +++ b/src/data/packets/client/misc.hpp @@ -22,8 +22,6 @@ class RawPacket : public Packet { buf.writeBytes(buffer.getDataRef()); } - GLOBED_PACKET_DECODE_UNIMPL - static std::shared_ptr create(packetid_t id, bool encrypted, ByteBuffer&& buffer) { return std::make_shared(id, encrypted, std::move(buffer)); } diff --git a/src/data/packets/packet.hpp b/src/data/packets/packet.hpp index 52e6b462..0d99e987 100644 --- a/src/data/packets/packet.hpp +++ b/src/data/packets/packet.hpp @@ -9,16 +9,6 @@ using packetid_t = uint16_t; packetid_t getPacketId() const override { return this->PACKET_ID; } \ bool getEncrypted() const override { return this->ENCRYPTED; } -#define GLOBED_PACKET_ENCODE_UNIMPL \ - GLOBED_PACKET_ENCODE { \ - GLOBED_UNIMPL(std::string("Encoding unimplemented for packet ") + std::to_string(getPacketId())) \ - } - -#define GLOBED_PACKET_DECODE_UNIMPL \ - GLOBED_PACKET_DECODE { \ - GLOBED_UNIMPL(std::string("Decoding unimplemented for packet ") + std::to_string(getPacketId())) \ - } - #define GLOBED_PACKET_ENCODE void encode(ByteBuffer& buf) const override #define GLOBED_PACKET_DECODE void decode(ByteBuffer& buf) override @@ -26,9 +16,14 @@ class Packet { public: virtual ~Packet() {} // Encodes the packet into a bytebuffer - virtual void encode(ByteBuffer& buf) const = 0; + virtual void encode(ByteBuffer& buf) const { + GLOBED_UNIMPL(std::string("Encoding unimplemented for packet ") + std::to_string(this->getPacketId())) + }; + // Decodes the packet from a bytebuffer - virtual void decode(ByteBuffer& buf) = 0; + virtual void decode(ByteBuffer& buf) { + GLOBED_UNIMPL(std::string("Decoding unimplemented for packet ") + std::to_string(this->getPacketId())) + }; virtual packetid_t getPacketId() const = 0; virtual bool getEncrypted() const = 0; diff --git a/src/data/packets/server/connection.hpp b/src/data/packets/server/connection.hpp index 9d9df142..cf918d61 100644 --- a/src/data/packets/server/connection.hpp +++ b/src/data/packets/server/connection.hpp @@ -5,7 +5,6 @@ class PingResponsePacket : public Packet { GLOBED_PACKET(20000, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { id = buf.readU32(); playerCount = buf.readU32(); @@ -17,7 +16,6 @@ class PingResponsePacket : public Packet { class CryptoHandshakeResponsePacket : public Packet { GLOBED_PACKET(20001, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { data = buf.readValue(); } CryptoPublicKey data; @@ -26,7 +24,6 @@ class CryptoHandshakeResponsePacket : public Packet { class KeepaliveResponsePacket : public Packet { GLOBED_PACKET(20002, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { playerCount = buf.readU32(); } uint32_t playerCount; @@ -35,7 +32,6 @@ class KeepaliveResponsePacket : public Packet { class ServerDisconnectPacket : public Packet { GLOBED_PACKET(20003, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { message = buf.readString(); } std::string message; @@ -44,7 +40,6 @@ class ServerDisconnectPacket : public Packet { class LoggedInPacket : public Packet { GLOBED_PACKET(20004, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { tps = buf.readU32(); } uint32_t tps; @@ -53,7 +48,6 @@ class LoggedInPacket : public Packet { class LoginFailedPacket : public Packet { GLOBED_PACKET(20005, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { message = buf.readString(); } std::string message; @@ -62,8 +56,15 @@ class LoginFailedPacket : public Packet { class ServerNoticePacket : public Packet { GLOBED_PACKET(20006, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { message = buf.readString(); } std::string message; -}; \ No newline at end of file +}; + +class ProtocolMismatchPacket : public Packet { + GLOBED_PACKET(20007, false) + + GLOBED_PACKET_DECODE { serverProtocol = buf.readU16(); } + + uint16_t serverProtocol; +}; diff --git a/src/data/packets/server/game.hpp b/src/data/packets/server/game.hpp index ee24857f..26d6fd70 100644 --- a/src/data/packets/server/game.hpp +++ b/src/data/packets/server/game.hpp @@ -5,7 +5,6 @@ class PlayerProfilesPacket : public Packet { GLOBED_PACKET(22000, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { players = buf.readValueVector(); } @@ -16,7 +15,6 @@ class PlayerProfilesPacket : public Packet { class LevelDataPacket : public Packet { GLOBED_PACKET(22001, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { players = buf.readValueVector(); } @@ -31,8 +29,6 @@ class LevelDataPacket : public Packet { class VoiceBroadcastPacket : public Packet { GLOBED_PACKET(22010, true) - GLOBED_PACKET_ENCODE_UNIMPL - #if GLOBED_VOICE_SUPPORT GLOBED_PACKET_DECODE { sender = buf.readI32(); @@ -49,7 +45,6 @@ class VoiceBroadcastPacket : public Packet { class ChatMessageBroadcastPacket : public Packet { GLOBED_PACKET(22011, true) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { sender = buf.readI32(); message = buf.readString(); diff --git a/src/data/packets/server/general.hpp b/src/data/packets/server/general.hpp index e649a438..4d7a6d9e 100644 --- a/src/data/packets/server/general.hpp +++ b/src/data/packets/server/general.hpp @@ -5,7 +5,6 @@ class GlobalPlayerListPacket : public Packet { GLOBED_PACKET(21000, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { buf.readValueVectorInto(data); } std::vector data; @@ -14,7 +13,6 @@ class GlobalPlayerListPacket : public Packet { class RoomCreatedPacket : public Packet { GLOBED_PACKET(21001, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { roomId = buf.readU32(); } uint32_t roomId; @@ -22,20 +20,17 @@ class RoomCreatedPacket : public Packet { class RoomJoinedPacket : public Packet { GLOBED_PACKET(21002, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE {} }; class RoomJoinFailedPacket : public Packet { GLOBED_PACKET(21003, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE {} }; class RoomPlayerListPacket : public Packet { GLOBED_PACKET(21004, false) - GLOBED_PACKET_ENCODE_UNIMPL GLOBED_PACKET_DECODE { roomId = buf.readU32(); buf.readValueVectorInto(data); diff --git a/src/defs/util.hpp b/src/defs/util.hpp index c161a15b..cd67a51d 100644 --- a/src/defs/util.hpp +++ b/src/defs/util.hpp @@ -36,8 +36,6 @@ class SingletonBase { // adding -fexceptions doesn't work, and neither does anything else i've tried. // thanks to this workaround, whenever you throw an exception, it sets a global variable to the exception message string, // so that you can do `catch(...)` (shorthand - CATCH) but still get the exception message via shorthand `CATCH_GET_EXC`. -// That makes it not thread safe, and therefore not the best practice. -// But let's be honest, what are the odds of 2 exceptions occurring in two threads at the same time? ;) // // Note for future me if this doesn't magically get resolved in 2.2 - // when throwing a vanilla exception, the abort happens in __cxa_throw, which calls std::terminate() when stack unwinding fails. @@ -52,7 +50,7 @@ class SingletonBase { class _GExceptionStore { public: static void store_exc(const char* err) { - auto len = min(strlen(err), BUF_LEN - 1); + auto len = std::min(strlen(err), BUF_LEN - 1); strncpy(what, err, len); what[len] = '\0'; initialized = true; @@ -68,8 +66,8 @@ class _GExceptionStore { private: static constexpr size_t BUF_LEN = 512; - inline static char what[BUF_LEN]; - inline static bool initialized = false; + inline thread_local static char what[BUF_LEN]; + inline thread_local static bool initialized = false; }; # define THROW(x) { \ diff --git a/src/managers/settings.cpp b/src/managers/settings.cpp index 3cd0dc85..fd16e27f 100644 --- a/src/managers/settings.cpp +++ b/src/managers/settings.cpp @@ -1,32 +1,80 @@ #include "settings.hpp" +#define SKEY(sstr) "_gsetting-" #sstr +#define SFLAGKEY(sstr) "_gflag-" #sstr +#define STOREV(cat, sstr) \ + do { \ + static auto _defaultFor##cat##sstr = defaultFor(#sstr); \ + if ((cat.sstr) != _defaultFor##cat##sstr) { \ + this->store(SKEY(sstr), cat.sstr);\ + } else { \ + this->clear(SKEY(sstr));\ + } \ + } while (0) \ + +#define LOADV(cat, sstr) \ + this->loadOptionalInto(SKEY(sstr), cat.sstr) + +#define STOREF(sstr) this->store(SFLAGKEY(sstr), flags.sstr) +#define LOADF(sstr) this->loadOptionalInto(SFLAGKEY(sstr), flags.sstr) + +#define RESET_SETTINGS(...) \ + do { \ + static const char* args[] = { __VA_ARGS__ }; \ + for (const char* arg : args) { \ + this->clear(arg);\ + } \ + } while (0) \ + GlobedSettings::GlobedSettings() { - _cache.lock()->refresh(*this); + this->reload(); } -void GlobedSettings::reset(const std::string& key, bool refreshCache) { - this->setFlag("_gset_-" + key, false); - if (refreshCache) { - _cache.lock()->refresh(*this); - } -} +void GlobedSettings::save() { + // The reason we do macro fuckery is because we don't want to save values that haven't been changed by the user, + // aka are at their defaults. So if the defaults get changed in the future, the user will be affected by the change too. + + STOREV(globed, tpsCap); + STOREV(globed, audioDevice); + STOREV(globed, autoconnect); + + STOREV(communication, voiceEnabled); -GlobedSettings::CachedSettings GlobedSettings::getCached() { - return *_cache.lock(); + // store flags + + STOREF(seenSignupNotice); } -bool GlobedSettings::getFlag(const std::string& key) { - return geode::Mod::get()->getSavedValue("gflag-" + key) == 2; +void GlobedSettings::reload() { + LOADV(globed, tpsCap); + LOADV(globed, audioDevice); + LOADV(globed, autoconnect); + + LOADV(communication, voiceEnabled); + + // load flags + + LOADF(seenSignupNotice); } -void GlobedSettings::setFlag(const std::string& key, bool state) { - geode::Mod::get()->setSavedValue("gflag-" + key, static_cast(state ? 2 : 0)); +void GlobedSettings::resetToDefaults() { + RESET_SETTINGS( + SKEY(tpsCap), + SKEY(audioDevice), + SKEY(autoconnect), + + SKEY(voiceEnabled) + ); + + this->reload(); } -void GlobedSettings::CachedSettings::refresh(GlobedSettings& gs) { - this->globed.tpsCap = gs.getValue("tps-cap", 0); - this->globed.audioDevice = gs.getValue("audio-device", 0); - this->globed.autoconnect = gs.getValue("autoconnect", false); +void GlobedSettings::clear(const std::string& key) { + auto& container = geode::Mod::get()->getSaveContainer(); + auto& obj = container.as_object(); - this->communication.voiceEnabled = gs.getValue("comms-voice-enabled", true); -} \ No newline at end of file + if (obj.contains(key)) { + // we cant remove objects in matjson so we just set it to null + obj[key] = json::Value(nullptr); + } +} diff --git a/src/managers/settings.hpp b/src/managers/settings.hpp index df4c85a7..707d87b6 100644 --- a/src/managers/settings.hpp +++ b/src/managers/settings.hpp @@ -1,103 +1,93 @@ #pragma once #include -#include -#define MAKE_DEFAULT(_key, value) if (key == (_key)) return (value); +#define GSETTING(ty,name) ty name = defaultFor(#name) +#define GDEFAULT(name, val) if (std::string_view(#name) == std::string_view(key)) return val -// Besides `getCached()`, this class is not thread safe (reason: Mod::getSavedValue/Mod::setSavedValue) +// This class should only be accessed from the main thread. class GlobedSettings : GLOBED_SINGLETON(GlobedSettings) { public: - GlobedSettings(); - - struct CachedSettings; - - /* - * ok i know this is a mess but here's a rundown of how the settings work: - * say we have a setting that is an int called 'hello'. - * when retreiving, we first attempt to read `gflag-_gset_-hello` via getFlag() - * if the flag is false, we return a default value (defaults reside in `getValue`). - * otherwise, we return `gsetting-hello`. - * - * upon saving, we explicitly set the flag described earlier to true and save the value. - * i hope this isn't confusing! - */ + struct Globed { + GSETTING(uint32_t, tpsCap); + GSETTING(int, audioDevice); + GSETTING(bool, autoconnect); // todo unimpl + }; - // directly set and save the setting as json - template - inline void setValue(const std::string& key, const T& elem, bool refresh = true) { - this->setFlag("_gset_-" + key); - geode::Mod::get()->setSavedValue("gsetting-" + key, elem); + struct Overlay {}; + struct Communication { + GSETTING(bool, voiceEnabled); + }; - if (refresh) { - _cache.lock()->refresh(*this); - } - } + struct LevelUI {}; + struct Players {}; + struct Advanced {}; - // directly get the setting as json, or return a default value if not set - template - inline T getValue(const std::string& key, T defaultopt = T{}) { - if (this->getFlag("_gset_-" + key)) { - return geode::Mod::get()->getSavedValue("gsetting-" + key); - } else { - return defaultopt; - } - } + struct Flags { + bool seenSignupNotice = false; + }; - // reset a setting to its default value - void reset(const std::string& key, bool refreshCache = true); + Globed globed; + Overlay overlay; + Communication communication; + LevelUI levelUi; + Players players; + Advanced advanced; + Flags flags; - // cached settings for performance & thread safety - CachedSettings getCached(); + GlobedSettings(); - bool getFlag(const std::string& key); - void setFlag(const std::string& key, bool state = true); + void save(); + void reload(); + void resetToDefaults(); + void clear(const std::string& key); - class CachedSettings { - public: - void refresh(GlobedSettings&); +private: + template + static constexpr T defaultFor(const char* key) { + GDEFAULT(tpsCap, 0); + GDEFAULT(audioDevice, 0); + GDEFAULT(autoconnect, false); - struct Globed { - uint32_t tpsCap; - int audioDevice; - bool autoconnect; // TODO unimpl - }; + GDEFAULT(voiceEnabled, true); - struct Overlay {}; - struct Communication { - bool voiceEnabled; - }; + geode::log::warn("warning: invalid default opt was requested: {}", key); - struct LevelUI {}; - struct Players {}; - struct Advanced {}; + return {}; + } - Globed globed; - Overlay overlay; - Communication communication; - LevelUI levelUi; - Players players; - Advanced advanced; - }; + template + void store(const std::string& key, const T& val) { + geode::Mod::get()->setSavedValue(key, val); + } - // reset all settings to their default values - inline void resetAll() { - static const char* settings[] = { - "tps-cap", - "audio-device", - "autoconnect", + bool has(const std::string& key) { + return geode::Mod::get()->hasSavedValue(key) + && !geode::Mod::get()->getSaveContainer().as_object()[key].is_null(); + } - "comms-voice-enabled", - }; + template + T load(const std::string& key) { + return geode::Mod::get()->getSavedValue(key); + } - for (auto* setting : settings) { - this->reset(setting, false); + // If setting is present, loads into `into`. Otherwise does nothing. + template + void loadOptionalInto(const std::string& key, T& into) { + if (this->has(key)) { + auto val = this->load(key); } + } - _cache.lock()->refresh(*this); + template + std::optional loadOptional(const std::string& key) { + return this->has(key) ? this->load(key) : std::nullopt; } -private: - util::sync::WrappingMutex _cache; + template + T loadOrDefault(const std::string& key, const T defaultval) { + return this->has(key) ? this->load(key) : defaultval; + } }; -#undef MAKE_DEFAULT \ No newline at end of file +#undef GSETTING +#undef GDEFAULT \ No newline at end of file diff --git a/src/net/network_manager.cpp b/src/net/network_manager.cpp index 6b77f02e..6e093233 100644 --- a/src/net/network_manager.cpp +++ b/src/net/network_manager.cpp @@ -15,7 +15,7 @@ NetworkManager::NetworkManager() { if (!gameSocket.create()) util::net::throwLastError(); - // add builtin listeners + // add builtin listeners for connection related packets addBuiltinListener([this](auto packet) { gameSocket.box->setPeerKey(packet->data.key.data()); @@ -62,6 +62,24 @@ NetworkManager::NetworkManager() { ErrorQueues::get().notice(packet->message); }); + addBuiltinListener([this](auto packet) { + std::string message; + if (packet->serverProtocol < PROTOCOL_VERSION) { + message = fmt::format( + "Outdated server! This server uses protocol v{}, while your client is using protocol v{}. Downgrade the mod to an older version or ask the server owner to update their server.", + packet->serverProtocol, PROTOCOL_VERSION + ); + } else { + message = fmt::format( + "Outdated client! Please update the mod to the latest version in order to connect. The server is using protocol v{}, while this version of the mod only supports protocol v{}.", + packet->serverProtocol, PROTOCOL_VERSION + ); + } + + ErrorQueues::get().error(message); + this->disconnect(true); + }); + // boot up the threads threadMain = std::thread(&NetworkManager::threadMainFunc, this); diff --git a/src/ui/hooks/play_layer.hpp b/src/ui/hooks/play_layer.hpp index 17f5de91..513e8cd2 100644 --- a/src/ui/hooks/play_layer.hpp +++ b/src/ui/hooks/play_layer.hpp @@ -19,7 +19,7 @@ using namespace geode::prelude; class $modify(GlobedPlayLayer, PlayLayer) { bool globedReady; bool deafened = false; - GlobedSettings::CachedSettings settings; + GlobedSettings& settings = GlobedSettings::get(); uint32_t configuredTps = 0; /* speedhack detection */ @@ -31,8 +31,6 @@ class $modify(GlobedPlayLayer, PlayLayer) { bool init(GJGameLevel* level) { if (!PlayLayer::init(level)) return false; - m_fields->settings = GlobedSettings::get().getCached(); - auto& nm = NetworkManager::get(); // if not authenticated, do nothing diff --git a/src/ui/menu/main/signup_layer.cpp b/src/ui/menu/main/signup_layer.cpp index 547fd8d5..15710bfb 100644 --- a/src/ui/menu/main/signup_layer.cpp +++ b/src/ui/menu/main/signup_layer.cpp @@ -23,10 +23,12 @@ bool GlobedSignupLayer::init() { Build::create("Login", "goldFont.fnt", "GJ_button_01.png", 0.8f) .intoMenuItem([](auto) { - if (!GlobedSettings::get().getFlag("seen-signup-notice")) { - geode::createQuickPopup("Notice", CONSENT_MESSAGE, "Cancel", "Ok", [](auto, bool agreed){ + auto& gs = GlobedSettings::get(); + if (!gs.flags.seenSignupNotice) { + geode::createQuickPopup("Notice", CONSENT_MESSAGE, "Cancel", "Ok", [&gs](auto, bool agreed){ if (agreed) { - GlobedSettings::get().setFlag("seen-signup-notice"); + gs.flags.seenSignupNotice = true; + gs.save(); GlobedSignupPopup::create()->show(); } }); diff --git a/src/ui/menu/main/signup_popup.cpp b/src/ui/menu/main/signup_popup.cpp index b654c9f8..7279b97d 100644 --- a/src/ui/menu/main/signup_popup.cpp +++ b/src/ui/menu/main/signup_popup.cpp @@ -32,6 +32,7 @@ bool GlobedSignupPopup::setup() { GHTTPRequest::post(url) .userAgent(util::net::webUserAgent()) + .timeout(util::time::secs(5)) .then([this](const GHTTPResponse& resp) { if (resp.anyfail()) { auto error = resp.anyfailmsg(); @@ -125,6 +126,7 @@ void GlobedSignupPopup::onChallengeCompleted(const std::string& authcode) { GHTTPRequest::post(url) .userAgent(util::net::webUserAgent()) + .timeout(util::time::secs(5)) .then([this, &am](const GHTTPResponse& resp) { if (resp.anyfail()) { auto error = resp.anyfailmsg(); diff --git a/src/ui/menu/server_switcher/add_server_popup.cpp b/src/ui/menu/server_switcher/add_server_popup.cpp index ed7f2b4e..e4a903eb 100644 --- a/src/ui/menu/server_switcher/add_server_popup.cpp +++ b/src/ui/menu/server_switcher/add_server_popup.cpp @@ -6,6 +6,9 @@ #include "server_switcher_popup.hpp" #include "server_test_popup.hpp" #include +#include +#include +#include using namespace geode::prelude; @@ -102,6 +105,29 @@ void AddServerPopup::onTestSuccess() { if (modifyingIndex == -1) { csm.addServer(newServer); } else { + auto oldServer = csm.getServer(modifyingIndex); + + // if it's the server we are connected to right now, and it's a completely different URL, + // do essentially the same stuff we do when switching to another server (copied from server_cell.cpp) + if (modifyingIndex == csm.getActiveIndex() && oldServer.url != newServer.url) { + csm.recentlySwitched = true; + // clear the authtoken + auto& gam = GlobedAccountManager::get(); + gam.authToken.lock()->clear(); + + // clear game servers + auto& gsm = GameServerManager::get(); + gsm.clear(); + gsm.pendingChanges = true; + + // disconnect + auto& nm = NetworkManager::get(); + nm.disconnect(false); + + // initialize creds + gam.autoInitialize(); + } + csm.modifyServer(modifyingIndex, newServer); } diff --git a/src/util/crypto.cpp b/src/util/crypto.cpp index 3ae56a62..4e3c73e7 100644 --- a/src/util/crypto.cpp +++ b/src/util/crypto.cpp @@ -152,11 +152,13 @@ bool stringsEqual(const std::string& s1, const std::string& s2) { std::string base64Encode(const byte* source, size_t size, Base64Variant variant) { size_t length = sodium_base64_ENCODED_LEN(size, (int)variant); - char* out = new char[length]; - sodium_bin2base64(out, length, source, size, (int)variant); - auto ret = std::string(out); - delete[] out; + std::string ret; + ret.resize(length); + + sodium_bin2base64(ret.data(), length, source, size, (int)variant); + + ret.resize(length - 1); // get rid of the trailing null byte return ret; } @@ -196,12 +198,13 @@ bytevector base64Decode(const bytevector& source, Base64Variant variant) { std::string hexEncode(const byte* source, size_t size) { auto outLen = size * 2 + 1; - char* out = new char[outLen]; - sodium_bin2hex(out, outLen, source, size); + std::string ret; + ret.resize(outLen); + + sodium_bin2hex(ret.data(), outLen, source, size); - std::string ret(out); - delete[] out; + ret.resize(outLen - 1); // get rid of the trailing null byte return ret; } diff --git a/src/util/math.cpp b/src/util/math.cpp index 52d385e5..d3122e67 100644 --- a/src/util/math.cpp +++ b/src/util/math.cpp @@ -1,51 +1,4 @@ #include "math.hpp" namespace util::math { - float snan(float val) { - return std::isnan(val) ? snan() : val; - } - - float snan() { - return std::numeric_limits::signaling_NaN(); - } - - bool equal(float val1, float val2, float errorMargin) { - return std::fabs(val2 - val1) < errorMargin; - } - - bool equal(double val1, double val2, double errorMargin) { - return std::abs(val2 - val1) < errorMargin; - } - - bool greater(float val1, float val2, float errorMargin) { - return val1 > val2 && std::fabs(val2 - val1) > errorMargin; - } - - bool greater(double val1, double val2, double errorMargin) { - return val1 > val2 && std::abs(val2 - val1) > errorMargin; - } - - bool greaterOrEqual(float val1, float val2, float errorMargin) { - return val1 > val2 || equal(val1, val2, errorMargin); - } - - bool greaterOrEqual(double val1, double val2, double errorMargin) { - return val1 > val2 || equal(val1, val2, errorMargin); - } - - bool smaller(float val1, float val2, float errorMargin) { - return val1 < val2 && std::fabs(val2 - val1) > errorMargin; - } - - bool smaller(double val1, double val2, double errorMargin) { - return val1 < val2 && std::abs(val2 - val1) > errorMargin; - } - - bool smallerOrEqual(float val1, float val2, float errorMargin) { - return val1 < val2 || equal(val1, val2, errorMargin); - } - - bool smallerOrEqual(double val1, double val2, double errorMargin) { - return val1 < val2 || equal(val1, val2, errorMargin); - } } \ No newline at end of file diff --git a/src/util/math.hpp b/src/util/math.hpp index 888f8ad8..429ef389 100644 --- a/src/util/math.hpp +++ b/src/util/math.hpp @@ -5,10 +5,15 @@ namespace util::math { constexpr float FLOAT_ERROR_MARGIN = 0.002f; constexpr double DOUBLE_ERROR_MARGIN = 0.0001; - // if `val` is NaN, return a signaling NaN, otherwise return `val` unchanged - float snan(float val); // returns a signaling NaN - float snan(); + inline float snan() { + return std::numeric_limits::signaling_NaN(); + } + + // if `val` is NaN, return a signaling NaN, otherwise return `val` unchanged + inline float snan(float val) { + return std::isnan(val) ? snan() : val; + } // Returns `true` if all passed numbers are valid. Returns `false` if at least one of them is NaN template @@ -18,25 +23,45 @@ namespace util::math { } // `val1` == `val2` - bool equal(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN); + inline bool equal(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN) { + return std::fabs(val2 - val1) < errorMargin; + } // `val1` == `val2` - bool equal(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN); + inline bool equal(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN) { + return std::abs(val2 - val1) < errorMargin; + } // `val1` > `val2` - bool greater(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN); + inline bool greater(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN) { + return val1 > val2 && std::fabs(val2 - val1) > errorMargin; + } // `val1` > `val2` - bool greater(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN); + inline bool greater(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN) { + return val1 > val2 && std::abs(val2 - val1) > errorMargin; + } // `val1` >= `val2` - bool greaterOrEqual(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN); + inline bool greaterOrEqual(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN) { + return val1 > val2 || equal(val1, val2, errorMargin); + } // `val1` >= `val2` - bool greaterOrEqual(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN); + inline bool greaterOrEqual(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN) { + return val1 > val2 || equal(val1, val2, errorMargin); + } // `val1` < `val2` - bool smaller(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN); + inline bool smaller(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN) { + return val1 < val2 && std::fabs(val2 - val1) > errorMargin; + } // `val1` < `val2` - bool smaller(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN); + inline bool smaller(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN) { + return val1 < val2 && std::abs(val2 - val1) > errorMargin; + } // `val1` <= `val2` - bool smallerOrEqual(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN); + inline bool smallerOrEqual(float val1, float val2, float errorMargin = FLOAT_ERROR_MARGIN) { + return val1 < val2 || equal(val1, val2, errorMargin); + } // `val1` <= `val2` - bool smallerOrEqual(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN); + inline bool smallerOrEqual(double val1, double val2, double errorMargin = DOUBLE_ERROR_MARGIN) { + return val1 < val2 || equal(val1, val2, errorMargin); + } } \ No newline at end of file diff --git a/src/util/net.cpp b/src/util/net.cpp index b74c3c47..5b8b46eb 100644 --- a/src/util/net.cpp +++ b/src/util/net.cpp @@ -41,14 +41,14 @@ namespace util::net { // i call this a windows moment because like what the fuck??? auto le = GetLastError(); geode::log::error("FormatMessageA failed formatting error code {}, last error: {}", code, le); - return std::string("[Unknown windows error ") + std::to_string(code) + "]: formatting failed because of: " + std::to_string(le); + return fmt::format("[Unknown windows error {}]: formatting failed because of: {}", code, le); } - std::string formatted = std::string("[Win error ") + std::to_string(code) + "]: " + std::string(s); + std::string formatted = fmt::format("[Win error {}]: {}", code, s); LocalFree(s); return formatted; #else - return std::string("[Unix error ") + std::to_string(code) + "]: " + std::string(strerror(code)); + return fmt::format("[Unix error {}]: {}", code, strerror(code)); #endif } diff --git a/src/util/sync.hpp b/src/util/sync.hpp index 98908047..7fc48f30 100644 --- a/src/util/sync.hpp +++ b/src/util/sync.hpp @@ -103,6 +103,10 @@ class WrappingMutex { class Guard { public: + // no copy + Guard(const Guard&) = delete; + Guard& operator=(const Guard&) = delete; + Guard(std::shared_ptr data, std::mutex& mutex) : data_(data), mutex_(mutex) { mutex_.lock(); } @@ -186,4 +190,31 @@ using AtomicI64 = RelaxedAtomic; using AtomicU64 = RelaxedAtomic; using AtomicSizeT = RelaxedAtomic; +// thread safe singleton class. +// when possible, it is recommended to avoid using and just use GLOBED_SINGLETON instead. +// it will allow you to implement a more robust and more efficient sync approach, +// rather than locking up the entire instance and preventing access while in use. + +template +class SyncSingletonBase { +public: + // no copy + SyncSingletonBase(const SyncSingletonBase&) = delete; + SyncSingletonBase& operator=(const SyncSingletonBase&) = delete; + // no move + SyncSingletonBase(SyncSingletonBase&&) = delete; + SyncSingletonBase& operator=(SyncSingletonBase&&) = delete; + + static WrappingMutex::Guard lock() { + static WrappingMutex instance; + return instance.lock(); + } + +protected: + SyncSingletonBase() = default; + virtual ~SyncSingletonBase() = default; +}; + +#define GLOBED_SYNC_SINGLETON(cls) public ::util::sync::SyncSingletonBase + } \ No newline at end of file