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
78pub 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 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 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 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 independent_connections:
202 HashMap<sys::HSteamNetConnection, Sender<networking_types::NetConnectionEvent>>,
203 connection_callback: Weak<CallbackHandle>,
204}
205
206pub 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 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 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 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 pub fn run_callbacks(&self) {
307 self.inner.run_callbacks()
308 }
309
310 pub fn process_callbacks(&self, mut callback_handler: impl FnMut(CallbackResult)) {
321 self.inner.process_callbacks(&mut callback_handler)
322 }
323
324 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
550enum Manager {
552 Client,
553 Server,
554}
555
556impl Manager {
557 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 match self {
571 Manager::Client => unsafe { sys::SteamAPI_Shutdown() },
572 Manager::Server => unsafe { sys::SteamGameServer_Shutdown() },
573 }
574 }
575}
576
577#[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 pub fn from_raw(id: u64) -> SteamId {
588 SteamId(id)
589 }
590
591 pub fn raw(&self) -> u64 {
596 self.0
597 }
598
599 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 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 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 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#[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#[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 = 0,
691 Individual = 1,
693 Multiseat = 2,
695 GameServer = 3,
697 AnonGameServer = 4,
699 Pending = 5,
701 ContentServer = 6,
703 Clan = 7,
705 Chat = 8,
707 ConsoleUser = 9,
709 AnonUser = 10,
711}
712
713#[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 pub fn from_raw(id: u32) -> AccountId {
724 AccountId(id)
725 }
726
727 pub fn raw(&self) -> u32 {
732 self.0
733 }
734}
735
736#[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 pub fn from_raw(id: u64) -> GameId {
749 GameId(id)
750 }
751
752 pub fn raw(&self) -> u64 {
757 self.0
758 }
759
760 pub fn app_id(&self) -> AppId {
762 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}