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
64pub 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
86pub 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 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
123pub 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
485pub unsafe trait Manager {
487 unsafe fn get_pipe() -> sys::HSteamPipe;
488}
489
490pub 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#[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 pub fn from_raw(id: u64) -> SteamId {
520 SteamId(id)
521 }
522
523 pub fn raw(&self) -> u64 {
528 self.0
529 }
530
531 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 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#[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 pub fn from_raw(id: u32) -> AccountId {
560 AccountId(id)
561 }
562
563 pub fn raw(&self) -> u32 {
568 self.0
569 }
570}
571
572#[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 pub fn from_raw(id: u64) -> GameId {
585 GameId(id)
586 }
587
588 pub fn raw(&self) -> u64 {
593 self.0
594 }
595
596 pub fn app_id(&self) -> AppId {
598 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}