steamworks/
lib.rs

1#[macro_use]
2extern crate thiserror;
3#[macro_use]
4extern crate bitflags;
5#[macro_use]
6extern crate lazy_static;
7
8#[cfg(feature = "raw-bindings")]
9pub use steamworks_sys as sys;
10#[cfg(not(feature = "raw-bindings"))]
11use steamworks_sys as sys;
12use sys::{EServerMode, ESteamAPIInitResult, SteamErrMsg};
13
14use core::ffi::c_void;
15use std::collections::HashMap;
16use std::ffi::{c_char, CStr, CString};
17use std::fmt::{self, Debug, Formatter};
18use std::marker::PhantomData;
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::networking::*;
32pub use crate::remote_play::*;
33pub use crate::remote_storage::*;
34pub use crate::server::*;
35pub use crate::ugc::*;
36pub use crate::user::*;
37pub use crate::user_stats::*;
38pub use crate::utils::*;
39
40mod app;
41mod callback;
42mod error;
43mod friends;
44mod input;
45mod matchmaking;
46mod networking;
47pub mod networking_messages;
48pub mod networking_sockets;
49mod networking_sockets_callback;
50pub mod networking_types;
51pub mod networking_utils;
52mod remote_play;
53mod remote_storage;
54mod server;
55mod ugc;
56mod user;
57mod user_stats;
58mod utils;
59
60pub type SResult<T> = Result<T, SteamError>;
61
62pub type SIResult<T> = Result<T, SteamAPIInitError>;
63
64// A note about thread-safety:
65// The steam api is assumed to be thread safe unless
66// the documentation for a method states otherwise,
67// however this is never stated anywhere in the docs
68// that I could see.
69
70/// The main entry point into the steam client.
71///
72/// This provides access to all of the steamworks api that
73/// clients can use.
74pub struct Client<Manager = ClientManager> {
75    inner: Arc<Inner<Manager>>,
76}
77
78impl<Manager> Clone for Client<Manager> {
79    fn clone(&self) -> Self {
80        Client {
81            inner: self.inner.clone(),
82        }
83    }
84}
85
86/// Allows access parts of the steam api that can only be called
87/// on a single thread at any given time.
88pub struct SingleClient<Manager = ClientManager> {
89    inner: Arc<Inner<Manager>>,
90    _not_sync: PhantomData<*mut ()>,
91}
92
93struct Inner<Manager> {
94    _manager: Manager,
95    callbacks: Mutex<Callbacks>,
96    networking_sockets_data: Mutex<NetworkingSocketsData<Manager>>,
97}
98
99struct Callbacks {
100    callbacks: HashMap<i32, Box<dyn FnMut(*mut c_void) + Send + 'static>>,
101    call_results: HashMap<sys::SteamAPICall_t, Box<dyn FnOnce(*mut c_void, bool) + Send + 'static>>,
102}
103
104struct NetworkingSocketsData<Manager> {
105    sockets: HashMap<
106        sys::HSteamListenSocket,
107        (
108            Weak<networking_sockets::InnerSocket<Manager>>,
109            Sender<networking_types::ListenSocketEvent<Manager>>,
110        ),
111    >,
112    /// Connections to a remote listening port
113    independent_connections: HashMap<sys::HSteamNetConnection, Sender<()>>,
114    connection_callback: Weak<CallbackHandle<Manager>>,
115}
116
117unsafe impl<Manager: Send + Sync> Send for Inner<Manager> {}
118unsafe impl<Manager: Send + Sync> Sync for Inner<Manager> {}
119unsafe impl<Manager: Send + Sync> Send for Client<Manager> {}
120unsafe impl<Manager: Send + Sync> Sync for Client<Manager> {}
121unsafe impl<Manager: Send + Sync> Send for SingleClient<Manager> {}
122
123/// Returns true if the app wasn't launched through steam and
124/// begins relaunching it, the app should exit as soon as possible.
125///
126/// Returns false if the app was either launched through steam
127/// or has a `steam_appid.txt`
128pub fn restart_app_if_necessary(app_id: AppId) -> bool {
129    unsafe { sys::SteamAPI_RestartAppIfNecessary(app_id.0) }
130}
131
132fn static_assert_send<T: Send>() {}
133fn static_assert_sync<T>()
134where
135    T: Sync,
136{
137}
138
139impl Client<ClientManager> {
140    fn steam_api_init_ex(p_out_err_msg: *mut SteamErrMsg) -> ESteamAPIInitResult {
141        let versions: Vec<&[u8]> = vec![
142            sys::STEAMUTILS_INTERFACE_VERSION,
143            sys::STEAMNETWORKINGUTILS_INTERFACE_VERSION,
144            sys::STEAMAPPLIST_INTERFACE_VERSION,
145            sys::STEAMAPPS_INTERFACE_VERSION,
146            sys::STEAMCONTROLLER_INTERFACE_VERSION,
147            sys::STEAMFRIENDS_INTERFACE_VERSION,
148            sys::STEAMGAMESEARCH_INTERFACE_VERSION,
149            sys::STEAMHTMLSURFACE_INTERFACE_VERSION,
150            sys::STEAMHTTP_INTERFACE_VERSION,
151            sys::STEAMINPUT_INTERFACE_VERSION,
152            sys::STEAMINVENTORY_INTERFACE_VERSION,
153            sys::STEAMMATCHMAKINGSERVERS_INTERFACE_VERSION,
154            sys::STEAMMATCHMAKING_INTERFACE_VERSION,
155            sys::STEAMMUSICREMOTE_INTERFACE_VERSION,
156            sys::STEAMMUSIC_INTERFACE_VERSION,
157            sys::STEAMNETWORKINGMESSAGES_INTERFACE_VERSION,
158            sys::STEAMNETWORKINGSOCKETS_INTERFACE_VERSION,
159            sys::STEAMNETWORKING_INTERFACE_VERSION,
160            sys::STEAMPARENTALSETTINGS_INTERFACE_VERSION,
161            sys::STEAMPARTIES_INTERFACE_VERSION,
162            sys::STEAMREMOTEPLAY_INTERFACE_VERSION,
163            sys::STEAMREMOTESTORAGE_INTERFACE_VERSION,
164            sys::STEAMSCREENSHOTS_INTERFACE_VERSION,
165            sys::STEAMUGC_INTERFACE_VERSION,
166            sys::STEAMUSERSTATS_INTERFACE_VERSION,
167            sys::STEAMUSER_INTERFACE_VERSION,
168            sys::STEAMVIDEO_INTERFACE_VERSION,
169            b"\0",
170        ];
171
172        let merged_versions: Vec<u8> = versions.into_iter().flatten().cloned().collect();
173        let merged_versions_ptr = merged_versions.as_ptr() as *const ::std::os::raw::c_char;
174
175        unsafe { sys::SteamInternal_SteamAPI_Init(merged_versions_ptr, p_out_err_msg) }
176    }
177
178    /// Attempts to initialize the steamworks api and returns
179    /// a client to access the rest of the api.
180    ///
181    /// This should only ever have one instance per a program.
182    ///
183    /// # Errors
184    ///
185    /// This can fail if:
186    /// * The steam client isn't running
187    /// * The app ID of the game couldn't be determined.
188    ///
189    ///   If the game isn't being run through steam this can be provided by
190    ///   placing a `steam_appid.txt` with the ID inside in the current
191    ///   working directory. Alternatively, you can use `Client::init_app(<app_id>)`
192    ///   to force a specific app ID.
193    /// * The game isn't running on the same user/level as the steam client
194    /// * The user doesn't own a license for the game.
195    /// * The app ID isn't completely set up.
196    pub fn init() -> SIResult<(Client<ClientManager>, SingleClient<ClientManager>)> {
197        static_assert_send::<Client<ClientManager>>();
198        static_assert_sync::<Client<ClientManager>>();
199        static_assert_send::<SingleClient<ClientManager>>();
200        unsafe {
201            let mut err_msg: sys::SteamErrMsg = [0; 1024];
202            let result = Self::steam_api_init_ex(&mut err_msg);
203
204            if result != sys::ESteamAPIInitResult::k_ESteamAPIInitResult_OK {
205                return Err(SteamAPIInitError::from_result_and_message(result, err_msg));
206            }
207
208            sys::SteamAPI_ManualDispatch_Init();
209            let client = Arc::new(Inner {
210                _manager: ClientManager { _priv: () },
211                callbacks: Mutex::new(Callbacks {
212                    callbacks: HashMap::new(),
213                    call_results: HashMap::new(),
214                }),
215                networking_sockets_data: Mutex::new(NetworkingSocketsData {
216                    sockets: Default::default(),
217                    independent_connections: Default::default(),
218                    connection_callback: Default::default(),
219                }),
220            });
221            Ok((
222                Client {
223                    inner: client.clone(),
224                },
225                SingleClient {
226                    inner: client,
227                    _not_sync: PhantomData,
228                },
229            ))
230        }
231    }
232
233    /// Attempts to initialize the steamworks api **for a specified app ID**
234    /// and returns a client to access the rest of the api.
235    ///
236    /// This should only ever have one instance per a program.
237    ///
238    /// # Errors
239    ///
240    /// This can fail if:
241    /// * The steam client isn't running
242    /// * The game isn't running on the same user/level as the steam client
243    /// * The user doesn't own a license for the game.
244    /// * The app ID isn't completely set up.
245    pub fn init_app<ID: Into<AppId>>(
246        app_id: ID,
247    ) -> SIResult<(Client<ClientManager>, SingleClient<ClientManager>)> {
248        let app_id = app_id.into().0.to_string();
249        std::env::set_var("SteamAppId", &app_id);
250        std::env::set_var("SteamGameId", app_id);
251        Client::init()
252    }
253}
254impl<M> SingleClient<M>
255where
256    M: Manager,
257{
258    /// Runs any currently pending callbacks
259    ///
260    /// This runs all currently pending callbacks on the current
261    /// thread.
262    ///
263    /// This should be called frequently (e.g. once per a frame)
264    /// in order to reduce the latency between recieving events.
265    pub fn run_callbacks(&self) {
266        unsafe {
267            let pipe = M::get_pipe();
268            sys::SteamAPI_ManualDispatch_RunFrame(pipe);
269            let mut callback = std::mem::zeroed();
270            while sys::SteamAPI_ManualDispatch_GetNextCallback(pipe, &mut callback) {
271                let mut callbacks = self.inner.callbacks.lock().unwrap();
272                if callback.m_iCallback == sys::SteamAPICallCompleted_t_k_iCallback as i32 {
273                    let apicall =
274                        &mut *(callback.m_pubParam as *mut _ as *mut sys::SteamAPICallCompleted_t);
275                    let mut apicall_result = vec![0; apicall.m_cubParam as usize];
276                    let mut failed = false;
277                    if sys::SteamAPI_ManualDispatch_GetAPICallResult(
278                        pipe,
279                        apicall.m_hAsyncCall,
280                        apicall_result.as_mut_ptr() as *mut _,
281                        apicall.m_cubParam as _,
282                        apicall.m_iCallback,
283                        &mut failed,
284                    ) {
285                        // The &{val} pattern here is to avoid taking a reference to a packed field
286                        // Since the value here is Copy, we can just copy it and borrow the copy
287                        if let Some(cb) = callbacks.call_results.remove(&{ apicall.m_hAsyncCall }) {
288                            cb(apicall_result.as_mut_ptr() as *mut _, failed);
289                        }
290                    }
291                } else {
292                    if let Some(cb) = callbacks.callbacks.get_mut(&callback.m_iCallback) {
293                        cb(callback.m_pubParam as *mut _);
294                    }
295                }
296                sys::SteamAPI_ManualDispatch_FreeLastCallback(pipe);
297            }
298        }
299    }
300}
301
302impl<Manager> Client<Manager> {
303    /// Registers the passed function as a callback for the
304    /// given type.
305    ///
306    /// The callback will be run on the thread that `run_callbacks`
307    /// is called when the event arrives.
308    pub fn register_callback<C, F>(&self, f: F) -> CallbackHandle<Manager>
309    where
310        C: Callback,
311        F: FnMut(C) + 'static + Send,
312    {
313        unsafe { register_callback(&self.inner, f) }
314    }
315
316    /// Returns an accessor to the steam utils interface
317    pub fn utils(&self) -> Utils<Manager> {
318        unsafe {
319            let utils = sys::SteamAPI_SteamUtils_v010();
320            debug_assert!(!utils.is_null());
321            Utils {
322                utils: utils,
323                _inner: self.inner.clone(),
324            }
325        }
326    }
327
328    /// Returns an accessor to the steam matchmaking interface
329    pub fn matchmaking(&self) -> Matchmaking<Manager> {
330        unsafe {
331            let mm = sys::SteamAPI_SteamMatchmaking_v009();
332            debug_assert!(!mm.is_null());
333            Matchmaking {
334                mm: mm,
335                inner: self.inner.clone(),
336            }
337        }
338    }
339
340    /// Returns an accessor to the steam networking interface
341    pub fn networking(&self) -> Networking<Manager> {
342        unsafe {
343            let net = sys::SteamAPI_SteamNetworking_v006();
344            debug_assert!(!net.is_null());
345            Networking {
346                net: net,
347                _inner: self.inner.clone(),
348            }
349        }
350    }
351
352    /// Returns an accessor to the steam apps interface
353    pub fn apps(&self) -> Apps<Manager> {
354        unsafe {
355            let apps = sys::SteamAPI_SteamApps_v008();
356            debug_assert!(!apps.is_null());
357            Apps {
358                apps: apps,
359                _inner: self.inner.clone(),
360            }
361        }
362    }
363
364    /// Returns an accessor to the steam friends interface
365    pub fn friends(&self) -> Friends<Manager> {
366        unsafe {
367            let friends = sys::SteamAPI_SteamFriends_v017();
368            debug_assert!(!friends.is_null());
369            Friends {
370                friends: friends,
371                inner: self.inner.clone(),
372            }
373        }
374    }
375
376    /// Returns an accessor to the steam input interface
377    pub fn input(&self) -> Input<Manager> {
378        unsafe {
379            let input = sys::SteamAPI_SteamInput_v006();
380            debug_assert!(!input.is_null());
381            Input {
382                input,
383                _inner: self.inner.clone(),
384            }
385        }
386    }
387
388    /// Returns an accessor to the steam user interface
389    pub fn user(&self) -> User<Manager> {
390        unsafe {
391            let user = sys::SteamAPI_SteamUser_v023();
392            debug_assert!(!user.is_null());
393            User {
394                user,
395                _inner: self.inner.clone(),
396            }
397        }
398    }
399
400    /// Returns an accessor to the steam user stats interface
401    pub fn user_stats(&self) -> UserStats<Manager> {
402        unsafe {
403            let us = sys::SteamAPI_SteamUserStats_v012();
404            debug_assert!(!us.is_null());
405            UserStats {
406                user_stats: us,
407                inner: self.inner.clone(),
408            }
409        }
410    }
411
412    /// Returns an accessor to the steam remote play interface
413    pub fn remote_play(&self) -> RemotePlay<Manager> {
414        unsafe {
415            let rp = sys::SteamAPI_SteamRemotePlay_v002();
416            debug_assert!(!rp.is_null());
417            RemotePlay {
418                rp,
419                inner: self.inner.clone(),
420            }
421        }
422    }
423
424    /// Returns an accessor to the steam remote storage interface
425    pub fn remote_storage(&self) -> RemoteStorage<Manager> {
426        unsafe {
427            let rs = sys::SteamAPI_SteamRemoteStorage_v016();
428            debug_assert!(!rs.is_null());
429            let util = sys::SteamAPI_SteamUtils_v010();
430            debug_assert!(!util.is_null());
431            RemoteStorage {
432                rs,
433                util,
434                inner: self.inner.clone(),
435            }
436        }
437    }
438
439    /// Returns an accessor to the steam UGC interface (steam workshop)
440    pub fn ugc(&self) -> UGC<Manager> {
441        unsafe {
442            let ugc = sys::SteamAPI_SteamUGC_v018();
443            debug_assert!(!ugc.is_null());
444            UGC {
445                ugc,
446                inner: self.inner.clone(),
447            }
448        }
449    }
450
451    pub fn networking_messages(&self) -> networking_messages::NetworkingMessages<Manager> {
452        unsafe {
453            let net = sys::SteamAPI_SteamNetworkingMessages_SteamAPI_v002();
454            debug_assert!(!net.is_null());
455            networking_messages::NetworkingMessages {
456                net,
457                inner: self.inner.clone(),
458            }
459        }
460    }
461
462    pub fn networking_sockets(&self) -> networking_sockets::NetworkingSockets<Manager> {
463        unsafe {
464            let sockets = sys::SteamAPI_SteamNetworkingSockets_SteamAPI_v012();
465            debug_assert!(!sockets.is_null());
466            networking_sockets::NetworkingSockets {
467                sockets,
468                inner: self.inner.clone(),
469            }
470        }
471    }
472
473    pub fn networking_utils(&self) -> networking_utils::NetworkingUtils<Manager> {
474        unsafe {
475            let utils = sys::SteamAPI_SteamNetworkingUtils_SteamAPI_v004();
476            debug_assert!(!utils.is_null());
477            networking_utils::NetworkingUtils {
478                utils,
479                inner: self.inner.clone(),
480            }
481        }
482    }
483}
484
485/// Used to separate client and game server modes
486pub unsafe trait Manager {
487    unsafe fn get_pipe() -> sys::HSteamPipe;
488}
489
490/// Manages keeping the steam api active for clients
491pub struct ClientManager {
492    _priv: (),
493}
494
495unsafe impl Manager for ClientManager {
496    unsafe fn get_pipe() -> sys::HSteamPipe {
497        sys::SteamAPI_GetHSteamPipe()
498    }
499}
500
501impl Drop for ClientManager {
502    fn drop(&mut self) {
503        unsafe {
504            sys::SteamAPI_Shutdown();
505        }
506    }
507}
508
509/// A user's steam id
510#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
511#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
512pub struct SteamId(pub(crate) u64);
513
514impl SteamId {
515    /// Creates a `SteamId` from a raw 64 bit value.
516    ///
517    /// May be useful for deserializing steam ids from
518    /// a network or save format.
519    pub fn from_raw(id: u64) -> SteamId {
520        SteamId(id)
521    }
522
523    /// Returns the raw 64 bit value of the steam id
524    ///
525    /// May be useful for serializing steam ids over a
526    /// network or to a save format.
527    pub fn raw(&self) -> u64 {
528        self.0
529    }
530
531    /// Returns the account id for this steam id
532    pub fn account_id(&self) -> AccountId {
533        unsafe {
534            let bits = sys::CSteamID_SteamID_t {
535                m_unAll64Bits: self.0,
536            };
537            AccountId(bits.m_comp.m_unAccountID())
538        }
539    }
540
541    /// Returns the formatted SteamID32 string for this steam id.
542    pub fn steamid32(&self) -> String {
543        let account_id = self.account_id().raw();
544        let last_bit = account_id & 1;
545        format!("STEAM_0:{}:{}", last_bit, (account_id >> 1))
546    }
547}
548
549/// A user's account id
550#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
551#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
552pub struct AccountId(pub(crate) u32);
553
554impl AccountId {
555    /// Creates an `AccountId` from a raw 32 bit value.
556    ///
557    /// May be useful for deserializing account ids from
558    /// a network or save format.
559    pub fn from_raw(id: u32) -> AccountId {
560        AccountId(id)
561    }
562
563    /// Returns the raw 32 bit value of the steam id
564    ///
565    /// May be useful for serializing steam ids over a
566    /// network or to a save format.
567    pub fn raw(&self) -> u32 {
568        self.0
569    }
570}
571
572/// A game id
573///
574/// Combines `AppId` and other information
575#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
576#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
577pub struct GameId(pub(crate) u64);
578
579impl GameId {
580    /// Creates a `GameId` from a raw 64 bit value.
581    ///
582    /// May be useful for deserializing game ids from
583    /// a network or save format.
584    pub fn from_raw(id: u64) -> GameId {
585        GameId(id)
586    }
587
588    /// Returns the raw 64 bit value of the game id
589    ///
590    /// May be useful for serializing game ids over a
591    /// network or to a save format.
592    pub fn raw(&self) -> u64 {
593        self.0
594    }
595
596    /// Returns the app id of this game
597    pub fn app_id(&self) -> AppId {
598        // TODO: Relies on internal details
599        AppId((self.0 & 0xFF_FF_FF) as u32)
600    }
601}
602
603#[cfg(test)]
604mod tests {
605    use serial_test::serial;
606
607    use super::*;
608
609    #[test]
610    #[serial]
611    fn basic_test() {
612        let (client, single) = Client::init().unwrap();
613
614        let _cb = client.register_callback(|p: PersonaStateChange| {
615            println!("Got callback: {:?}", p);
616        });
617
618        let utils = client.utils();
619        println!("Utils:");
620        println!("AppId: {:?}", utils.app_id());
621        println!("UI Language: {}", utils.ui_language());
622
623        let apps = client.apps();
624        println!("Apps");
625        println!("IsInstalled(480): {}", apps.is_app_installed(AppId(480)));
626        println!("InstallDir(480): {}", apps.app_install_dir(AppId(480)));
627        println!("BuildId: {}", apps.app_build_id());
628        println!("AppOwner: {:?}", apps.app_owner());
629        println!("Langs: {:?}", apps.available_game_languages());
630        println!("Lang: {}", apps.current_game_language());
631        println!("Beta: {:?}", apps.current_beta_name());
632
633        let friends = client.friends();
634        println!("Friends");
635        let list = friends.get_friends(FriendFlags::IMMEDIATE);
636        println!("{:?}", list);
637        for f in &list {
638            println!("Friend: {:?} - {}({:?})", f.id(), f.name(), f.state());
639            friends.request_user_information(f.id(), true);
640        }
641        friends.request_user_information(SteamId(76561198174976054), true);
642
643        for _ in 0..50 {
644            single.run_callbacks();
645            ::std::thread::sleep(::std::time::Duration::from_millis(100));
646        }
647    }
648
649    #[test]
650    fn steamid_test() {
651        let steamid = SteamId(76561198040894045);
652        assert_eq!("STEAM_0:1:40314158", steamid.steamid32());
653
654        let steamid = SteamId(76561198174976054);
655        assert_eq!("STEAM_0:0:107355163", steamid.steamid32());
656    }
657}