diff --git a/CHANGELOG.md b/CHANGELOG.md index ba39d8af27..a031024523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Coral reefs, kelp forests, and seagrass - Talk animation - New bosses in 5 lower dungeons -= New enemies in 5 lower dungeons +- New enemies in 5 lower dungeons +- Added on join event in plugins - Item stacking and splitting ### Changed diff --git a/client/src/error.rs b/client/src/error.rs index 2a6ac310ef..bf7770998a 100644 --- a/client/src/error.rs +++ b/client/src/error.rs @@ -4,6 +4,7 @@ use network::{ParticipantError, StreamError}; #[derive(Debug)] pub enum Error { + Kicked(String), NetworkErr(NetworkError), ParticipantErr(ParticipantError), StreamErr(StreamError), diff --git a/client/src/lib.rs b/client/src/lib.rs index bacc5d2e83..e83842c6a7 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -500,6 +500,7 @@ impl Client { Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)), Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter), Err(RegisterError::NotOnWhitelist) => Err(Error::NotOnWhitelist), + Err(RegisterError::Kicked(err)) => Err(Error::Kicked(err)), Err(RegisterError::Banned(reason)) => Err(Error::Banned(reason)), Ok(()) => { self.registered = true; diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 2f55c0d21f..9ea2297a97 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -191,6 +191,7 @@ pub enum RegisterError { AlreadyLoggedIn, AuthError(String), Banned(String), + Kicked(String), InvalidCharacter, NotOnWhitelist, //TODO: InvalidAlias, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index b2ffefc035..420f16fea4 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -8,7 +8,7 @@ pub mod buff; #[cfg(not(target_arch = "wasm32"))] mod character_state; #[cfg(not(target_arch = "wasm32"))] pub mod chat; -pub mod combo; +#[cfg(not(target_arch = "wasm32"))] pub mod combo; #[cfg(not(target_arch = "wasm32"))] mod controller; #[cfg(not(target_arch = "wasm32"))] mod energy; diff --git a/common/sys/src/plugin/memory_manager.rs b/common/sys/src/plugin/memory_manager.rs index 771e78b9e4..b69834c74e 100644 --- a/common/sys/src/plugin/memory_manager.rs +++ b/common/sys/src/plugin/memory_manager.rs @@ -1,4 +1,4 @@ -use std::sync::atomic::{AtomicI32, AtomicPtr, AtomicU32, Ordering}; +use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, Ordering}; use serde::{de::DeserializeOwned, Serialize}; use specs::World; @@ -52,14 +52,14 @@ impl EcsAccessManager { } pub struct MemoryManager { - pub pointer: AtomicI32, + pub pointer: AtomicU64, pub length: AtomicU32, } impl Default for MemoryManager { fn default() -> Self { Self { - pointer: AtomicI32::new(0), + pointer: AtomicU64::new(0), length: AtomicU32::new(0), } } @@ -74,16 +74,18 @@ impl MemoryManager { &self, object_length: u32, allocator: &Function, - ) -> Result { + ) -> Result { if self.length.load(Ordering::SeqCst) >= object_length { return Ok(self.pointer.load(Ordering::SeqCst)); } let pointer = allocator .call(&[Value::I32(object_length as i32)]) .map_err(MemoryAllocationError::CantAllocate)?; - let pointer = pointer[0] - .i32() - .ok_or(MemoryAllocationError::InvalidReturnType)?; + let pointer = super::module::from_i64( + pointer[0] + .i64() + .ok_or(MemoryAllocationError::InvalidReturnType)?, + ); self.length.store(object_length, Ordering::SeqCst); self.pointer.store(pointer, Ordering::SeqCst); Ok(pointer) @@ -96,7 +98,7 @@ impl MemoryManager { memory: &Memory, allocator: &Function, object: &T, - ) -> Result<(i32, u32), PluginModuleError> { + ) -> Result<(u64, u64), PluginModuleError> { self.write_bytes( memory, allocator, @@ -104,23 +106,65 @@ impl MemoryManager { ) } + /// This function writes an object to the wasm memory using the allocator if + /// necessary using length padding. + /// + /// With length padding the first 8 bytes written are the length of the the + /// following slice (The object serialized). + pub fn write_data_as_pointer( + &self, + memory: &Memory, + allocator: &Function, + object: &T, + ) -> Result { + self.write_bytes_as_pointer( + memory, + allocator, + &bincode::serialize(object).map_err(PluginModuleError::Encoding)?, + ) + } + /// This function writes an raw bytes to WASM memory returning a pointer and /// a length. Will realloc the buffer is not wide enough pub fn write_bytes( &self, memory: &Memory, allocator: &Function, - array: &[u8], - ) -> Result<(i32, u32), PluginModuleError> { - let len = array.len(); + bytes: &[u8], + ) -> Result<(u64, u64), PluginModuleError> { + let len = bytes.len(); let mem_position = self .get_pointer(len as u32, allocator) .map_err(PluginModuleError::MemoryAllocation)? as usize; memory.view()[mem_position..mem_position + len] .iter() - .zip(array.iter()) + .zip(bytes.iter()) .for_each(|(cell, byte)| cell.set(*byte)); - Ok((mem_position as i32, len as u32)) + Ok((mem_position as u64, len as u64)) + } + + /// This function writes bytes to the wasm memory using the allocator if + /// necessary using length padding. + /// + /// With length padding the first 8 bytes written are the length of the the + /// following slice. + pub fn write_bytes_as_pointer( + &self, + memory: &Memory, + allocator: &Function, + bytes: &[u8], + ) -> Result { + let len = bytes.len(); + let mem_position = self + .get_pointer(len as u32 + 8, allocator) + .map_err(PluginModuleError::MemoryAllocation)? as usize; + // Here we write the length as le bytes followed by the slice data itself in + // WASM memory + memory.view()[mem_position..mem_position + len + 8] + .iter() + .zip((len as u64).to_le_bytes().iter().chain(bytes.iter())) + .for_each(|(cell, byte)| cell.set(*byte)); + Ok(mem_position as u64) } } @@ -128,14 +172,14 @@ impl MemoryManager { /// converts it to an object using bincode pub fn read_data( memory: &Memory, - position: i32, - length: u32, + position: u64, + length: u64, ) -> Result { bincode::deserialize(&read_bytes(memory, position, length)) } /// This function read raw bytes from memory at a position with the array length -pub fn read_bytes(memory: &Memory, position: i32, length: u32) -> Vec { +pub fn read_bytes(memory: &Memory, position: u64, length: u64) -> Vec { memory.view()[(position as usize)..(position as usize) + length as usize] .iter() .map(|x| x.get()) diff --git a/common/sys/src/plugin/mod.rs b/common/sys/src/plugin/mod.rs index 2d2c0a8342..df1b733457 100644 --- a/common/sys/src/plugin/mod.rs +++ b/common/sys/src/plugin/mod.rs @@ -84,7 +84,6 @@ impl Plugin { pub fn execute_prepared( &self, ecs: &World, - event_name: &str, event: &PreparedEventQuery, ) -> Result, PluginError> where @@ -93,11 +92,11 @@ impl Plugin { self.modules .iter() .flat_map(|module| { - module.try_execute(ecs, event_name, event).map(|x| { + module.try_execute(ecs, event).map(|x| { x.map_err(|e| { PluginError::PluginModuleError( self.data.name.to_owned(), - event_name.to_owned(), + event.get_function_name().to_owned(), e, ) }) @@ -123,7 +122,6 @@ impl PluginMgr { pub fn execute_prepared( &self, ecs: &World, - event_name: &str, event: &PreparedEventQuery, ) -> Result, PluginError> where @@ -132,23 +130,18 @@ impl PluginMgr { Ok(self .plugins .par_iter() - .map(|plugin| plugin.execute_prepared(ecs, event_name, event)) + .map(|plugin| plugin.execute_prepared(ecs, event)) .collect::, _>>()? .into_iter() .flatten() .collect()) } - pub fn execute_event( - &self, - ecs: &World, - event_name: &str, - event: &T, - ) -> Result, PluginError> + pub fn execute_event(&self, ecs: &World, event: &T) -> Result, PluginError> where T: Event, { - self.execute_prepared(ecs, event_name, &PreparedEventQuery::new(event)?) + self.execute_prepared(ecs, &PreparedEventQuery::new(event)?) } pub fn from_dir>(path: P) -> Result { diff --git a/common/sys/src/plugin/module.rs b/common/sys/src/plugin/module.rs index c8b16d613c..2bf7ca3dec 100644 --- a/common/sys/src/plugin/module.rs +++ b/common/sys/src/plugin/module.rs @@ -43,8 +43,8 @@ impl PluginModule { let module = Module::new(&store, &wasm_data).expect("Can't compile"); // This is the function imported into the wasm environement - fn raw_emit_actions(env: &HostFunctionEnvironement, ptr: u32, len: u32) { - handle_actions(match env.read_data(ptr as i32, len) { + fn raw_emit_actions(env: &HostFunctionEnvironement, ptr: i64, len: i64) { + handle_actions(match env.read_data(from_i64(ptr), from_i64(len)) { Ok(e) => e, Err(e) => { tracing::error!(?e, "Can't decode action"); @@ -53,16 +53,15 @@ impl PluginModule { }); } - fn raw_retrieve_action(env: &HostFunctionEnvironement, ptr: u32, len: u32) -> i64 { - let out = match env.read_data(ptr as _, len) { + fn raw_retrieve_action(env: &HostFunctionEnvironement, ptr: i64, len: i64) -> i64 { + let out = match env.read_data(from_i64(ptr), from_i64(len)) { Ok(data) => retrieve_action(&env.ecs, data), Err(e) => Err(RetrieveError::BincodeError(e.to_string())), }; // If an error happen set the i64 to 0 so the WASM side can tell an error // occured - let (ptr, len) = env.write_data(&out).unwrap(); - to_i64(ptr, len as _) + to_i64(env.write_data_as_pointer(&out).unwrap()) } fn dbg(a: i32) { @@ -112,19 +111,18 @@ impl PluginModule { pub fn try_execute( &self, ecs: &World, - event_name: &str, request: &PreparedEventQuery, ) -> Option> where T: Event, { - if !self.events.contains(event_name) { + if !self.events.contains(&request.function_name) { return None; } // Store the ECS Pointer for later use in `retreives` let bytes = match self.ecs.execute_with(ecs, || { let mut state = self.wasm_state.lock().unwrap(); - execute_raw(self, &mut state, event_name, &request.bytes) + execute_raw(self, &mut state, &request.function_name, &request.bytes) }) { Ok(e) => e, Err(e) => return Some(Err(e)), @@ -137,6 +135,7 @@ impl PluginModule { /// reencoding for each module in every plugin) pub struct PreparedEventQuery { bytes: Vec, + function_name: String, _phantom: PhantomData, } @@ -150,25 +149,36 @@ impl PreparedEventQuery { { Ok(Self { bytes: bincode::serialize(&event).map_err(PluginError::Encoding)?, + function_name: event.get_event_name(), _phantom: PhantomData::default(), }) } + + pub fn get_function_name(&self) -> &str { &self.function_name } } -fn from_i64(i: i64) -> (i32, i32) { +/// This function split a u128 in two u64 encoding them as le bytes +pub fn from_u128(i: u128) -> (u64, u64) { let i = i.to_le_bytes(); ( - i32::from_le_bytes(i[0..4].try_into().unwrap()), - i32::from_le_bytes(i[4..8].try_into().unwrap()), + u64::from_le_bytes(i[0..8].try_into().unwrap()), + u64::from_le_bytes(i[8..16].try_into().unwrap()), ) } -pub fn to_i64(a: i32, b: i32) -> i64 { +/// This function merge two u64 encoded as le in one u128 +pub fn to_u128(a: u64, b: u64) -> u128 { let a = a.to_le_bytes(); let b = b.to_le_bytes(); - i64::from_le_bytes([a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]]) + u128::from_le_bytes([a, b].concat().try_into().unwrap()) } +/// This function encode a u64 into a i64 using le bytes +pub fn to_i64(i: u64) -> i64 { i64::from_le_bytes(i.to_le_bytes()) } + +/// This function decode a i64 into a u64 using le bytes +pub fn from_i64(i: i64) -> u64 { u64::from_le_bytes(i.to_le_bytes()) } + // This function is not public because this function should not be used without // an interface to limit unsafe behaviours #[allow(clippy::needless_range_loop)] @@ -196,24 +206,28 @@ fn execute_raw( // We call the function with the pointer and the length let function_result = func - .call(&[Value::I32(mem_position as i32), Value::I32(len as i32)]) + .call(&[Value::I64(to_i64(mem_position)), Value::I64(to_i64(len))]) .map_err(PluginModuleError::RunFunction)?; - // Waiting for `multi-value` to be added to LLVM. So we encode the two i32 as an - // i64 + // Waiting for `multi-value` to be added to LLVM. So we encode a pointer to a + // u128 that represent [u64; 2] - let (pointer, length) = from_i64( + let u128_pointer = from_i64( function_result[0] .i64() .ok_or_else(PluginModuleError::InvalidArgumentType)?, ); + let bytes = memory_manager::read_bytes(&module.memory, u128_pointer, 16); + // We read the return object and deserialize it + // The first 8 bytes are encoded as le and represent the pointer to the data + // The next 8 bytes are encoded as le and represent the length of the data Ok(memory_manager::read_bytes( &module.memory, - pointer, - length as u32, + u64::from_le_bytes(bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), )) } diff --git a/common/sys/src/plugin/wasm_env.rs b/common/sys/src/plugin/wasm_env.rs index e383f7f3d5..bcd5ec3d55 100644 --- a/common/sys/src/plugin/wasm_env.rs +++ b/common/sys/src/plugin/wasm_env.rs @@ -37,7 +37,7 @@ impl HostFunctionEnvironement { /// This function is a safe interface to WASM memory that writes data to the /// memory returning a pointer and length - pub fn write_data(&self, object: &T) -> Result<(i32, u32), PluginModuleError> { + pub fn write_data(&self, object: &T) -> Result<(u64, u64), PluginModuleError> { self.memory_manager.write_data( self.memory.get_ref().unwrap(), self.allocator.get_ref().unwrap(), @@ -45,12 +45,25 @@ impl HostFunctionEnvironement { ) } + /// This function is a safe interface to WASM memory that writes data to the + /// memory returning a pointer and length + pub fn write_data_as_pointer( + &self, + object: &T, + ) -> Result { + self.memory_manager.write_data_as_pointer( + self.memory.get_ref().unwrap(), + self.allocator.get_ref().unwrap(), + object, + ) + } + /// This function is a safe interface to WASM memory that reads memory from /// pointer and length returning an object pub fn read_data( &self, - position: i32, - length: u32, + position: u64, + length: u64, ) -> Result { memory_manager::read_data(self.memory.get_ref().unwrap(), position, length) } diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs index 32c6c20327..d53ad1e1e0 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -209,10 +209,8 @@ impl State { #[cfg(feature = "plugins")] ecs.insert(match PluginMgr::from_assets() { Ok(plugin_mgr) => { - if let Err(e) = - plugin_mgr.execute_event(&ecs, "on_load", &plugin_api::event::PluginLoadEvent { - game_mode, - }) + if let Err(e) = plugin_mgr + .execute_event(&ecs, &plugin_api::event::PluginLoadEvent { game_mode }) { tracing::error!(?e, "Failed to run plugin init"); tracing::info!( diff --git a/plugin/api/src/lib.rs b/plugin/api/src/lib.rs index 0ce193cac7..3794310313 100644 --- a/plugin/api/src/lib.rs +++ b/plugin/api/src/lib.rs @@ -96,6 +96,8 @@ pub enum RetrieveResult { /// This trait is implement by all events and ensure type safety of FFI. pub trait Event: Serialize + DeserializeOwned + Send + Sync { type Response: Serialize + DeserializeOwned + Send + Sync; + + fn get_event_name(&self) -> String; } /// This module contains all events from the api @@ -138,6 +140,8 @@ pub mod event { impl Event for ChatCommandEvent { type Response = Result, String>; + + fn get_event_name(&self) -> String { format!("on_command_{}", self.command) } } /// This struct represent a player @@ -162,11 +166,13 @@ pub mod event { #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] pub struct PlayerJoinEvent { pub player_name: String, - pub player_id: Uid, + pub player_id: [u8; 16], } impl Event for PlayerJoinEvent { type Response = PlayerJoinResult; + + fn get_event_name(&self) -> String { "on_join".to_owned() } } /// This is the return type of an `on_join` event. See [`PlayerJoinEvent`] @@ -175,8 +181,9 @@ pub mod event { /// - `CloseConnection` will kick the player. /// - `None` will let the player join the server. #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] + #[repr(u8)] pub enum PlayerJoinResult { - CloseConnection, + Kick(String), None, } @@ -205,6 +212,8 @@ pub mod event { impl Event for PluginLoadEvent { type Response = (); + + fn get_event_name(&self) -> String { "on_load".to_owned() } } // impl Default for PlayerJoinResult { diff --git a/plugin/derive/src/lib.rs b/plugin/derive/src/lib.rs index 69fd927fa5..d275848d87 100644 --- a/plugin/derive/src/lib.rs +++ b/plugin/derive/src/lib.rs @@ -16,8 +16,8 @@ pub fn event_handler(_args: TokenStream, item: TokenStream) -> TokenStream { let out: proc_macro2::TokenStream = quote! { #[allow(clippy::unnecessary_wraps)] #[no_mangle] - pub fn #fn_name(intern__ptr: i32, intern__len: i32) -> i64 { - let input = ::veloren_plugin_rt::read_input(intern__ptr,intern__len as u32).unwrap(); + pub fn #fn_name(intern__ptr: i64, intern__len: i64) -> i64 { + let input = ::veloren_plugin_rt::read_input(intern__ptr as _,intern__len as _).unwrap(); #[inline] fn inner(#fn_args) #fn_return { #fn_body diff --git a/plugin/rt/examples/hello.rs b/plugin/rt/examples/hello.rs index bd4ad051db..f9009b6525 100644 --- a/plugin/rt/examples/hello.rs +++ b/plugin/rt/examples/hello.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + use veloren_plugin_rt::{ api::{event::*, Action, GameMode}, *, @@ -29,14 +31,12 @@ pub fn on_command_testplugin(command: ChatCommandEvent) -> Result, S )]) } +static COUNTER: AtomicBool = AtomicBool::new(false); + #[event_handler] -pub fn on_player_join(input: PlayerJoinEvent) -> PlayerJoinResult { - emit_action(Action::PlayerSendMessage( - input.player_id, - format!("Welcome {} on our server", input.player_name), - )); - if input.player_name == "Cheater123" { - PlayerJoinResult::CloseConnection +pub fn on_join(input: PlayerJoinEvent) -> PlayerJoinResult { + if COUNTER.swap(!COUNTER.load(Ordering::SeqCst), Ordering::SeqCst) { + PlayerJoinResult::Kick(format!("You are a cheater {:?}", input)) } else { PlayerJoinResult::None } diff --git a/plugin/rt/src/lib.rs b/plugin/rt/src/lib.rs index 954f412e52..82ef453eb4 100644 --- a/plugin/rt/src/lib.rs +++ b/plugin/rt/src/lib.rs @@ -18,8 +18,8 @@ use serde::{de::DeserializeOwned, Serialize}; #[cfg(target_arch = "wasm32")] extern "C" { - fn raw_emit_actions(ptr: *const u8, len: usize); - fn raw_retrieve_action(ptr: *const u8, len: usize) -> i64; + fn raw_emit_actions(ptr: i64, len: i64); + fn raw_retrieve_action(ptr: i64, len: i64) -> i64; pub fn dbg(i: i32); } @@ -28,8 +28,11 @@ pub fn retrieve_action(_actions: &api::Retrieve) -> Result< { let ret = bincode::serialize(&_actions).expect("Can't serialize action in emit"); unsafe { - let (ptr, len) = from_i64(raw_retrieve_action(ret.as_ptr(), ret.len())); - let a = ::std::slice::from_raw_parts(ptr as _, len as _); + let ptr = raw_retrieve_action(to_i64(ret.as_ptr() as _), to_i64(ret.len() as _)); + let ptr = from_i64(ptr); + let len = + u64::from_le_bytes(std::slice::from_raw_parts(ptr as _, 8).try_into().unwrap()); + let a = ::std::slice::from_raw_parts((ptr + 8) as _, len as _); bincode::deserialize::>(&a) .map_err(|x| RetrieveError::BincodeError(x.to_string()))? } @@ -45,36 +48,54 @@ pub fn emit_actions(_actions: Vec) { { let ret = bincode::serialize(&_actions).expect("Can't serialize action in emit"); unsafe { - raw_emit_actions(ret.as_ptr(), ret.len()); + raw_emit_actions(to_i64(ret.as_ptr() as _), to_i64(ret.len() as _)); } } } -pub fn read_input(ptr: i32, len: u32) -> Result +pub fn read_input(ptr: i64, len: i64) -> Result where T: DeserializeOwned, { - let slice = unsafe { ::std::slice::from_raw_parts(ptr as _, len as _) }; + let slice = unsafe { ::std::slice::from_raw_parts(from_i64(ptr) as _, from_i64(len) as _) }; bincode::deserialize(slice).map_err(|_| "Failed to deserialize function input") } -pub fn from_i64(i: i64) -> (i32, i32) { +/// This function split a u128 in two u64 encoding them as le bytes +pub fn from_u128(i: u128) -> (u64, u64) { let i = i.to_le_bytes(); ( - i32::from_le_bytes(i[0..4].try_into().unwrap()), - i32::from_le_bytes(i[4..8].try_into().unwrap()), + u64::from_le_bytes(i[0..8].try_into().unwrap()), + u64::from_le_bytes(i[8..16].try_into().unwrap()), ) } -pub fn to_i64(a: i32, b: i32) -> i64 { +/// This function merge two u64 encoded as le in one u128 +pub fn to_u128(a: u64, b: u64) -> u128 { let a = a.to_le_bytes(); let b = b.to_le_bytes(); - i64::from_le_bytes([a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]]) + u128::from_le_bytes([a, b].concat().try_into().unwrap()) } +/// This function encode a u64 into a i64 using le bytes +pub fn to_i64(i: u64) -> i64 { i64::from_le_bytes(i.to_le_bytes()) } + +/// This function decode a i64 into a u64 using le bytes +pub fn from_i64(i: i64) -> u64 { u64::from_le_bytes(i.to_le_bytes()) } + +static mut VEC: Vec = vec![]; +static mut DATA: Vec = vec![]; + pub fn write_output(value: impl Serialize) -> i64 { - let ret = bincode::serialize(&value).expect("Can't serialize event output"); - to_i64(ret.as_ptr() as _, ret.len() as _) + unsafe { + VEC = bincode::serialize(&value).expect("Can't serialize event output"); + DATA = [ + (VEC.as_ptr() as u64).to_le_bytes(), + (VEC.len() as u64).to_le_bytes(), + ] + .concat(); + to_i64(DATA.as_ptr() as u64) + } } static mut BUFFERS: Vec = Vec::new(); @@ -83,7 +104,7 @@ static mut BUFFERS: Vec = Vec::new(); /// # Safety /// This function should never be used only intented to by used by the host #[no_mangle] -pub unsafe fn wasm_prepare_buffer(size: i32) -> i32 { +pub unsafe fn wasm_prepare_buffer(size: i32) -> i64 { BUFFERS = vec![0u8; size as usize]; - BUFFERS.as_ptr() as i32 + BUFFERS.as_ptr() as i64 } diff --git a/server/src/lib.rs b/server/src/lib.rs index 089133d7bd..82c69f5f7d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -24,6 +24,7 @@ pub mod login_provider; pub mod metrics; pub mod persistence; pub mod presence; +mod register; pub mod rtsim; pub mod settings; pub mod state_ext; @@ -58,7 +59,7 @@ use common::{ assets::AssetExt, cmd::ChatCommand, comp, - comp::{item::MaterialStatManifest, CharacterAbility}, + comp::{item::MaterialStatManifest, Admin, CharacterAbility, Player, Stats}, event::{EventBus, ServerEvent}, recipe::default_recipe_book, resources::TimeOfDay, @@ -68,19 +69,22 @@ use common::{ }; use common_net::{ msg::{ - ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerInit, ServerMsg, WorldMapMsg, + CharacterInfo, ClientType, DisconnectReason, PlayerInfo, PlayerListUpdate, ServerGeneral, + ServerInfo, ServerInit, ServerMsg, WorldMapMsg, }, sync::WorldSyncExt, }; #[cfg(feature = "plugins")] use common_sys::plugin::PluginMgr; use common_sys::state::State; -use metrics::{PhysicsMetrics, StateTickMetrics, TickMetrics}; +use hashbrown::HashMap; +use metrics::{PhysicsMetrics, PlayerMetrics, StateTickMetrics, TickMetrics}; use network::{Network, Pid, ProtocolAddr}; use persistence::{ character_loader::{CharacterLoader, CharacterLoaderResponseKind}, character_updater::CharacterUpdater, }; +use plugin_api::Uid; use prometheus::Registry; use prometheus_hyper::Server as PrometheusServer; use specs::{join::Join, Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt}; @@ -195,7 +199,6 @@ impl Server { state.ecs_mut().insert(sys::EntitySyncTimer::default()); state.ecs_mut().insert(sys::GeneralMsgTimer::default()); state.ecs_mut().insert(sys::PingMsgTimer::default()); - state.ecs_mut().insert(sys::RegisterMsgTimer::default()); state .ecs_mut() .insert(sys::CharacterScreenMsgTimer::default()); @@ -507,7 +510,7 @@ impl Server { // (e.g. run before controller system) //TODO: run in parallel sys::msg::general::Sys.run_now(&self.state.ecs()); - sys::msg::register::Sys.run_now(&self.state.ecs()); + self.register_run(); sys::msg::character_screen::Sys.run_now(&self.state.ecs()); sys::msg::in_game::Sys.run_now(&self.state.ecs()); sys::msg::ping::Sys.run_now(&self.state.ecs()); @@ -711,7 +714,6 @@ impl Server { let state = self.state.ecs(); (state.read_resource::().nanos + state.read_resource::().nanos - + state.read_resource::().nanos + state.read_resource::().nanos + state.read_resource::().nanos) as i64 }; @@ -917,6 +919,75 @@ impl Server { self.state.cleanup(); } + pub fn register_run(&mut self) { + let world = self.state_mut().ecs_mut(); + let entities = world.entities(); + let player_metrics = world.read_resource::(); + let uids = world.read_storage::(); + let clients = world.read_storage::(); + let mut players = world.write_storage::(); + let stats = world.read_storage::(); + let mut login_provider = world.write_resource::(); + let mut admins = world.write_storage::(); + let editable_settings = world.read_resource::(); + + // Player list to send new players. + let player_list = (&uids, &players, stats.maybe(), admins.maybe()) + .join() + .map(|(uid, player, stats, admin)| { + (*uid, PlayerInfo { + is_online: true, + is_admin: admin.is_some(), + player_alias: player.alias.clone(), + character: stats.map(|stats| CharacterInfo { + name: stats.name.clone(), + }), + }) + }) + .collect::>(); + // List of new players to update player lists of all clients. + let mut new_players = Vec::new(); + + for (entity, client) in (&entities, &clients).join() { + let _ = sys::msg::try_recv_all(client, 0, |client, msg| { + register::handle_register_msg( + &world, + &player_list, + &mut new_players, + entity, + client, + &player_metrics, + &mut login_provider, + &mut admins, + &mut players, + &editable_settings, + msg, + ) + }); + } + + // Handle new players. + // Tell all clients to add them to the player list. + for entity in new_players { + if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) { + let mut lazy_msg = None; + for (_, client) in (&players, &clients).join() { + if lazy_msg.is_none() { + lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate( + PlayerListUpdate::Add(*uid, PlayerInfo { + player_alias: player.alias.clone(), + is_online: true, + is_admin: admins.get(entity).is_some(), + character: None, // new players will be on character select. + }), + ))); + } + lazy_msg.as_ref().map(|ref msg| client.send_prepared(&msg)); + } + } + } + } + fn initialize_client( &mut self, client: crate::connection_handler::IncomingClient, @@ -1033,11 +1104,9 @@ impl Server { } else { #[cfg(feature = "plugins")] { - use common::uid::Uid; let plugin_manager = self.state.ecs().read_resource::(); let rs = plugin_manager.execute_event( self.state.ecs(), - &format!("on_command_{}", &kwd), &plugin_api::event::ChatCommandEvent { command: kwd.clone(), command_args: args.split(' ').map(|x| x.to_owned()).collect(), diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs index 67c5aaaa7f..64007e4684 100644 --- a/server/src/login_provider.rs +++ b/server/src/login_provider.rs @@ -1,7 +1,10 @@ use crate::settings::BanRecord; use authc::{AuthClient, AuthClientError, AuthToken, Uuid}; use common_net::msg::RegisterError; +use common_sys::plugin::PluginMgr; use hashbrown::{HashMap, HashSet}; +use plugin_api::event::{PlayerJoinEvent, PlayerJoinResult}; +use specs::World; use std::str::FromStr; use tracing::{error, info}; @@ -53,6 +56,8 @@ impl LoginProvider { pub fn try_login( &mut self, username_or_token: &str, + world: &World, + plugin_manager: &PluginMgr, admins: &HashSet, whitelist: &HashSet, banlist: &HashMap, @@ -74,6 +79,22 @@ impl LoginProvider { return Err(RegisterError::NotOnWhitelist); } + match plugin_manager.execute_event(&world, &PlayerJoinEvent { + player_name: username.clone(), + player_id: *uuid.as_bytes(), + }) { + Ok(e) => { + for i in e.into_iter() { + if let PlayerJoinResult::Kick(a) = i { + return Err(RegisterError::Kicked(a)); + } + } + }, + Err(e) => { + error!("Error occured while executing `on_join`: {:?}",e); + }, + }; + // add the user to self.accounts self.login(uuid, username.clone())?; diff --git a/server/src/register.rs b/server/src/register.rs new file mode 100644 index 0000000000..3f480bd5f7 --- /dev/null +++ b/server/src/register.rs @@ -0,0 +1,81 @@ +use common::comp::{Admin, Player}; +use common_net::msg::{ + ClientRegister, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneral, + ServerRegisterAnswer, +}; +use common_sys::plugin::PluginMgr; +use hashbrown::HashMap; +use plugin_api::Uid; +use specs::{ + shred::{Fetch, FetchMut}, + Entity, World, WorldExt, WriteStorage, +}; + +use crate::{ + client::Client, login_provider::LoginProvider, metrics::PlayerMetrics, EditableSettings, +}; + +#[allow(clippy::too_many_arguments)] +pub(crate) fn handle_register_msg( + world: &World, + player_list: &HashMap, + new_players: &mut Vec, + entity: Entity, + client: &Client, + player_metrics: &Fetch<'_, PlayerMetrics>, + login_provider: &mut FetchMut<'_, LoginProvider>, + admins: &mut WriteStorage<'_, Admin>, + players: &mut WriteStorage<'_, Player>, + editable_settings: &Fetch<'_, EditableSettings>, + msg: ClientRegister, +) -> Result<(), crate::error::Error> { + let plugin_mgr = world.read_resource::(); + let (username, uuid) = match login_provider.try_login( + &msg.token_or_username, + world, + &plugin_mgr, + &*editable_settings.admins, + &*editable_settings.whitelist, + &*editable_settings.banlist, + ) { + Err(err) => { + client.send(ServerRegisterAnswer::Err(err))?; + return Ok(()); + }, + Ok((username, uuid)) => (username, uuid), + }; + + let player = Player::new(username, uuid); + let is_admin = editable_settings.admins.contains(&uuid); + + if !player.is_valid() { + // Invalid player + client.send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?; + return Ok(()); + } + + if !players.contains(entity) { + // Add Player component to this client + let _ = players.insert(entity, player); + player_metrics.players_connected.inc(); + + // Give the Admin component to the player if their name exists in + // admin list + if is_admin { + let _ = admins.insert(entity, Admin); + } + + // Tell the client its request was successful. + client.send(ServerRegisterAnswer::Ok(()))?; + + // Send initial player list + client.send(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init( + player_list.clone(), + )))?; + + // Add to list to notify all clients of the new player + new_players.push(entity); + } + + Ok(()) +} diff --git a/server/src/sys/mod.rs b/server/src/sys/mod.rs index d784ec5c18..649c52114b 100644 --- a/server/src/sys/mod.rs +++ b/server/src/sys/mod.rs @@ -19,7 +19,6 @@ use std::{ pub type EntitySyncTimer = SysTimer; pub type GeneralMsgTimer = SysTimer; pub type PingMsgTimer = SysTimer; -pub type RegisterMsgTimer = SysTimer; pub type CharacterScreenMsgTimer = SysTimer; pub type InGameMsgTimer = SysTimer; pub type SentinelTimer = SysTimer; diff --git a/server/src/sys/msg/mod.rs b/server/src/sys/msg/mod.rs index 0242d83794..e50d09ca90 100644 --- a/server/src/sys/msg/mod.rs +++ b/server/src/sys/msg/mod.rs @@ -2,14 +2,13 @@ pub mod character_screen; pub mod general; pub mod in_game; pub mod ping; -pub mod register; use crate::client::Client; use serde::de::DeserializeOwned; /// handles all send msg and calls a handle fn /// Aborts when a error occurred returns cnt of successful msg otherwise -pub(in crate::sys::msg) fn try_recv_all( +pub(crate) fn try_recv_all( client: &Client, stream_id: u8, mut f: F, diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs deleted file mode 100644 index e433f4d4ef..0000000000 --- a/server/src/sys/msg/register.rs +++ /dev/null @@ -1,172 +0,0 @@ -use super::super::SysTimer; -use crate::{ - client::Client, login_provider::LoginProvider, metrics::PlayerMetrics, EditableSettings, -}; -use common::{ - comp::{Admin, Player, Stats}, - span, - uid::Uid, -}; -use common_net::msg::{ - CharacterInfo, ClientRegister, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneral, - ServerRegisterAnswer, -}; -use hashbrown::HashMap; -use specs::{Entities, Join, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage}; - -impl Sys { - #[allow(clippy::too_many_arguments)] - fn handle_register_msg( - player_list: &HashMap, - new_players: &mut Vec, - entity: specs::Entity, - client: &Client, - player_metrics: &ReadExpect<'_, PlayerMetrics>, - login_provider: &mut WriteExpect<'_, LoginProvider>, - admins: &mut WriteStorage<'_, Admin>, - players: &mut WriteStorage<'_, Player>, - editable_settings: &ReadExpect<'_, EditableSettings>, - msg: ClientRegister, - ) -> Result<(), crate::error::Error> { - let (username, uuid) = match login_provider.try_login( - &msg.token_or_username, - &*editable_settings.admins, - &*editable_settings.whitelist, - &*editable_settings.banlist, - ) { - Err(err) => { - client.send(ServerRegisterAnswer::Err(err))?; - return Ok(()); - }, - Ok((username, uuid)) => (username, uuid), - }; - - let player = Player::new(username, uuid); - let is_admin = editable_settings.admins.contains(&uuid); - - if !player.is_valid() { - // Invalid player - client.send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?; - return Ok(()); - } - - if !players.contains(entity) { - // Add Player component to this client - let _ = players.insert(entity, player); - player_metrics.players_connected.inc(); - - // Give the Admin component to the player if their name exists in - // admin list - if is_admin { - let _ = admins.insert(entity, Admin); - } - - // Tell the client its request was successful. - client.send(ServerRegisterAnswer::Ok(()))?; - - // Send initial player list - client.send(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init( - player_list.clone(), - )))?; - - // Add to list to notify all clients of the new player - new_players.push(entity); - } - - Ok(()) - } -} - -/// This system will handle new messages from clients -pub struct Sys; -impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] - type SystemData = ( - Entities<'a>, - ReadExpect<'a, PlayerMetrics>, - Write<'a, SysTimer>, - ReadStorage<'a, Uid>, - ReadStorage<'a, Client>, - WriteStorage<'a, Player>, - ReadStorage<'a, Stats>, - WriteExpect<'a, LoginProvider>, - WriteStorage<'a, Admin>, - ReadExpect<'a, EditableSettings>, - ); - - fn run( - &mut self, - ( - entities, - player_metrics, - mut timer, - uids, - clients, - mut players, - stats, - mut login_provider, - mut admins, - editable_settings, - ): Self::SystemData, - ) { - span!(_guard, "run", "msg::register::Sys::run"); - timer.start(); - - // Player list to send new players. - let player_list = (&uids, &players, stats.maybe(), admins.maybe()) - .join() - .map(|(uid, player, stats, admin)| { - (*uid, PlayerInfo { - is_online: true, - is_admin: admin.is_some(), - player_alias: player.alias.clone(), - character: stats.map(|stats| CharacterInfo { - name: stats.name.clone(), - }), - }) - }) - .collect::>(); - // List of new players to update player lists of all clients. - let mut new_players = Vec::new(); - - for (entity, client) in (&entities, &clients).join() { - let _ = super::try_recv_all(client, 0, |client, msg| { - Self::handle_register_msg( - &player_list, - &mut new_players, - entity, - client, - &player_metrics, - &mut login_provider, - &mut admins, - &mut players, - &editable_settings, - msg, - ) - }); - } - - // Handle new players. - // Tell all clients to add them to the player list. - for entity in new_players { - if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) { - let mut lazy_msg = None; - for (_, client) in (&players, &clients).join() { - if lazy_msg.is_none() { - lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate( - PlayerListUpdate::Add(*uid, PlayerInfo { - player_alias: player.alias.clone(), - is_online: true, - is_admin: admins.get(entity).is_some(), - character: None, // new players will be on character select. - }), - ))); - } - lazy_msg.as_ref().map(|ref msg| client.send_prepared(&msg)); - } - } - } - - timer.end() - } -} diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 6cc4df7ff0..80de3e768c 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -134,6 +134,7 @@ impl PlayState for MainMenuState { localized_strings.get("main.login.authentication_error"), e ), + client::Error::Kicked(e) => e, client::Error::TooManyPlayers => { localized_strings.get("main.login.server_full").into() },