Skip to main content

steamworks/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[macro_use]
4extern crate thiserror;
5#[macro_use]
6extern crate bitflags;
7
8use screenshots::Screenshots;
9#[cfg(feature = "raw-bindings")]
10pub use steamworks_sys as sys;
11#[cfg(not(feature = "raw-bindings"))]
12use steamworks_sys as sys;
13use sys::{EServerMode, ESteamAPIInitResult, SteamErrMsg};
14
15use core::ffi::c_void;
16use std::collections::HashMap;
17use std::ffi::{c_char, CStr, CString};
18use std::fmt::{self, Debug, Formatter};
19use std::sync::mpsc::Sender;
20use std::sync::{Arc, Mutex, Weak};
21
22#[cfg(feature = "serde")]
23use serde::{Deserialize, Serialize};
24
25pub use crate::app::*;
26pub use crate::callback::*;
27pub use crate::error::*;
28pub use crate::friends::*;
29pub use crate::input::*;
30pub use crate::matchmaking::*;
31pub use crate::matchmaking_servers::*;
32pub use crate::networking::*;
33pub use crate::remote_play::*;
34pub use crate::remote_storage::*;
35pub use crate::server::*;
36pub use crate::timeline::*;
37pub use crate::ugc::*;
38pub use crate::user::*;
39pub use crate::user_stats::*;
40pub use crate::utils::*;
41
42#[macro_use]
43mod callback;
44mod app;
45mod error;
46mod friends;
47mod input;
48mod matchmaking;
49mod matchmaking_servers;
50mod networking;
51pub mod networking_messages;
52pub mod networking_sockets;
53mod networking_sockets_callback;
54pub mod networking_types;
55pub mod networking_utils;
56mod remote_play;
57mod remote_storage;
58pub mod screenshots;
59mod server;
60pub mod timeline;
61mod ugc;
62mod user;
63mod user_stats;
64mod utils;
65
66pub type SResult<T> = Result<T, SteamError>;
67
68pub type SIResult<T> = Result<T, SteamAPIInitError>;
69
70pub(crate) fn to_steam_result(result: sys::EResult) -> SResult<()> {
71    if result == sys::EResult::k_EResultOK {
72        Ok(())
73    } else {
74        Err(result.into())
75    }
76}
77
78// A note about thread-safety:
79// The steam api is assumed to be thread safe unless
80// the documentation for a method states otherwise,
81// however this is never stated anywhere in the docs
82// that I could see.
83
84/// The main entry point into the steam client.
85///
86/// This provides access to all of the steamworks api that
87/// clients can use.
88pub struct Client {
89    inner: Arc<Inner>,
90}
91
92impl Clone for Client {
93    fn clone(&self) -> Self {
94        Client {
95            inner: self.inner.clone(),
96        }
97    }
98}
99
100struct Inner {
101    manager: Manager,
102    callbacks: Callbacks,
103    networking_sockets_data: Mutex<NetworkingSocketsData>,
104}
105
106struct Callbacks {
107    callbacks: Mutex<HashMap<i32, Box<dyn FnMut(*mut c_void) + Send + 'static>>>,
108    call_results:
109        Mutex<HashMap<sys::SteamAPICall_t, Box<dyn FnOnce(*mut c_void, bool) + Send + 'static>>>,
110}
111
112impl Inner {
113    /// Runs any currently pending callbacks
114    ///
115    /// This runs all currently pending callbacks on the current
116    /// thread.
117    ///
118    /// This should be called frequently (e.g. once per a frame)
119    /// in order to reduce the latency between recieving events.
120    pub fn run_callbacks(&self) {
121        self.run_callbacks_raw(|cb_discrim, data| {
122            let mut callbacks = self.callbacks.callbacks.lock().unwrap();
123            if let Some(cb) = callbacks.get_mut(&cb_discrim) {
124                cb(data);
125            }
126        });
127    }
128
129    /// Runs any currently pending callbacks.
130    ///
131    /// This is identical to `run_callbacks` in every way, except that
132    /// `callback_handler` is called for every callback invoked.
133    ///
134    /// This option provides an alternative for handling callbacks that
135    /// can doesn't require the handler to be `Send`, and `'static`.
136    ///
137    /// This should be called frequently (e.g. once per a frame)
138    /// in order to reduce the latency between recieving events.
139    pub fn process_callbacks(&self, mut callback_handler: impl FnMut(CallbackResult)) {
140        self.run_callbacks_raw(|cb_discrim, data| {
141            {
142                let mut callbacks = self.callbacks.callbacks.lock().unwrap();
143                if let Some(cb) = callbacks.get_mut(&cb_discrim) {
144                    cb(data);
145                }
146            }
147            let cb_result = unsafe { CallbackResult::from_raw(cb_discrim, data) };
148            if let Some(cb_result) = cb_result {
149                callback_handler(cb_result);
150            }
151        });
152    }
153
154    fn run_callbacks_raw(&self, mut callback_handler: impl FnMut(i32, *mut c_void)) {
155        unsafe {
156            let pipe = self.manager.get_pipe();
157            sys::SteamAPI_ManualDispatch_RunFrame(pipe);
158            let mut callback = std::mem::zeroed();
159            let mut apicall_result = Vec::new();
160            while sys::SteamAPI_ManualDispatch_GetNextCallback(pipe, &mut callback) {
161                if callback.m_iCallback == sys::SteamAPICallCompleted_t_k_iCallback as i32 {
162                    let apicall = callback
163                        .m_pubParam
164                        .cast::<sys::SteamAPICallCompleted_t>()
165                        .read_unaligned();
166                    apicall_result.resize(apicall.m_cubParam as usize, 0u8);
167                    let mut failed = false;
168                    if sys::SteamAPI_ManualDispatch_GetAPICallResult(
169                        pipe,
170                        apicall.m_hAsyncCall,
171                        apicall_result.as_mut_ptr().cast(),
172                        apicall.m_cubParam as _,
173                        apicall.m_iCallback,
174                        &mut failed,
175                    ) {
176                        let mut call_results = self.callbacks.call_results.lock().unwrap();
177                        // The &{val} pattern here is to avoid taking a reference to a packed field
178                        // Since the value here is Copy, we can just copy it and borrow the copy
179                        if let Some(cb) = call_results.remove(&{ apicall.m_hAsyncCall }) {
180                            cb(apicall_result.as_mut_ptr().cast(), failed);
181                        }
182                    }
183                } else {
184                    callback_handler(callback.m_iCallback, callback.m_pubParam.cast());
185                }
186                sys::SteamAPI_ManualDispatch_FreeLastCallback(pipe);
187            }
188        }
189    }
190}
191
192struct NetworkingSocketsData {
193    sockets: HashMap<
194        sys::HSteamListenSocket,
195        (
196            Weak<networking_sockets::InnerSocket>,
197            Sender<networking_types::ListenSocketEvent>,
198        ),
199    >,
200    /// Connections to a remote listening port
201    independent_connections:
202        HashMap<sys::HSteamNetConnection, Sender<networking_types::NetConnectionEvent>>,
203    connection_callback: Weak<CallbackHandle>,
204}
205
206/// Returns true if the app wasn't launched through steam and
207/// begins relaunching it, the app should exit as soon as possible.
208///
209/// Returns false if the app was either launched through steam
210/// or has a `steam_appid.txt`
211pub fn restart_app_if_necessary(app_id: AppId) -> bool {
212    unsafe { sys::SteamAPI_RestartAppIfNecessary(app_id.0) }
213}
214
215fn static_assert_send<T: Send>() {}
216fn static_assert_sync<T>()
217where
218    T: Sync,
219{
220}
221
222impl Client {
223    /// Call to the native SteamAPI_Init function.
224    /// should not be used directly, but through either
225    /// init_flat() or init_flat_app()
226    unsafe fn steam_api_init_flat(p_out_err_msg: *mut SteamErrMsg) -> ESteamAPIInitResult {
227        unsafe { sys::SteamAPI_InitFlat(p_out_err_msg) }
228    }
229
230    /// Attempts to initialize the steamworks api without full API integration
231    /// through SteamAPI_InitFlat added in SDK 1.59
232    /// and returns a client to access the rest of the api.
233    ///
234    /// This should only ever have one instance per a program.
235    ///
236    /// # Errors
237    ///
238    /// This can fail if:
239    /// * The steam client isn't running
240    /// * The app ID of the game couldn't be determined.
241    ///
242    ///   If the game isn't being run through steam this can be provided by
243    ///   placing a `steam_appid.txt` with the ID inside in the current
244    ///   working directory. Alternatively, you can use `Client::init_app(<app_id>)`
245    ///   to force a specific app ID.
246    /// * The game isn't running on the same user/level as the steam client
247    /// * The user doesn't own a license for the game.
248    /// * The app ID isn't completely set up.
249    pub fn init() -> SIResult<Client> {
250        static_assert_send::<Client>();
251        static_assert_sync::<Client>();
252        unsafe {
253            let mut err_msg: sys::SteamErrMsg = [0; 1024];
254            let result = Self::steam_api_init_flat(&mut err_msg);
255
256            if result != sys::ESteamAPIInitResult::k_ESteamAPIInitResult_OK {
257                return Err(SteamAPIInitError::from_result_and_message(result, err_msg));
258            }
259
260            sys::SteamAPI_ManualDispatch_Init();
261            let client = Arc::new(Inner {
262                manager: Manager::Client,
263                callbacks: Callbacks {
264                    callbacks: Mutex::new(HashMap::new()),
265                    call_results: Mutex::new(HashMap::new()),
266                },
267                networking_sockets_data: Mutex::new(NetworkingSocketsData {
268                    sockets: Default::default(),
269                    independent_connections: Default::default(),
270                    connection_callback: Default::default(),
271                }),
272            });
273            Ok(Client { inner: client })
274        }
275    }
276
277    /// Attempts to initialize the steamworks api with the APP_ID
278    /// without full API integration through SteamAPI_InitFlat
279    /// and returns a client to access the rest of the api.
280    ///
281    /// This should only ever have one instance per a program.
282    ///
283    /// # Errors
284    ///
285    /// This can fail if:
286    /// * The steam client isn't running
287    /// * The game isn't running on the same user/level as the steam client
288    /// * The user doesn't own a license for the game.
289    /// * The app ID isn't completely set up.
290    pub fn init_app<ID: Into<AppId>>(app_id: ID) -> SIResult<Client> {
291        let app_id = app_id.into().0.to_string();
292        std::env::set_var("SteamAppId", &app_id);
293        std::env::set_var("SteamGameId", app_id);
294        Client::init()
295    }
296}
297
298impl Client {
299    /// Runs any currently pending callbacks
300    ///
301    /// This runs all currently pending callbacks on the current
302    /// thread.
303    ///
304    /// This should be called frequently (e.g. once per a frame)
305    /// in order to reduce the latency between recieving events.
306    pub fn run_callbacks(&self) {
307        self.inner.run_callbacks()
308    }
309
310    /// Runs any currently pending callbacks.
311    ///
312    /// This is identical to `run_callbacks` in every way, except that
313    /// `callback_handler` is called for every callback invoked.
314    ///
315    /// This option provides an alternative for handling callbacks that
316    /// can doesn't require the handler to be `Send`, and `'static`.
317    ///
318    /// This should be called frequently (e.g. once per a frame)
319    /// in order to reduce the latency between recieving events.
320    pub fn process_callbacks(&self, mut callback_handler: impl FnMut(CallbackResult)) {
321        self.inner.process_callbacks(&mut callback_handler)
322    }
323
324    /// Registers the passed function as a callback for the
325    /// given type.
326    ///
327    /// The callback will be run on the thread that [`run_callbacks`]
328    /// is called when the event arrives.
329    ///
330    /// If the callback handler cannot be made `Send` or `'static`
331    /// the call to [`run_callbacks`] should be replaced with a call to
332    /// [`process_callbacks`] instead.
333    ///
334    /// [`run_callbacks`]: Self::run_callbacks
335    /// [`process_callbacks`]: Self::process_callbacks
336    pub fn register_callback<C, F>(&self, f: F) -> CallbackHandle
337    where
338        C: Callback,
339        F: FnMut(C) + 'static + Send,
340    {
341        unsafe { register_callback(&self.inner, f) }
342    }
343
344    /// Returns an accessor to the steam utils interface
345    pub fn utils(&self) -> Utils {
346        unsafe {
347            let utils = sys::SteamAPI_SteamUtils_v010();
348            debug_assert!(!utils.is_null());
349            Utils {
350                utils: utils,
351                _inner: self.inner.clone(),
352            }
353        }
354    }
355
356    /// Returns an accessor to the steam matchmaking interface
357    pub fn matchmaking(&self) -> Matchmaking {
358        unsafe {
359            let mm = sys::SteamAPI_SteamMatchmaking_v009();
360            debug_assert!(!mm.is_null());
361            Matchmaking {
362                mm: mm,
363                inner: self.inner.clone(),
364            }
365        }
366    }
367
368    /// Returns an accessor to the steam matchmaking_servers interface
369    pub fn matchmaking_servers(&self) -> MatchmakingServers {
370        unsafe {
371            let mm = sys::SteamAPI_SteamMatchmakingServers_v002();
372            debug_assert!(!mm.is_null());
373            MatchmakingServers {
374                mms: mm,
375                _inner: self.inner.clone(),
376            }
377        }
378    }
379
380    /// Returns an accessor to the steam networking interface
381    pub fn networking(&self) -> Networking {
382        unsafe {
383            let net = sys::SteamAPI_SteamNetworking_v006();
384            debug_assert!(!net.is_null());
385            Networking {
386                net: net,
387                _inner: self.inner.clone(),
388            }
389        }
390    }
391
392    /// Returns an accessor to the steam apps interface
393    pub fn apps(&self) -> Apps {
394        unsafe {
395            let apps = sys::SteamAPI_SteamApps_v009();
396            debug_assert!(!apps.is_null());
397            Apps {
398                apps: apps,
399                _inner: self.inner.clone(),
400            }
401        }
402    }
403
404    /// Returns an accessor to the steam friends interface
405    pub fn friends(&self) -> Friends {
406        unsafe {
407            let friends = sys::SteamAPI_SteamFriends_v018();
408            debug_assert!(!friends.is_null());
409            Friends {
410                friends: friends,
411                inner: self.inner.clone(),
412            }
413        }
414    }
415
416    /// Returns an accessor to the steam input interface
417    pub fn input(&self) -> Input {
418        unsafe {
419            let input = sys::SteamAPI_SteamInput_v006();
420            debug_assert!(!input.is_null());
421            Input {
422                input,
423                _inner: self.inner.clone(),
424            }
425        }
426    }
427
428    /// Returns an accessor to the steam user interface
429    pub fn user(&self) -> User {
430        unsafe {
431            let user = sys::SteamAPI_SteamUser_v023();
432            debug_assert!(!user.is_null());
433            User {
434                user,
435                _inner: self.inner.clone(),
436            }
437        }
438    }
439
440    /// Returns an accessor to the steam user stats interface
441    pub fn user_stats(&self) -> UserStats {
442        unsafe {
443            let us = sys::SteamAPI_SteamUserStats_v013();
444            debug_assert!(!us.is_null());
445            UserStats {
446                user_stats: us,
447                inner: self.inner.clone(),
448            }
449        }
450    }
451
452    /// Returns an accessor to the steam remote play interface
453    pub fn remote_play(&self) -> RemotePlay {
454        unsafe {
455            let rp = sys::SteamAPI_SteamRemotePlay_v004();
456            debug_assert!(!rp.is_null());
457            RemotePlay {
458                rp,
459                inner: self.inner.clone(),
460            }
461        }
462    }
463
464    /// Returns an accessor to the steam remote storage interface
465    pub fn remote_storage(&self) -> RemoteStorage {
466        unsafe {
467            let rs = sys::SteamAPI_SteamRemoteStorage_v016();
468            debug_assert!(!rs.is_null());
469            let util = sys::SteamAPI_SteamUtils_v010();
470            debug_assert!(!util.is_null());
471            RemoteStorage {
472                rs,
473                util,
474                inner: self.inner.clone(),
475            }
476        }
477    }
478
479    /// Returns an accessor to the steam screenshots interface
480    pub fn screenshots(&self) -> Screenshots {
481        unsafe {
482            let screenshots = sys::SteamAPI_SteamScreenshots_v003();
483            debug_assert!(!screenshots.is_null());
484            Screenshots {
485                screenshots,
486                _inner: self.inner.clone(),
487            }
488        }
489    }
490
491    /// Returns an accessor to the steam UGC interface (steam workshop)
492    pub fn ugc(&self) -> UGC {
493        unsafe {
494            let ugc = sys::SteamAPI_SteamUGC_v021();
495            debug_assert!(!ugc.is_null());
496            UGC {
497                ugc,
498                inner: self.inner.clone(),
499            }
500        }
501    }
502
503    /// Returns an accessor to the steam timeline interface
504    pub fn timeline(&self) -> Timeline {
505        unsafe {
506            let timeline = sys::SteamAPI_SteamTimeline_v004();
507
508            Timeline {
509                timeline,
510                disabled: timeline.is_null(),
511                _inner: self.inner.clone(),
512            }
513        }
514    }
515
516    pub fn networking_messages(&self) -> networking_messages::NetworkingMessages {
517        unsafe {
518            let net = sys::SteamAPI_SteamNetworkingMessages_SteamAPI_v002();
519            debug_assert!(!net.is_null());
520            networking_messages::NetworkingMessages {
521                net,
522                inner: self.inner.clone(),
523            }
524        }
525    }
526
527    pub fn networking_sockets(&self) -> networking_sockets::NetworkingSockets {
528        unsafe {
529            let sockets = sys::SteamAPI_SteamNetworkingSockets_SteamAPI_v012();
530            debug_assert!(!sockets.is_null());
531            networking_sockets::NetworkingSockets {
532                sockets,
533                inner: self.inner.clone(),
534            }
535        }
536    }
537
538    pub fn networking_utils(&self) -> networking_utils::NetworkingUtils {
539        unsafe {
540            let utils = sys::SteamAPI_SteamNetworkingUtils_SteamAPI_v004();
541            debug_assert!(!utils.is_null());
542            networking_utils::NetworkingUtils {
543                utils,
544                inner: self.inner.clone(),
545            }
546        }
547    }
548}
549
550/// Used to separate client and game server modes
551enum Manager {
552    Client,
553    Server,
554}
555
556impl Manager {
557    /// Returns the pipe handle for the steam api
558    fn get_pipe(&self) -> sys::HSteamPipe {
559        match self {
560            Manager::Client => unsafe { sys::SteamAPI_GetHSteamPipe() },
561            Manager::Server => unsafe { sys::SteamGameServer_GetHSteamPipe() },
562        }
563    }
564}
565
566impl Drop for Manager {
567    fn drop(&mut self) {
568        // SAFETY: This is considered unsafe only because of FFI, the function is otherwise
569        // always safe to call from any thread.
570        match self {
571            Manager::Client => unsafe { sys::SteamAPI_Shutdown() },
572            Manager::Server => unsafe { sys::SteamGameServer_Shutdown() },
573        }
574    }
575}
576
577/// A user's steam id
578#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
579#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
580pub struct SteamId(pub(crate) u64);
581
582impl SteamId {
583    /// Creates a `SteamId` from a raw 64 bit value.
584    ///
585    /// May be useful for deserializing steam ids from
586    /// a network or save format.
587    pub fn from_raw(id: u64) -> SteamId {
588        SteamId(id)
589    }
590
591    /// Returns the raw 64 bit value of the steam id
592    ///
593    /// May be useful for serializing steam ids over a
594    /// network or to a save format.
595    pub fn raw(&self) -> u64 {
596        self.0
597    }
598
599    /// Returns whether or not this Steam ID is invalid, which is when `account_type` is `k_EAccountTypeInvalid`.
600    pub fn is_invalid(&self) -> bool {
601        unsafe {
602            let bits = sys::CSteamID_SteamID_t {
603                m_unAll64Bits: self.0,
604            };
605            bits.m_comp.m_EAccountType()
606                == sys::EAccountType::k_EAccountTypeInvalid as std::os::raw::c_uint
607        }
608    }
609
610    /// Returns the account id for this steam id
611    pub fn account_id(&self) -> AccountId {
612        unsafe {
613            let bits = sys::CSteamID_SteamID_t {
614                m_unAll64Bits: self.0,
615            };
616            AccountId(bits.m_comp.m_unAccountID())
617        }
618    }
619
620    /// Returns the Steam universe this Steam ID is part of.
621    pub fn universe(&self) -> Universe {
622        let bits = sys::CSteamID_SteamID_t {
623            m_unAll64Bits: self.0,
624        };
625        match unsafe { bits.m_comp }.m_EUniverse() {
626            sys::EUniverse::k_EUniversePublic => Universe::Public,
627            sys::EUniverse::k_EUniverseBeta => Universe::Beta,
628            sys::EUniverse::k_EUniverseInternal => Universe::Internal,
629            sys::EUniverse::k_EUniverseDev => Universe::Dev,
630            _ => Universe::Invalid,
631        }
632    }
633
634    pub fn account_type(&self) -> AccountType {
635        let bits = sys::CSteamID_SteamID_t {
636            m_unAll64Bits: self.0,
637        };
638        match unsafe { bits.m_comp }.m_EAccountType() {
639            x if x == sys::EAccountType::k_EAccountTypeIndividual as u32 => AccountType::Individual,
640            x if x == sys::EAccountType::k_EAccountTypeMultiseat as u32 => AccountType::Multiseat,
641            x if x == sys::EAccountType::k_EAccountTypeGameServer as u32 => AccountType::GameServer,
642            x if x == sys::EAccountType::k_EAccountTypeAnonGameServer as u32 => {
643                AccountType::AnonGameServer
644            }
645            x if x == sys::EAccountType::k_EAccountTypePending as u32 => AccountType::Pending,
646            x if x == sys::EAccountType::k_EAccountTypeContentServer as u32 => {
647                AccountType::ContentServer
648            }
649            x if x == sys::EAccountType::k_EAccountTypeClan as u32 => AccountType::Clan,
650            x if x == sys::EAccountType::k_EAccountTypeChat as u32 => AccountType::Chat,
651            x if x == sys::EAccountType::k_EAccountTypeConsoleUser as u32 => {
652                AccountType::ConsoleUser
653            }
654            x if x == sys::EAccountType::k_EAccountTypeAnonUser as u32 => AccountType::AnonUser,
655            _ => AccountType::Invalid,
656        }
657    }
658
659    /// Returns the formatted SteamID32 string for this steam id.
660    pub fn steamid32(&self) -> String {
661        let account_id = self.account_id().raw();
662        let last_bit = account_id & 1;
663        format!("STEAM_0:{}:{}", last_bit, (account_id >> 1))
664    }
665}
666
667/// Steam universes.
668///
669/// Each universe is a self-contained Steam instance.
670#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
671#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
672#[repr(u32)]
673pub enum Universe {
674    Invalid = 0,
675    Public = 1,
676    Beta = 2,
677    Internal = 3,
678    Dev = 4,
679}
680
681/// Steam account types.
682///
683/// [`SteamId`]s are used to identify many different types of entities within Steam.
684/// This type represents the type of account associated with a Steam ID.
685#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
686#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
687#[repr(u32)]
688pub enum AccountType {
689    /// Invalid user account type.
690    Invalid = 0,
691    /// Individual user account.
692    Individual = 1,
693    /// Multiseat (e.g. cybercafe) account.
694    Multiseat = 2,
695    /// Game server account with a fixed Steam ID.
696    GameServer = 3,
697    /// Anonymous game server account.
698    AnonGameServer = 4,
699    /// Pending account.
700    Pending = 5,
701    /// Identifies a Steam content server.
702    ContentServer = 6,
703    /// Identifies a Steam clan.
704    Clan = 7,
705    /// Identifies a Steam chat.
706    Chat = 8,
707    /// Fake SteamID for local PSN account on PS3 or Live account on 360, etc.
708    ConsoleUser = 9,
709    /// Anoynmous user account.
710    AnonUser = 10,
711}
712
713/// A user's account id
714#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
715#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
716pub struct AccountId(pub(crate) u32);
717
718impl AccountId {
719    /// Creates an `AccountId` from a raw 32 bit value.
720    ///
721    /// May be useful for deserializing account ids from
722    /// a network or save format.
723    pub fn from_raw(id: u32) -> AccountId {
724        AccountId(id)
725    }
726
727    /// Returns the raw 32 bit value of the steam id
728    ///
729    /// May be useful for serializing steam ids over a
730    /// network or to a save format.
731    pub fn raw(&self) -> u32 {
732        self.0
733    }
734}
735
736/// A game id
737///
738/// Combines `AppId` and other information
739#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
740#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
741pub struct GameId(#[cfg_attr(feature = "serde", serde(rename = "game_id"))] pub(crate) u64);
742
743impl GameId {
744    /// Creates a `GameId` from a raw 64 bit value.
745    ///
746    /// May be useful for deserializing game ids from
747    /// a network or save format.
748    pub fn from_raw(id: u64) -> GameId {
749        GameId(id)
750    }
751
752    /// Returns the raw 64 bit value of the game id
753    ///
754    /// May be useful for serializing game ids over a
755    /// network or to a save format.
756    pub fn raw(&self) -> u64 {
757        self.0
758    }
759
760    /// Returns the app id of this game
761    pub fn app_id(&self) -> AppId {
762        // TODO: Relies on internal details
763        AppId((self.0 & 0xFF_FF_FF) as u32)
764    }
765}
766
767#[cfg(test)]
768mod tests {
769    use serial_test::serial;
770
771    use super::*;
772
773    #[test]
774    #[serial]
775    fn basic_test() {
776        let client = Client::init().unwrap();
777
778        let _cb = client.register_callback(|p: PersonaStateChange| {
779            println!("Got callback: {:?}", p);
780        });
781
782        let utils = client.utils();
783        println!("Utils:");
784        println!("AppId: {:?}", utils.app_id());
785        println!("UI Language: {}", utils.ui_language());
786
787        let apps = client.apps();
788        println!("Apps");
789        println!("IsInstalled(480): {}", apps.is_app_installed(AppId(480)));
790        println!("InstallDir(480): {}", apps.app_install_dir(AppId(480)));
791        println!("BuildId: {}", apps.app_build_id());
792        println!("AppOwner: {:?}", apps.app_owner());
793        println!("Langs: {:?}", apps.available_game_languages());
794        println!("Lang: {}", apps.current_game_language());
795        println!("Beta: {:?}", apps.current_beta_name());
796
797        let friends = client.friends();
798        println!("Friends");
799        let list = friends.get_friends(FriendFlags::IMMEDIATE);
800        println!("{:?}", list);
801        for f in &list {
802            println!("Friend: {:?} - {}({:?})", f.id(), f.name(), f.state());
803            friends.request_user_information(f.id(), true);
804        }
805        friends.request_user_information(SteamId(76561198174976054), true);
806
807        for _ in 0..50 {
808            client.run_callbacks();
809            ::std::thread::sleep(::std::time::Duration::from_millis(100));
810        }
811    }
812
813    #[test]
814    fn steamid_test() {
815        let steamid = SteamId(76561198040894045);
816        assert_eq!("STEAM_0:1:40314158", steamid.steamid32());
817
818        let steamid = SteamId(76561198174976054);
819        assert_eq!("STEAM_0:0:107355163", steamid.steamid32());
820    }
821}