steamworks/
server.rs

1use super::*;
2use crate::networking_types::NetworkingIdentity;
3#[cfg(test)]
4use serial_test::serial;
5use std::net::{Ipv4Addr, SocketAddrV4};
6
7/// The main entry point into the steam client for servers.
8///
9/// This provides access to all of the steamworks api that
10/// servers can use.
11#[derive(Clone)]
12pub struct Server {
13    inner: Arc<Inner>,
14    server: *mut sys::ISteamGameServer,
15}
16
17unsafe impl Send for Server {}
18unsafe impl Sync for Server {}
19
20/// Used to set the mode that a gameserver will run in
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum ServerMode {
23    /// Don't authenticate user logins.
24    ///
25    /// The server will not appear on the server list
26    NoAuthentication,
27    /// Authenticate users
28    ///
29    /// The server will appear on the server list and
30    /// VAC will not be run on clients.
31    Authentication,
32    /// Authenticate users and use anti-cheat.
33    ///
34    /// The server will appear on the server list and
35    /// VAC will be run on clients.
36    AuthenticationAndSecure,
37}
38
39/// Pass to SteamGameServer_Init to indicate that the same UDP port will be used for game traffic
40/// UDP queries for server browser pings and LAN discovery.  In this case, Steam will not open up a
41/// socket to handle server browser queries, and you must use ISteamGameServer::HandleIncomingPacket
42/// and ISteamGameServer::GetNextOutgoingPacket to handle packets related to server discovery on
43/// your socket.
44pub const QUERY_PORT_SHARED: u16 = sys::STEAMGAMESERVER_QUERY_PORT_SHARED;
45
46impl Server {
47    fn steam_game_server_init_ex(
48        un_ip: std::ffi::c_uint,
49        us_game_port: std::ffi::c_ushort,
50        us_query_port: std::ffi::c_ushort,
51        e_server_mode: EServerMode,
52        pch_version_string: *const c_char,
53        p_out_err_msg: *mut SteamErrMsg,
54    ) -> ESteamAPIInitResult {
55        let versions: Vec<&[u8]> = vec![
56            sys::STEAMUTILS_INTERFACE_VERSION,
57            sys::STEAMNETWORKINGUTILS_INTERFACE_VERSION,
58            sys::STEAMGAMESERVER_INTERFACE_VERSION,
59            sys::STEAMGAMESERVERSTATS_INTERFACE_VERSION,
60            sys::STEAMHTTP_INTERFACE_VERSION,
61            sys::STEAMINVENTORY_INTERFACE_VERSION,
62            sys::STEAMNETWORKING_INTERFACE_VERSION,
63            sys::STEAMNETWORKINGMESSAGES_INTERFACE_VERSION,
64            sys::STEAMNETWORKINGSOCKETS_INTERFACE_VERSION,
65            sys::STEAMUGC_INTERFACE_VERSION,
66            b"\0",
67        ];
68
69        let merged_versions: Vec<u8> = versions.into_iter().flatten().cloned().collect();
70        let merged_versions_ptr = merged_versions.as_ptr().cast::<::std::os::raw::c_char>();
71
72        return unsafe {
73            sys::SteamInternal_GameServer_Init_V2(
74                un_ip,
75                us_game_port,
76                us_query_port,
77                e_server_mode,
78                pch_version_string,
79                merged_versions_ptr,
80                p_out_err_msg,
81            )
82        };
83    }
84
85    /// Attempts to initialize the steamworks api and returns
86    /// a server to access the rest of the api.
87    ///
88    /// This should only ever have one instance per a program.
89    ///
90    /// Currently the steamworks api doesn't support IPv6.
91    ///
92    /// # Errors
93    ///
94    /// This can fail if:
95    /// * The steam client isn't running
96    /// * The app ID of the game couldn't be determined.
97    ///
98    ///   If the game isn't being run through steam this can be provided by
99    ///   placing a `steam_appid.txt` with the ID inside in the current
100    ///   working directory
101    /// * The game isn't running on the same user/level as the steam client
102    /// * The user doesn't own a license for the game.
103    /// * The app ID isn't completely set up.
104    pub fn init(
105        ip: Ipv4Addr,
106        game_port: u16,
107        query_port: u16,
108        server_mode: ServerMode,
109        version: &str,
110    ) -> SIResult<(Server, Client)> {
111        unsafe {
112            let version = CString::new(version).unwrap();
113
114            // let internal_check_interface_versions =
115
116            let raw_ip: u32 = ip.into();
117            let server_mode = match server_mode {
118                ServerMode::NoAuthentication => sys::EServerMode::eServerModeNoAuthentication,
119                ServerMode::Authentication => sys::EServerMode::eServerModeAuthentication,
120                ServerMode::AuthenticationAndSecure => {
121                    sys::EServerMode::eServerModeAuthenticationAndSecure
122                }
123            };
124
125            let mut err_msg: sys::SteamErrMsg = [0; 1024];
126            let result = Self::steam_game_server_init_ex(
127                raw_ip,
128                game_port,
129                query_port,
130                server_mode,
131                version.as_ptr(),
132                &mut err_msg,
133            );
134
135            if result != sys::ESteamAPIInitResult::k_ESteamAPIInitResult_OK {
136                return Err(SteamAPIInitError::from_result_and_message(result, err_msg));
137            }
138
139            sys::SteamAPI_ManualDispatch_Init();
140            let server_raw = sys::SteamAPI_SteamGameServer_v015();
141            let server = Arc::new(Inner {
142                manager: Manager::Server,
143                callbacks: Callbacks {
144                    callbacks: Mutex::new(HashMap::new()),
145                    call_results: Mutex::new(HashMap::new()),
146                },
147                networking_sockets_data: Mutex::new(NetworkingSocketsData {
148                    sockets: Default::default(),
149                    independent_connections: Default::default(),
150                    connection_callback: Default::default(),
151                }),
152            });
153            Ok((
154                Server {
155                    inner: server.clone(),
156                    server: server_raw,
157                },
158                Client { inner: server },
159            ))
160        }
161    }
162
163    /// Runs any currently pending callbacks
164    ///
165    /// This runs all currently pending callbacks on the current
166    /// thread.
167    ///
168    /// This should be called frequently (e.g. once per a frame)
169    /// in order to reduce the latency between recieving events.
170    pub fn run_callbacks(&self) {
171        self.inner.run_callbacks()
172    }
173
174    /// Runs any currently pending callbacks.
175    ///
176    /// This is identical to `run_callbacks` in every way, except that
177    /// `callback_handler` is called for every callback invoked.
178    ///
179    /// This option provides an alternative for handling callbacks that
180    /// can doesn't require the handler to be `Send`, and `'static`.
181    ///
182    /// This should be called frequently (e.g. once per a frame)
183    /// in order to reduce the latency between recieving events.
184    pub fn process_callbacks(&self, mut callback_handler: impl FnMut(CallbackResult)) {
185        self.inner.process_callbacks(&mut callback_handler)
186    }
187
188    /// Registers the passed function as a callback for the
189    /// given type.
190    ///
191    /// The callback will be run on the thread that [`run_callbacks`]
192    /// is called when the event arrives.
193    ///
194    /// If the callback handler cannot be made `Send` or `'static`
195    /// the call to [`run_callbacks`] should be replaced with a call to
196    /// [`process_callbacks`] instead.
197    ///
198    /// [`run_callbacks`]: Self::run_callbacks
199    /// [`process_callbacks`]: Self::proceses_callbacks
200    pub fn register_callback<C, F>(&self, f: F) -> CallbackHandle
201    where
202        C: Callback,
203        F: FnMut(C) + 'static + Send,
204    {
205        unsafe { register_callback(&self.inner, f) }
206    }
207
208    /// Returns the steam id of the current server
209    pub fn steam_id(&self) -> SteamId {
210        unsafe { SteamId(sys::SteamAPI_ISteamGameServer_GetSteamID(self.server)) }
211    }
212
213    /// Retrieve an authentication session ticket that can be sent
214    /// to an entity that wishes to verify you.
215    ///
216    /// This ticket should not be reused.
217    ///
218    /// When creating ticket for use by the web API you should wait
219    /// for the `AuthSessionTicketResponse` event before trying to
220    /// use the ticket.
221    ///
222    /// When the multiplayer session terminates you must call
223    /// `cancel_authentication_ticket`
224    pub fn authentication_session_ticket_with_steam_id(
225        &self,
226        steam_id: SteamId,
227    ) -> (AuthTicket, Vec<u8>) {
228        self.authentication_session_ticket(NetworkingIdentity::new_steam_id(steam_id))
229    }
230    pub fn authentication_session_ticket(
231        &self,
232        network_identity: NetworkingIdentity,
233    ) -> (AuthTicket, Vec<u8>) {
234        unsafe {
235            let mut ticket = vec![0; 1024];
236            let mut ticket_len = 0;
237            let auth_ticket = sys::SteamAPI_ISteamGameServer_GetAuthSessionTicket(
238                self.server,
239                ticket.as_mut_ptr().cast(),
240                1024,
241                &mut ticket_len,
242                network_identity.as_ptr(),
243            );
244            ticket.truncate(ticket_len as usize);
245            (AuthTicket(auth_ticket), ticket)
246        }
247    }
248
249    /// Cancels an authentication session ticket received from
250    /// `authentication_session_ticket`.
251    ///
252    /// This should be called when you are no longer playing with
253    /// the specified entity.
254    pub fn cancel_authentication_ticket(&self, ticket: AuthTicket) {
255        unsafe {
256            sys::SteamAPI_ISteamGameServer_CancelAuthTicket(self.server, ticket.0);
257        }
258    }
259
260    /// Authenticate the ticket from the steam ID to make sure it is
261    /// valid and not reused.
262    ///
263    /// A `ValidateAuthTicketResponse` callback will be fired if
264    /// the entity goes offline or cancels the ticket.
265    ///
266    /// When the multiplayer session terminates you must call
267    /// `end_authentication_session`
268    pub fn begin_authentication_session(
269        &self,
270        user: SteamId,
271        ticket: &[u8],
272    ) -> Result<(), AuthSessionError> {
273        unsafe {
274            let res = sys::SteamAPI_ISteamGameServer_BeginAuthSession(
275                self.server,
276                ticket.as_ptr().cast(),
277                ticket.len() as _,
278                user.0,
279            );
280            Err(match res {
281                sys::EBeginAuthSessionResult::k_EBeginAuthSessionResultOK => return Ok(()),
282                sys::EBeginAuthSessionResult::k_EBeginAuthSessionResultInvalidTicket => {
283                    AuthSessionError::InvalidTicket
284                }
285                sys::EBeginAuthSessionResult::k_EBeginAuthSessionResultDuplicateRequest => {
286                    AuthSessionError::DuplicateRequest
287                }
288                sys::EBeginAuthSessionResult::k_EBeginAuthSessionResultInvalidVersion => {
289                    AuthSessionError::InvalidVersion
290                }
291                sys::EBeginAuthSessionResult::k_EBeginAuthSessionResultGameMismatch => {
292                    AuthSessionError::GameMismatch
293                }
294                sys::EBeginAuthSessionResult::k_EBeginAuthSessionResultExpiredTicket => {
295                    AuthSessionError::ExpiredTicket
296                }
297                _ => unreachable!(),
298            })
299        }
300    }
301
302    /// Ends an authentication session that was started with
303    /// `begin_authentication_session`.
304    ///
305    /// This should be called when you are no longer playing with
306    /// the specified entity.
307    pub fn end_authentication_session(&self, user: SteamId) {
308        unsafe {
309            sys::SteamAPI_ISteamGameServer_EndAuthSession(self.server, user.0);
310        }
311    }
312
313    /// Server browser related query packet processing for shared socket mode.  These are used
314    /// when you pass STEAMGAMESERVER_QUERY_PORT_SHARED as the query port to SteamGameServer_Init.
315    /// IP address and port are in host order, i.e 127.0.0.1 == 0x7f000001
316    ///
317    /// Source games use this to simplify the job of the server admins, so they
318    /// don't have to open up more ports on their firewalls.
319    ///
320    /// Call this when a packet that starts with 0xFFFFFFFF comes in on the shared socket.
321    pub fn handle_incoming_packet(&self, data: &[u8], addr: SocketAddrV4) -> bool {
322        unsafe {
323            let result = sys::SteamAPI_ISteamGameServer_HandleIncomingPacket(
324                self.server,
325                data.as_ptr() as _,
326                data.len() as _,
327                addr.ip().to_bits(),
328                addr.port(),
329            );
330            return result;
331        }
332    }
333
334    /// Call this function after calling handle_incoming_packet. The callback
335    /// function `cb` will be called for each outgoing packet that needs to be sent
336    /// to an address. The buffer must be at least 16384 bytes in size.
337    pub fn get_next_outgoing_packet(&self, buffer: &mut [u8], cb: impl Fn(SocketAddrV4, &[u8])) {
338        assert!(
339            buffer.len() >= 16 * 1024,
340            "Buffer size must be at least 16 KiB"
341        );
342
343        loop {
344            let mut addr = 0u32;
345            let mut port = 0u16;
346
347            let len = unsafe {
348                sys::SteamAPI_ISteamGameServer_GetNextOutgoingPacket(
349                    self.server,
350                    buffer.as_mut_ptr() as *mut _,
351                    buffer.len() as _,
352                    &mut addr,
353                    &mut port,
354                )
355            };
356
357            if len == 0 {
358                break; // No more packets to process
359            }
360
361            let addr = SocketAddrV4::new(Ipv4Addr::from_bits(addr), port);
362            cb(addr, &buffer[..len as usize]);
363        }
364    }
365
366    /// Sets the game product identifier. This is currently used by the master server for version
367    /// checking purposes. Converting the games app ID to a string for this is recommended.
368    ///
369    /// This is required for all game servers and can only be set before calling
370    /// log_on() or log_on_anonymous().
371    pub fn set_product(&self, product: &str) {
372        let product = CString::new(product).unwrap();
373        unsafe {
374            sys::SteamAPI_ISteamGameServer_SetProduct(self.server, product.as_ptr());
375        }
376    }
377
378    /// Sets the game description. Setting this to the full name of your game is recommended.
379    ///
380    /// This is required for all game servers and can only be set before calling
381    /// log_on() or log_on_anonymous().
382    pub fn set_game_description(&self, desc: &str) {
383        let desc = CString::new(desc).unwrap();
384        unsafe {
385            sys::SteamAPI_ISteamGameServer_SetGameDescription(self.server, desc.as_ptr());
386        }
387    }
388
389    /// Sets a string defining the "gamedata" for this server, this is optional, but if set it
390    /// allows users to filter in the matchmaking/server-browser interfaces based on the value.
391    ///
392    /// This is usually formatted as a comma or semicolon separated list.
393    ///
394    /// Don't set this unless it actually changes, its only uploaded to the master once; when
395    /// acknowledged.
396    pub fn set_game_data(&self, data: &str) {
397        let desc = CString::new(data).unwrap();
398        unsafe {
399            sys::SteamAPI_ISteamGameServer_SetGameData(self.server, desc.as_ptr());
400        }
401    }
402
403    /// Sets whether this server is dedicated or a listen server.
404    pub fn set_dedicated_server(&self, dedicated: bool) {
405        unsafe {
406            sys::SteamAPI_ISteamGameServer_SetDedicatedServer(self.server, dedicated);
407        }
408    }
409
410    /// Login to a generic anonymous account
411    pub fn log_on_anonymous(&self) {
412        unsafe {
413            sys::SteamAPI_ISteamGameServer_LogOnAnonymous(self.server);
414        }
415    }
416
417    /// Login to a generic account by token
418    pub fn log_on(&self, token: &str) {
419        let token = CString::new(token).unwrap();
420        unsafe {
421            sys::SteamAPI_ISteamGameServer_LogOn(self.server, token.as_ptr());
422        }
423    }
424
425    /// If active, updates the master server with this server's presence so players can find it via
426    /// the steam matchmaking/server browser interfaces.
427    pub fn enable_heartbeats(&self, active: bool) {
428        unsafe {
429            sys::SteamAPI_ISteamGameServer_SetAdvertiseServerActive(self.server, active);
430        }
431    }
432
433    /// Indicate whether you wish to be listed on the master server list
434    /// and/or respond to server browser / LAN discovery packets.
435    /// The server starts with this value set to false.  You should set all
436    /// relevant server parameters before enabling advertisement on the server.
437    ///
438    /// (This function used to be named EnableHeartbeats, so if you are wondering
439    /// where that function went, it's right here.  It does the same thing as before,
440    /// the old name was just confusing.)
441    #[inline(always)]
442    pub fn set_advertise_server_active(&self, active: bool) {
443        self.enable_heartbeats(active);
444    }
445
446    /// If your game is a "mod," pass the string that identifies it.  The default is an empty
447    /// string, meaning this application is the original game, not a mod.
448    pub fn set_mod_dir(&self, mod_dir: &str) {
449        let mod_dir = CString::new(mod_dir).unwrap();
450        unsafe {
451            sys::SteamAPI_ISteamGameServer_SetModDir(self.server, mod_dir.as_ptr());
452        }
453    }
454
455    /// Set name of map to report in the server browser
456    pub fn set_map_name(&self, map_name: &str) {
457        let map_name = CString::new(map_name).unwrap();
458        unsafe {
459            sys::SteamAPI_ISteamGameServer_SetMapName(self.server, map_name.as_ptr());
460        }
461    }
462
463    /// Set the name of server as it will appear in the server browser
464    pub fn set_server_name(&self, server_name: &str) {
465        let server_name = CString::new(server_name).unwrap();
466        unsafe {
467            sys::SteamAPI_ISteamGameServer_SetServerName(self.server, server_name.as_ptr());
468        }
469    }
470
471    /// Sets the maximum number of players allowed on the server at once.
472    ///
473    /// This value may be changed at any time.
474    pub fn set_max_players(&self, count: i32) {
475        unsafe {
476            sys::SteamAPI_ISteamGameServer_SetMaxPlayerCount(self.server, count);
477        }
478    }
479
480    /// Sets a string defining the "gametags" for this server, this is optional, but if set it
481    /// allows users to filter in the matchmaking/server-browser interfaces based on the value.
482    ///
483    /// This is usually formatted as a comma or semicolon separated list.
484    ///
485    /// Don't set this unless it actually changes, its only uploaded to the master once;
486    /// when acknowledged.
487    ///
488    /// The new "gametags" value to set. Must not be an empty string ("").
489    /// This can not be longer than 127.
490    pub fn set_game_tags(&self, tags: &str) {
491        assert!(tags.len() != 0, "tags must not be an empty string (\"\").");
492        assert!(tags.len() < 128, "tags can not be longer than 127.");
493
494        let tags = CString::new(tags).unwrap();
495        unsafe {
496            sys::SteamAPI_ISteamGameServer_SetGameTags(self.server, tags.as_ptr());
497        }
498    }
499
500    /// Add/update a rules key/value pair.
501    pub fn set_key_value(&self, key: &str, value: &str) {
502        let key = CString::new(key).unwrap();
503        let value = CString::new(value).unwrap();
504
505        unsafe {
506            sys::SteamAPI_ISteamGameServer_SetKeyValue(self.server, key.as_ptr(), value.as_ptr());
507        }
508    }
509
510    /// Clears the whole list of key/values that are sent in rules queries.
511    pub fn clear_all_key_values(&self) {
512        unsafe {
513            sys::SteamAPI_ISteamGameServer_ClearAllKeyValues(self.server);
514        }
515    }
516
517    /// Let people know if your server will require a password
518    pub fn set_password_protected(&self, b_password_protected: bool) {
519        unsafe {
520            sys::SteamAPI_ISteamGameServer_SetPasswordProtected(self.server, b_password_protected);
521        }
522    }
523
524    /// Number of bots.  Default value is zero
525    pub fn set_bot_player_count(&self, c_bot_players: i32) {
526        unsafe {
527            sys::SteamAPI_ISteamGameServer_SetBotPlayerCount(self.server, c_bot_players);
528        }
529    }
530
531    /// Returns an accessor to the steam UGC interface (steam workshop)
532    ///
533    /// **For this to work properly, you need to call `UGC::init_for_game_server()`!**
534    pub fn ugc(&self) -> UGC {
535        unsafe {
536            let ugc = sys::SteamAPI_SteamGameServerUGC_v021();
537            debug_assert!(!ugc.is_null());
538            UGC {
539                ugc,
540                inner: self.inner.clone(),
541            }
542        }
543    }
544
545    /// Returns an accessor to the steam utils interface
546    pub fn utils(&self) -> Utils {
547        unsafe {
548            let utils = sys::SteamAPI_SteamGameServerUtils_v010();
549            debug_assert!(!utils.is_null());
550            Utils {
551                utils: utils,
552                _inner: self.inner.clone(),
553            }
554        }
555    }
556
557    /// Returns an accessor to the steam networking interface
558    pub fn networking(&self) -> Networking {
559        unsafe {
560            let net = sys::SteamAPI_SteamGameServerNetworking_v006();
561            debug_assert!(!net.is_null());
562            Networking {
563                net: net,
564                _inner: self.inner.clone(),
565            }
566        }
567    }
568
569    pub fn networking_messages(&self) -> networking_messages::NetworkingMessages {
570        unsafe {
571            let net = sys::SteamAPI_SteamGameServerNetworkingMessages_SteamAPI_v002();
572            debug_assert!(!net.is_null());
573            networking_messages::NetworkingMessages {
574                net,
575                inner: self.inner.clone(),
576            }
577        }
578    }
579
580    pub fn networking_sockets(&self) -> networking_sockets::NetworkingSockets {
581        unsafe {
582            let sockets = sys::SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v012();
583            debug_assert!(!sockets.is_null());
584            networking_sockets::NetworkingSockets {
585                sockets,
586                inner: self.inner.clone(),
587            }
588        }
589    }
590
591    /* TODO: Buggy currently?
592    /// Returns an accessor to the steam apps interface
593    pub fn apps(&self) -> Apps {
594        unsafe {
595            let apps = sys::steam_rust_get_server_apps();
596            debug_assert!(!apps.is_null());
597            Apps {
598                apps: apps,
599                _inner: self.inner.clone(),
600            }
601        }
602    }
603    */
604}
605
606#[test]
607#[serial]
608fn test() {
609    let (server, single) = Server::init(
610        [127, 0, 0, 1].into(),
611        23334,
612        23335,
613        ServerMode::Authentication,
614        "0.0.1",
615    )
616    .unwrap();
617
618    println!("{:?}", server.steam_id());
619
620    server.set_product("steamworks-rs test");
621    server.set_game_description("basic server test");
622    server.set_dedicated_server(true);
623    server.log_on_anonymous();
624
625    println!("{:?}", server.steam_id());
626
627    let _cb = server.register_callback(|v: AuthSessionTicketResponse| {
628        println!("Got auth ticket response: {:?}", v.result)
629    });
630    let _cb = server.register_callback(|v: ValidateAuthTicketResponse| {
631        println!("Got validate auth ticket response: {:?}", v)
632    });
633
634    let id = server.steam_id();
635    let (auth, ticket) = server.authentication_session_ticket_with_steam_id(id);
636
637    println!("{:?}", server.begin_authentication_session(id, &ticket));
638
639    for _ in 0..20 {
640        single.run_callbacks();
641        ::std::thread::sleep(::std::time::Duration::from_millis(50));
642    }
643
644    println!("END");
645
646    server.cancel_authentication_ticket(auth);
647
648    for _ in 0..20 {
649        single.run_callbacks();
650        ::std::thread::sleep(::std::time::Duration::from_millis(50));
651    }
652
653    server.end_authentication_session(id);
654}
655
656/// Called when a client has been approved to connect to this game server
657#[derive(Clone, Debug)]
658#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
659pub struct GSClientApprove {
660    /// The steam ID of the user requesting a p2p session
661    pub user: SteamId,
662    /// Owner of the game, may be different from user when Family Sharing is being used
663    pub owner: SteamId,
664}
665
666impl_callback!(cb: GSClientApprove_t => GSClientApprove {
667    Self {
668        user: SteamId(cb.m_SteamID.m_steamid.m_unAll64Bits),
669        owner: SteamId(cb.m_OwnerSteamID.m_steamid.m_unAll64Bits),
670    }
671});
672
673/// Reason for when a client fails to join or is kicked from a game server.
674#[repr(i32)]
675#[derive(Clone, Copy, Debug, PartialEq, Eq)]
676#[non_exhaustive]
677#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
678pub enum DenyReason {
679    Invalid,
680    InvalidVersion,
681    Generic,
682    NotLoggedOn,
683    NoLicense,
684    Cheater,
685    LoggedInElseWhere,
686    UnknownText,
687    IncompatibleAnticheat,
688    MemoryCorruption,
689    IncompatibleSoftware,
690    SteamConnectionLost,
691    SteamConnectionError,
692    SteamResponseTimedOut,
693    SteamValidationStalled,
694    SteamOwnerLeftGuestUser,
695}
696
697impl From<sys::EDenyReason> for DenyReason {
698    fn from(r: sys::EDenyReason) -> Self {
699        match r {
700            sys::EDenyReason::k_EDenyInvalid => DenyReason::Invalid,
701            sys::EDenyReason::k_EDenyInvalidVersion => DenyReason::InvalidVersion,
702            sys::EDenyReason::k_EDenyGeneric => DenyReason::Generic,
703            sys::EDenyReason::k_EDenyNotLoggedOn => DenyReason::NotLoggedOn,
704            sys::EDenyReason::k_EDenyNoLicense => DenyReason::NoLicense,
705            sys::EDenyReason::k_EDenyCheater => DenyReason::Cheater,
706            sys::EDenyReason::k_EDenyLoggedInElseWhere => DenyReason::LoggedInElseWhere,
707            sys::EDenyReason::k_EDenyUnknownText => DenyReason::UnknownText,
708            sys::EDenyReason::k_EDenyIncompatibleAnticheat => DenyReason::IncompatibleAnticheat,
709            sys::EDenyReason::k_EDenyMemoryCorruption => DenyReason::MemoryCorruption,
710            sys::EDenyReason::k_EDenyIncompatibleSoftware => DenyReason::IncompatibleSoftware,
711            sys::EDenyReason::k_EDenySteamConnectionLost => DenyReason::SteamConnectionLost,
712            sys::EDenyReason::k_EDenySteamConnectionError => DenyReason::SteamConnectionError,
713            sys::EDenyReason::k_EDenySteamResponseTimedOut => DenyReason::SteamResponseTimedOut,
714            sys::EDenyReason::k_EDenySteamValidationStalled => DenyReason::SteamValidationStalled,
715            sys::EDenyReason::k_EDenySteamOwnerLeftGuestUser => DenyReason::SteamOwnerLeftGuestUser,
716            _ => DenyReason::Invalid,
717        }
718    }
719}
720
721/// Called when a user has been denied to connection to this game server
722#[derive(Clone, Debug)]
723#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
724pub struct GSClientDeny {
725    /// The steam ID of the user requesting a p2p session
726    pub user: SteamId,
727    pub deny_reason: DenyReason,
728    pub optional_text: String,
729}
730
731impl_callback!(cb: GSClientDeny_t => GSClientDeny {
732    let deny_text = unsafe {
733        let cstr = CStr::from_ptr(cb.m_rgchOptionalText.as_ptr() as *const c_char);
734        cstr.to_string_lossy().to_owned().into_owned()
735    };
736
737    GSClientDeny {
738        user: SteamId(cb.m_SteamID.m_steamid.m_unAll64Bits),
739        deny_reason: DenyReason::from(cb.m_eDenyReason),
740        optional_text: deny_text,
741    }
742});
743
744/// Called when a user has been denied to connection to this game server
745#[derive(Clone, Debug)]
746#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
747pub struct GSClientKick {
748    /// The steam ID of the user requesting a p2p session
749    pub user: SteamId,
750    pub deny_reason: DenyReason,
751}
752
753impl_callback!(cb: GSClientKick_t => GSClientKick {
754    Self {
755        user: SteamId(cb.m_SteamID.m_steamid.m_unAll64Bits),
756        deny_reason: DenyReason::from(cb.m_eDenyReason),
757    }
758});
759
760/// Called when we have received the group status of a user
761#[derive(Clone, Debug)]
762#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
763pub struct GSClientGroupStatus {
764    /// The steam ID of the user we queried
765    pub user: SteamId,
766    pub group: SteamId,
767    pub member: bool,
768    pub officer: bool,
769}
770
771impl_callback!(cb: GSClientGroupStatus_t => GSClientGroupStatus {
772    Self {
773        user: SteamId(cb.m_SteamIDUser.m_steamid.m_unAll64Bits),
774        group: SteamId(cb.m_SteamIDGroup.m_steamid.m_unAll64Bits),
775        member: cb.m_bMember,
776        officer: cb.m_bOfficer,
777    }
778});