diff options
| author | Matthew Collins <[email protected]> | 2018-02-28 16:12:31 +0000 |
|---|---|---|
| committer | Matthew Collins <[email protected]> | 2018-02-28 16:12:31 +0000 |
| commit | 844887aea84cb17a135d070304726a33f17b30b7 (patch) | |
| tree | dd26d34956283c9ef261fbae9c7e56c9992ea2ce | |
| parent | Implement user authentication session methods (diff) | |
| download | steamworks-rs-844887aea84cb17a135d070304726a33f17b30b7.tar.xz steamworks-rs-844887aea84cb17a135d070304726a33f17b30b7.zip | |
Initial support for servers
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/lib.rs | 84 | ||||
| -rw-r--r-- | src/server.rs | 299 | ||||
| -rw-r--r-- | src/user.rs | 2 | ||||
| -rw-r--r-- | steamworks-sys/Cargo.toml | 2 | ||||
| -rw-r--r-- | steamworks-sys/src/lib.cpp | 24 | ||||
| -rw-r--r-- | steamworks-sys/src/lib.rs | 28 |
7 files changed, 398 insertions, 43 deletions
@@ -20,7 +20,7 @@ members = [ ] [dependencies] -steamworks-sys = {path = "./steamworks-sys", version = "0.1.1"} +steamworks-sys = {path = "./steamworks-sys", version = "0.2.0"} failure = "0.1.1" bitflags = "1.0.1" libc = "0.2.36" @@ -9,6 +9,8 @@ extern crate bitflags; mod error; pub use error::*; +mod server; +pub use server::*; mod utils; pub use utils::*; mod app; @@ -38,25 +40,28 @@ pub type SResult<T> = Result<T, SteamError>; /// The main entry point into the steam client. /// -/// This provides access to all of the steamworks api. +/// This provides access to all of the steamworks api that +/// clients can use. #[derive(Clone)] pub struct Client<Manager = ClientManager> { inner: Arc<Inner<Manager>>, + _client: *mut sys::ISteamClient, } struct Inner<Manager> { _manager: Manager, - _client: *mut sys::ISteamClient, - callbacks: Mutex<ClientCallbacks>, + callbacks: Mutex<Callbacks>, } -struct ClientCallbacks { +struct Callbacks { callbacks: Vec<*mut libc::c_void>, call_results: HashMap<sys::SteamAPICall, *mut libc::c_void>, } unsafe impl <Manager: Send + Sync> Send for Inner<Manager> {} unsafe impl <Manager: Send + Sync> Sync for Inner<Manager> {} +unsafe impl <Manager: Send + Sync> Send for Client<Manager> {} +unsafe impl <Manager: Send + Sync> Sync for Client<Manager> {} impl Client<ClientManager> { /// Attempts to initialize the steamworks api and returns @@ -81,17 +86,17 @@ impl Client<ClientManager> { if sys::SteamAPI_Init() == 0 { return Err(SteamError::InitFailed); } - let client = sys::steam_rust_get_client(); + let raw_client = sys::steam_rust_get_client(); let client = Arc::new(Inner { _manager: ClientManager { _priv: () }, - _client: client, - callbacks: Mutex::new(ClientCallbacks { + callbacks: Mutex::new(Callbacks { callbacks: Vec::new(), call_results: HashMap::new(), }), }); Ok(Client { inner: client, + _client: raw_client, }) } } @@ -121,35 +126,7 @@ impl <Manager> Client<Manager> { F: FnMut(C) + 'static + Send + Sync { unsafe { - let userdata = Box::into_raw(Box::new(f)); - - extern "C" fn run_func<C, F>(userdata: *mut libc::c_void, param: *mut libc::c_void) - where C: Callback, - F: FnMut(C) + 'static + Send + Sync - { - unsafe { - let func: &mut F = &mut *(userdata as *mut F); - let param = C::from_raw(param); - func(param); - } - } - extern "C" fn dealloc<C, F>(userdata: *mut libc::c_void) - where C: Callback, - F: FnMut(C) + 'static + Send + Sync - { - let func: Box<F> = unsafe { Box::from_raw(userdata as _) }; - drop(func); - } - - let ptr = sys::register_rust_steam_callback( - C::size() as _, - userdata as _, - run_func::<C, F>, - dealloc::<C, F>, - C::id() as _ - ); - let mut cbs = self.inner.callbacks.lock().unwrap(); - cbs.callbacks.push(ptr); + register_callback(&self.inner, f, false); } } @@ -233,6 +210,41 @@ impl <Manager> Drop for Inner<Manager> { } +pub(crate) unsafe fn register_callback<C, F, Manager>(inner: &Arc<Inner<Manager>>, f: F, game_server: bool) + where C: Callback, + F: FnMut(C) + 'static + Send + Sync +{ + let userdata = Box::into_raw(Box::new(f)); + + extern "C" fn run_func<C, F>(userdata: *mut libc::c_void, param: *mut libc::c_void) + where C: Callback, + F: FnMut(C) + 'static + Send + Sync + { + unsafe { + let func: &mut F = &mut *(userdata as *mut F); + let param = C::from_raw(param); + func(param); + } + } + extern "C" fn dealloc<C, F>(userdata: *mut libc::c_void) + where C: Callback, + F: FnMut(C) + 'static + Send + Sync + { + let func: Box<F> = unsafe { Box::from_raw(userdata as _) }; + drop(func); + } + + let ptr = sys::register_rust_steam_callback( + C::size() as _, + userdata as _, + run_func::<C, F>, + dealloc::<C, F>, + C::id() as _, + game_server as _, + ); + let mut cbs = inner.callbacks.lock().unwrap(); + cbs.callbacks.push(ptr); +} pub(crate) unsafe fn register_call_result<C, F, Manager>(inner: &Arc<Inner<Manager>>, api_call: sys::SteamAPICall, callback_id: i32, f: F) where F: for <'a> FnMut(&'a C, bool) + 'static + Send + Sync diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..ee37cac --- /dev/null +++ b/src/server.rs @@ -0,0 +1,299 @@ +use super::*; + +use std::net::Ipv4Addr; + +/// The main entry point into the steam client for servers. +/// +/// This provides access to all of the steamworks api that +/// servers can use. +#[derive(Clone)] +pub struct Server { + inner: Arc<Inner<ServerManager>>, + server: *mut sys::ISteamGameServer, +} + +unsafe impl Send for Server {} +unsafe impl Sync for Server {} + +/// Used to set the mode that a gameserver will run in +pub enum ServerMode { + /// Don't authenticate user logins. + /// + /// The server will not appear on the server list + NoAuthentication, + /// Authenticate users + /// + /// The server will appear on the server list and + /// VAC will not be run on clients. + Authentication, + /// Authenticate users and use anti-cheat. + /// + /// The server will appear on the server list and + /// VAC will be run on clients. + AuthenticationAndSecure, +} + +impl Server { + /// Attempts to initialize the steamworks api and returns + /// a server to access the rest of the api. + /// + /// This should only ever have one instance per a program. + /// + /// Currently the steamworks api doesn't support IPv6. + /// + /// # Errors + /// + /// This can fail if: + /// * The steam client isn't running + /// * The app ID of the game couldn't be determined. + /// + /// If the game isn't being run through steam this can be provided by + /// placing a `steam_appid.txt` with the ID inside in the current + /// working directory + /// * The game isn't running on the same user/level as the steam client + /// * The user doesn't own a license for the game. + /// * The app ID isn't completely set up. + pub fn init( + ip: Ipv4Addr, steam_port: u16, + game_port: u16, query_port: u16, + server_mode: ServerMode, version: &str, + ) -> SResult<Server> { + unsafe { + let version = CString::new(version).unwrap(); + let raw_ip: u32 = ip.into(); + let server_mode = match server_mode { + ServerMode::NoAuthentication => sys::ServerMode::NoAuthentication, + ServerMode::Authentication => sys::ServerMode::Authentication, + ServerMode::AuthenticationAndSecure => sys::ServerMode::AuthenticationAndSecure, + }; + if sys::steam_rust_game_server_init( + raw_ip, steam_port, + game_port, query_port, + server_mode, + version.as_ptr() as *const _, + ) == 0 { + return Err(SteamError::InitFailed); + } + let server_raw = sys::steam_rust_get_server(); + let server = Arc::new(Inner { + _manager: ServerManager { _priv: () }, + callbacks: Mutex::new(Callbacks { + callbacks: Vec::new(), + call_results: HashMap::new(), + }), + }); + Ok(Server { + inner: server, + server: server_raw, + }) + } + } + /// Runs any currently pending callbacks + /// + /// This runs all currently pending callbacks on the current + /// thread. + /// + /// This should be called frequently (e.g. once per a frame) + /// in order to reduce the latency between recieving events. + pub fn run_callbacks(&self) { + unsafe { + sys::SteamGameServer_RunCallbacks(); + } + } + + /// Registers the passed function as a callback for the + /// given type. + /// + /// The callback will be run on the thread that `run_callbacks` + /// is called when the event arrives. + pub fn register_callback<C, F>(&self, f: F) + where C: Callback, + F: FnMut(C) + 'static + Send + Sync + { + unsafe { + register_callback(&self.inner, f, true); + } + } + + /// Returns the steam id of the current server + pub fn steam_id(&self) -> SteamId { + unsafe { + SteamId(sys::SteamAPI_ISteamGameServer_GetSteamID(self.server)) + } + } + + /// Retrieve an authentication session ticket that can be sent + /// to an entity that wishes to verify you. + /// + /// This ticket should not be reused. + /// + /// When creating ticket for use by the web API you should wait + /// for the `AuthSessionTicketResponse` event before trying to + /// use the ticket. + /// + /// When the multiplayer session terminates you must call + /// `cancel_authentication_ticket` + pub fn authentication_session_ticket(&self) -> (AuthTicket, Vec<u8>) { + unsafe { + let mut ticket = vec![0; 1024]; + let mut ticket_len = 0; + let auth_ticket = sys::SteamAPI_ISteamGameServer_GetAuthSessionTicket(self.server, ticket.as_mut_ptr() as *mut _, 1024, &mut ticket_len); + ticket.truncate(ticket_len as usize); + (AuthTicket(auth_ticket), ticket) + } + } + + /// Cancels an authentication session ticket received from + /// `authentication_session_ticket`. + /// + /// This should be called when you are no longer playing with + /// the specified entity. + pub fn cancel_authentication_ticket(&self, ticket: AuthTicket) { + unsafe { + sys::SteamAPI_ISteamGameServer_CancelAuthTicket(self.server, ticket.0); + } + } + + /// Authenticate the ticket from the steam ID to make sure it is + /// valid and not reused. + /// + /// A `ValidateAuthTicketResponse` callback will be fired if + /// the entity goes offline or cancels the ticket. + /// + /// When the multiplayer session terminates you must call + /// `end_authentication_session` + pub fn begin_authentication_session(&self, user: SteamId, ticket: &[u8]) -> Result<(), AuthSessionError> { + unsafe { + let res = sys::SteamAPI_ISteamGameServer_BeginAuthSession( + self.server, + ticket.as_ptr() as *const _, ticket.len() as _, + user.0 + ); + Err(match res { + sys::BeginAuthSessionResult::Ok => return Ok(()), + sys::BeginAuthSessionResult::InvalidTicket => AuthSessionError::InvalidTicket, + sys::BeginAuthSessionResult::DuplicateRequest => AuthSessionError::DuplicateRequest, + sys::BeginAuthSessionResult::InvalidVersion => AuthSessionError::InvalidVersion, + sys::BeginAuthSessionResult::GameMismatch => AuthSessionError::GameMismatch, + sys::BeginAuthSessionResult::ExpiredTicket => AuthSessionError::ExpiredTicket, + }) + } + } + + /// Ends an authentication session that was started with + /// `begin_authentication_session`. + /// + /// This should be called when you are no longer playing with + /// the specified entity. + pub fn end_authentication_session(&self, user: SteamId) { + unsafe { + sys::SteamAPI_ISteamGameServer_EndAuthSession(self.server, user.0); + } + } + + /// Sets the game product identifier. + /// + /// Used by the master server for version checking. Required + /// field but it will go away eventually. + pub fn set_product(&self, product: &str) { + unsafe { + let product = CString::new(product).unwrap(); + sys::SteamAPI_ISteamGameServer_SetProduct(self.server, product.as_ptr() as *const _); + } + } + + /// Sets the game description. + /// + /// Displayed in the steam server browser (for now). Required + /// field but it will go away eventually. + pub fn set_game_description(&self, desc: &str) { + unsafe { + let desc = CString::new(desc).unwrap(); + sys::SteamAPI_ISteamGameServer_SetGameDescription(self.server, desc.as_ptr() as *const _); + } + } + + /// Sets whether this server is dedicated or a listen server. + pub fn set_dedicated_server(&self, dedicated: bool) { + unsafe { + sys::SteamAPI_ISteamGameServer_SetDedicatedServer(self.server, dedicated as u8); + } + } + + /// Login to a generic anonymous account + pub fn log_on_anonymous(&self) { + unsafe { + sys::SteamAPI_ISteamGameServer_LogOnAnonymous(self.server); + } + } + + /* TODO: Buggy currently? + /// Returns an accessor to the steam apps interface + pub fn apps(&self) -> Apps<ServerManager> { + unsafe { + let apps = sys::steam_rust_get_server_apps(); + debug_assert!(!apps.is_null()); + Apps { + apps: apps, + _inner: self.inner.clone(), + } + } + } + */ +} + +#[test] +fn test() { + let server = Server::init( + [127, 0, 0, 1].into(), + 23333, 23334, 23335, + ServerMode::Authentication, "0.0.1" + ).unwrap(); + + println!("{:?}", server.steam_id()); + + server.set_product("steamworks-rs test"); + server.set_game_description("basic server test"); + server.set_dedicated_server(true); + server.log_on_anonymous(); + + println!("{:?}", server.steam_id()); + + server.register_callback(|v: AuthSessionTicketResponse| println!("{:?}", v)); + server.register_callback(|v: ValidateAuthTicketResponse| println!("{:?}", v)); + + let id = server.steam_id(); + let (auth, ticket) = server.authentication_session_ticket(); + + println!("{:?}", server.begin_authentication_session(id, &ticket)); + + for _ in 0 .. 20 { + server.run_callbacks(); + ::std::thread::sleep(::std::time::Duration::from_millis(50)); + } + + println!("END"); + + server.cancel_authentication_ticket(auth); + + for _ in 0 .. 20 { + server.run_callbacks(); + ::std::thread::sleep(::std::time::Duration::from_millis(50)); + } + + server.end_authentication_session(id); +} + + +/// Manages keeping the steam api active for servers +pub struct ServerManager { + _priv: (), +} + +impl Drop for ServerManager { + fn drop(&mut self) { + unsafe { + sys::SteamGameServer_Shutdown(); + } + } +}
\ No newline at end of file diff --git a/src/user.rs b/src/user.rs index 85a9795..3babc18 100644 --- a/src/user.rs +++ b/src/user.rs @@ -138,7 +138,7 @@ fn test() { /// A handle for an authentication ticket that can be used to cancel /// it. #[derive(Debug)] -pub struct AuthTicket(sys::HAuthTicket); +pub struct AuthTicket(pub(crate) sys::HAuthTicket); /// Called when generating a authentication session ticket. /// diff --git a/steamworks-sys/Cargo.toml b/steamworks-sys/Cargo.toml index db3af3d..d3b22cb 100644 --- a/steamworks-sys/Cargo.toml +++ b/steamworks-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "steamworks-sys" -version = "0.1.1" +version = "0.2.0" authors = ["Thinkofname"] build = "build.rs" description = "Provides raw bindings to the steamworks sdk" diff --git a/steamworks-sys/src/lib.cpp b/steamworks-sys/src/lib.cpp index 75c399a..bed54e6 100644 --- a/steamworks-sys/src/lib.cpp +++ b/steamworks-sys/src/lib.cpp @@ -1,12 +1,17 @@ #include <steam_api.h> +#include <steam_gameserver.h> +#include <stdint.h> class RustSteamCallback final : CCallbackBase { public: RustSteamCallback(int parameter_size, void *userdata, void (*run_func)(void *, void *), void (*dealloc)(void *), - int callback_id) + int callback_id, int game_server) : parameter_size(parameter_size), userdata(userdata), run_func(run_func), dealloc(dealloc) { + if (game_server) { + m_nCallbackFlags |= k_ECallbackFlagsGameServer; + } SteamAPI_RegisterCallback(this, callback_id); } ~RustSteamCallback() { @@ -31,9 +36,11 @@ extern "C" void *register_rust_steam_callback(int parameter_size, void *userdata, void (*run_func)(void *, void *), void (*dealloc)(void *), - int callback_id) { + int callback_id, + int game_server + ) { return new RustSteamCallback(parameter_size, userdata, run_func, dealloc, - callback_id); + callback_id, game_server); } extern "C" void unregister_rust_steam_callback(void *ty) { @@ -93,6 +100,13 @@ extern "C" void unregister_rust_steam_call_result(void *ty) { delete cb; } +extern "C" int steam_rust_game_server_init( + uint32_t ip, uint16_t steam_port, uint16_t game_port, + uint16_t query_port, EServerMode server_mode, const char* version +) { + return SteamGameServer_Init(ip, steam_port, game_port, query_port, server_mode, version); +} + extern "C" ISteamClient *steam_rust_get_client() { return SteamClient(); } extern "C" ISteamMatchmaking *steam_rust_get_matchmaking() { return SteamMatchmaking(); @@ -100,4 +114,6 @@ extern "C" ISteamMatchmaking *steam_rust_get_matchmaking() { extern "C" ISteamUtils *steam_rust_get_utils() { return SteamUtils(); } extern "C" ISteamApps *steam_rust_get_apps() { return SteamApps(); } extern "C" ISteamFriends *steam_rust_get_friends() { return SteamFriends(); } -extern "C" ISteamUser *steam_rust_get_user() { return SteamUser(); }
\ No newline at end of file +extern "C" ISteamUser *steam_rust_get_user() { return SteamUser(); } +extern "C" ISteamGameServer *steam_rust_get_server() { return SteamGameServer(); } +extern "C" ISteamApps *steam_rust_get_server_apps() { return SteamGameServerApps(); }
\ No newline at end of file diff --git a/steamworks-sys/src/lib.rs b/steamworks-sys/src/lib.rs index e84848b..42b5049 100644 --- a/steamworks-sys/src/lib.rs +++ b/steamworks-sys/src/lib.rs @@ -20,6 +20,8 @@ pub struct ISteamFriends(c_void); pub struct ISteamMatchmaking(c_void); #[repr(C)] pub struct ISteamUser(c_void); +#[repr(C)] +pub struct ISteamGameServer(c_void); pub type HSteamPipe = i32; pub type HSteamUser = i32; @@ -110,6 +112,14 @@ pub enum AuthSessionResponse { } #[repr(C)] +pub enum ServerMode { + Invalid = 0, + NoAuthentication = 1, + Authentication = 2, + AuthenticationAndSecure = 3, +} + +#[repr(C)] #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)] pub enum SResult { Ok = 1, @@ -235,6 +245,7 @@ extern "C" { run_func: extern "C" fn (*mut c_void, *mut c_void), dealloc: extern "C" fn (*mut c_void), callback_id: c_int, + game_server: c_int, ) -> *mut c_void; pub fn unregister_rust_steam_callback( ty: *mut c_void, @@ -257,12 +268,19 @@ extern "C" { pub fn steam_rust_get_apps() -> *mut ISteamApps; pub fn steam_rust_get_friends() -> *mut ISteamFriends; pub fn steam_rust_get_user() -> *mut ISteamUser; + pub fn steam_rust_get_server() -> *mut ISteamGameServer; + pub fn steam_rust_get_server_apps() -> *mut ISteamApps; + + pub fn steam_rust_game_server_init(ip: u32, steam_port: u16, game_port: u16, query_port: u16, server_mode: ServerMode, version: *const c_char) -> c_int; // pub fn SteamAPI_Init() -> u8; pub fn SteamAPI_Shutdown(); pub fn SteamAPI_RunCallbacks(); + pub fn SteamGameServer_Shutdown(); + pub fn SteamGameServer_RunCallbacks(); + pub fn SteamAPI_ISteamClient_CreateSteamPipe(instance: *mut ISteamClient) -> HSteamPipe; pub fn SteamAPI_ISteamClient_BReleaseSteamPipe(instance: *mut ISteamClient, pipe: HSteamPipe) -> u8; pub fn SteamAPI_ISteamClient_ConnectToGlobalUser(instance: *mut ISteamClient, pipe: HSteamPipe) -> HSteamUser; @@ -303,4 +321,14 @@ extern "C" { pub fn SteamAPI_ISteamUser_BeginAuthSession(instance: *mut ISteamUser, ticket: *const c_void, ticket_size: *mut u32, steam_id: u64) -> BeginAuthSessionResult; pub fn SteamAPI_ISteamUser_EndAuthSession(instance: *mut ISteamUser, steam_id: u64); pub fn SteamAPI_ISteamUser_CancelAuthTicket(instance: *mut ISteamUser, auth_ticket: HAuthTicket); + + pub fn SteamAPI_ISteamGameServer_LogOnAnonymous(instance: *mut ISteamGameServer); + pub fn SteamAPI_ISteamGameServer_SetProduct(instance: *mut ISteamGameServer, product: *const c_char); + pub fn SteamAPI_ISteamGameServer_SetGameDescription(instance: *mut ISteamGameServer, description: *const c_char); + pub fn SteamAPI_ISteamGameServer_SetDedicatedServer(instance: *mut ISteamGameServer, dedicated: u8); + pub fn SteamAPI_ISteamGameServer_GetSteamID(instance: *mut ISteamGameServer) -> u64; + pub fn SteamAPI_ISteamGameServer_GetAuthSessionTicket(instance: *mut ISteamGameServer, ticket: *mut c_void, max_ticket: c_int, ticket_size: *mut u32) -> HAuthTicket; + pub fn SteamAPI_ISteamGameServer_BeginAuthSession(instance: *mut ISteamGameServer, ticket: *const c_void, ticket_size: *mut u32, steam_id: u64) -> BeginAuthSessionResult; + pub fn SteamAPI_ISteamGameServer_EndAuthSession(instance: *mut ISteamGameServer, steam_id: u64); + pub fn SteamAPI_ISteamGameServer_CancelAuthTicket(instance: *mut ISteamGameServer, auth_ticket: HAuthTicket); } |