steamworks/steam/
common.rs

1use crate::Client;
2use crate::callbacks::PersonaStateChangeFlags;
3use enum_primitive_derive::Primitive;
4use futures::{Future, StreamExt};
5use num_traits::FromPrimitive;
6use std::cmp::Ordering;
7use std::ffi::CStr;
8use std::fmt::{self, Debug, Display, Formatter};
9use std::hash::{Hash, Hasher};
10use steamworks_sys as sys;
11use steamworks_sys::CSteamID;
12
13#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
14pub struct AppId(pub u32);
15
16impl AppId {
17    pub fn new(id: u32) -> Self {
18        AppId(id)
19    }
20}
21
22impl From<u32> for AppId {
23    fn from(x: u32) -> AppId {
24        AppId(x)
25    }
26}
27
28impl From<AppId> for u32 {
29    fn from(x: AppId) -> u32 {
30        x.0
31    }
32}
33
34#[derive(Copy, Clone)]
35pub struct SteamId(pub(crate) u64);
36
37impl SteamId {
38    pub fn new(id: u64) -> Self {
39        id.into()
40    }
41
42    pub fn persona_name(self, client: &Client) -> impl Future<Output = String> + Send + '_ {
43        let mut persona_state_changes = client.on_persona_state_changed();
44        let request_in_progress = unsafe {
45            sys::SteamAPI_ISteamFriends_RequestUserInformation(*client.0.friends, self.0, true)
46        };
47        async move {
48            if request_in_progress {
49                loop {
50                    let change = persona_state_changes.next().await.unwrap();
51                    if change.steam_id == self
52                        && change.change_flags.contains(PersonaStateChangeFlags::NAME)
53                    {
54                        break;
55                    }
56                }
57            }
58
59            unsafe {
60                let name =
61                    sys::SteamAPI_ISteamFriends_GetFriendPersonaName(*client.0.friends, self.0);
62
63                CStr::from_ptr(name)
64                    .to_owned()
65                    .into_string()
66                    .expect("persona name contained invalid UTF-8")
67            }
68        }
69    }
70
71    pub fn as_u64(self) -> u64 {
72        self.0
73    }
74}
75
76impl From<u64> for SteamId {
77    fn from(inner: u64) -> Self {
78        SteamId(inner)
79    }
80}
81
82impl From<SteamId> for u64 {
83    fn from(steam_id: SteamId) -> Self {
84        steam_id.0
85    }
86}
87
88impl From<CSteamID> for SteamId {
89    fn from(id: CSteamID) -> Self {
90        unsafe { SteamId(id.m_steamid.m_unAll64Bits) }
91    }
92}
93
94impl Debug for SteamId {
95    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
96        f.debug_tuple("SteamId").field(&self.as_u64()).finish()
97    }
98}
99
100impl PartialEq for SteamId {
101    fn eq(&self, other: &SteamId) -> bool {
102        self.as_u64() == other.as_u64()
103    }
104}
105
106impl Eq for SteamId {}
107
108impl Hash for SteamId {
109    fn hash<H: Hasher>(&self, state: &mut H) {
110        self.as_u64().hash(state);
111    }
112}
113
114impl PartialOrd for SteamId {
115    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
116        Some(self.cmp(other))
117    }
118}
119
120impl Ord for SteamId {
121    fn cmp(&self, other: &Self) -> Ordering {
122        self.as_u64().cmp(&other.as_u64())
123    }
124}
125
126#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Primitive)]
127#[repr(i32)]
128pub enum SteamResult {
129    OK = sys::EResult_k_EResultOK as i32,
130    Fail = sys::EResult_k_EResultFail as i32,
131    NoConnection = sys::EResult_k_EResultNoConnection as i32,
132    InvalidPassword = sys::EResult_k_EResultInvalidPassword as i32,
133    LoggedInElsewhere = sys::EResult_k_EResultLoggedInElsewhere as i32,
134    InvalidProtocolVer = sys::EResult_k_EResultInvalidProtocolVer as i32,
135    InvalidParam = sys::EResult_k_EResultInvalidParam as i32,
136    FileNotFound = sys::EResult_k_EResultFileNotFound as i32,
137    Busy = sys::EResult_k_EResultBusy as i32,
138    InvalidState = sys::EResult_k_EResultInvalidState as i32,
139    InvalidName = sys::EResult_k_EResultInvalidName as i32,
140    InvalidEmail = sys::EResult_k_EResultInvalidEmail as i32,
141    DuplicateName = sys::EResult_k_EResultDuplicateName as i32,
142    AccessDenied = sys::EResult_k_EResultAccessDenied as i32,
143    Timeout = sys::EResult_k_EResultTimeout as i32,
144    Banned = sys::EResult_k_EResultBanned as i32,
145    AccountNotFound = sys::EResult_k_EResultAccountNotFound as i32,
146    InvalidSteamID = sys::EResult_k_EResultInvalidSteamID as i32,
147    ServiceUnavailable = sys::EResult_k_EResultServiceUnavailable as i32,
148    NotLoggedOn = sys::EResult_k_EResultNotLoggedOn as i32,
149    Pending = sys::EResult_k_EResultPending as i32,
150    EncryptionFailure = sys::EResult_k_EResultEncryptionFailure as i32,
151    InsufficientPrivilege = sys::EResult_k_EResultInsufficientPrivilege as i32,
152    LimitExceeded = sys::EResult_k_EResultLimitExceeded as i32,
153    Revoked = sys::EResult_k_EResultRevoked as i32,
154    Expired = sys::EResult_k_EResultExpired as i32,
155    AlreadyRedeemed = sys::EResult_k_EResultAlreadyRedeemed as i32,
156    DuplicateRequest = sys::EResult_k_EResultDuplicateRequest as i32,
157    AlreadyOwned = sys::EResult_k_EResultAlreadyOwned as i32,
158    IPNotFound = sys::EResult_k_EResultIPNotFound as i32,
159    PersistFailed = sys::EResult_k_EResultPersistFailed as i32,
160    LockingFailed = sys::EResult_k_EResultLockingFailed as i32,
161    LogonSessionReplaced = sys::EResult_k_EResultLogonSessionReplaced as i32,
162    ConnectFailed = sys::EResult_k_EResultConnectFailed as i32,
163    HandshakeFailed = sys::EResult_k_EResultHandshakeFailed as i32,
164    IOFailure = sys::EResult_k_EResultIOFailure as i32,
165    RemoteDisconnect = sys::EResult_k_EResultRemoteDisconnect as i32,
166    ShoppingCartNotFound = sys::EResult_k_EResultShoppingCartNotFound as i32,
167    Blocked = sys::EResult_k_EResultBlocked as i32,
168    Ignored = sys::EResult_k_EResultIgnored as i32,
169    NoMatch = sys::EResult_k_EResultNoMatch as i32,
170    AccountDisabled = sys::EResult_k_EResultAccountDisabled as i32,
171    ServiceReadOnly = sys::EResult_k_EResultServiceReadOnly as i32,
172    AccountNotFeatured = sys::EResult_k_EResultAccountNotFeatured as i32,
173    AdministratorOK = sys::EResult_k_EResultAdministratorOK as i32,
174    ContentVersion = sys::EResult_k_EResultContentVersion as i32,
175    TryAnotherCM = sys::EResult_k_EResultTryAnotherCM as i32,
176    PasswordRequiredToKickSession = sys::EResult_k_EResultPasswordRequiredToKickSession as i32,
177    AlreadyLoggedInElsewhere = sys::EResult_k_EResultAlreadyLoggedInElsewhere as i32,
178    Suspended = sys::EResult_k_EResultSuspended as i32,
179    Cancelled = sys::EResult_k_EResultCancelled as i32,
180    DataCorruption = sys::EResult_k_EResultDataCorruption as i32,
181    DiskFull = sys::EResult_k_EResultDiskFull as i32,
182    RemoteCallFailed = sys::EResult_k_EResultRemoteCallFailed as i32,
183    PasswordUnset = sys::EResult_k_EResultPasswordUnset as i32,
184    ExternalAccountUnlinked = sys::EResult_k_EResultExternalAccountUnlinked as i32,
185    PSNTicketInvalid = sys::EResult_k_EResultPSNTicketInvalid as i32,
186    ExternalAccountAlreadyLinked = sys::EResult_k_EResultExternalAccountAlreadyLinked as i32,
187    RemoteFileConflict = sys::EResult_k_EResultRemoteFileConflict as i32,
188    IllegalPassword = sys::EResult_k_EResultIllegalPassword as i32,
189    SameAsPreviousValue = sys::EResult_k_EResultSameAsPreviousValue as i32,
190    AccountLogonDenied = sys::EResult_k_EResultAccountLogonDenied as i32,
191    CannotUseOldPassword = sys::EResult_k_EResultCannotUseOldPassword as i32,
192    InvalidLoginAuthCode = sys::EResult_k_EResultInvalidLoginAuthCode as i32,
193    AccountLogonDeniedNoMail = sys::EResult_k_EResultAccountLogonDeniedNoMail as i32,
194    HardwareNotCapableOfIPT = sys::EResult_k_EResultHardwareNotCapableOfIPT as i32,
195    IPTInitError = sys::EResult_k_EResultIPTInitError as i32,
196    ParentalControlRestricted = sys::EResult_k_EResultParentalControlRestricted as i32,
197    FacebookQueryError = sys::EResult_k_EResultFacebookQueryError as i32,
198    ExpiredLoginAuthCode = sys::EResult_k_EResultExpiredLoginAuthCode as i32,
199    IPLoginRestrictionFailed = sys::EResult_k_EResultIPLoginRestrictionFailed as i32,
200    AccountLockedDown = sys::EResult_k_EResultAccountLockedDown as i32,
201    AccountLogonDeniedVerifiedEmailRequired =
202        sys::EResult_k_EResultAccountLogonDeniedVerifiedEmailRequired as i32,
203    NoMatchingURL = sys::EResult_k_EResultNoMatchingURL as i32,
204    BadResponse = sys::EResult_k_EResultBadResponse as i32,
205    RequirePasswordReEntry = sys::EResult_k_EResultRequirePasswordReEntry as i32,
206    ValueOutOfRange = sys::EResult_k_EResultValueOutOfRange as i32,
207    UnexpectedError = sys::EResult_k_EResultUnexpectedError as i32,
208    Disabled = sys::EResult_k_EResultDisabled as i32,
209    InvalidCEGSubmission = sys::EResult_k_EResultInvalidCEGSubmission as i32,
210    RestrictedDevice = sys::EResult_k_EResultRestrictedDevice as i32,
211    RegionLocked = sys::EResult_k_EResultRegionLocked as i32,
212    RateLimitExceeded = sys::EResult_k_EResultRateLimitExceeded as i32,
213    AccountLoginDeniedNeedTwoFactor = sys::EResult_k_EResultAccountLoginDeniedNeedTwoFactor as i32,
214    ItemDeleted = sys::EResult_k_EResultItemDeleted as i32,
215    AccountLoginDeniedThrottle = sys::EResult_k_EResultAccountLoginDeniedThrottle as i32,
216    TwoFactorCodeMismatch = sys::EResult_k_EResultTwoFactorCodeMismatch as i32,
217    TwoFactorActivationCodeMismatch = sys::EResult_k_EResultTwoFactorActivationCodeMismatch as i32,
218    AccountAssociatedToMultiplePartners =
219        sys::EResult_k_EResultAccountAssociatedToMultiplePartners as i32,
220    NotModified = sys::EResult_k_EResultNotModified as i32,
221    NoMobileDevice = sys::EResult_k_EResultNoMobileDevice as i32,
222    TimeNotSynced = sys::EResult_k_EResultTimeNotSynced as i32,
223    SmsCodeFailed = sys::EResult_k_EResultSmsCodeFailed as i32,
224    AccountLimitExceeded = sys::EResult_k_EResultAccountLimitExceeded as i32,
225    AccountActivityLimitExceeded = sys::EResult_k_EResultAccountActivityLimitExceeded as i32,
226    PhoneActivityLimitExceeded = sys::EResult_k_EResultPhoneActivityLimitExceeded as i32,
227    RefundToWallet = sys::EResult_k_EResultRefundToWallet as i32,
228    EmailSendFailure = sys::EResult_k_EResultEmailSendFailure as i32,
229    NotSettled = sys::EResult_k_EResultNotSettled as i32,
230    NeedCaptcha = sys::EResult_k_EResultNeedCaptcha as i32,
231    GSLTDenied = sys::EResult_k_EResultGSLTDenied as i32,
232    GSOwnerDenied = sys::EResult_k_EResultGSOwnerDenied as i32,
233    InvalidItemType = sys::EResult_k_EResultInvalidItemType as i32,
234    IPBanned = sys::EResult_k_EResultIPBanned as i32,
235    GSLTExpired = sys::EResult_k_EResultGSLTExpired as i32,
236    InsufficientFunds = sys::EResult_k_EResultInsufficientFunds as i32,
237    TooManyPending = sys::EResult_k_EResultTooManyPending as i32,
238    NoSiteLicensesFound = sys::EResult_k_EResultNoSiteLicensesFound as i32,
239    WGNetworkSendExceeded = sys::EResult_k_EResultWGNetworkSendExceeded as i32,
240    AccountNotFriends = sys::EResult_k_EResultAccountNotFriends as i32,
241    LimitedUserAccount = sys::EResult_k_EResultLimitedUserAccount as i32,
242    CantRemoveItem = sys::EResult_k_EResultCantRemoveItem as i32,
243}
244
245impl SteamResult {
246    pub(crate) fn from_inner(inner: sys::EResult) -> Self {
247        SteamResult::from_i32(inner as i32)
248            .unwrap_or_else(|| panic!("Unknown EResult variant: {inner}"))
249    }
250}
251
252impl Display for SteamResult {
253    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
254        use SteamResult::*;
255
256        let error_string = match *self {
257            OK => "Success.",
258            Fail => "Generic failure.",
259            NoConnection => "Your Steam client doesn't have a connection to the back-end.",
260            InvalidPassword => "Password/ticket is invalid.",
261            LoggedInElsewhere => "The user is logged in elsewhere.",
262            InvalidProtocolVer => "Protocol version is incorrect.",
263            InvalidParam => "A parameter is incorrect.",
264            FileNotFound => "File was not found.",
265            Busy => "Called method is busy - action not taken.",
266            InvalidState => "Called object was in an invalid state.",
267            InvalidName => "The name was invalid.",
268            InvalidEmail => "The email was invalid.",
269            DuplicateName => "The name is not unique.",
270            AccessDenied => "Access is denied.",
271            Timeout => "Operation timed out.",
272            Banned => "The user is VAC2 banned.",
273            AccountNotFound => "Account not found.",
274            InvalidSteamID => "The Steam ID was invalid.",
275            ServiceUnavailable => "The requested service is currently unavailable.",
276            NotLoggedOn => "The user is not logged on.",
277            Pending => "Request is pending, it may be in process or waiting on third party.",
278            EncryptionFailure => "Encryption or Decryption failed.",
279            InsufficientPrivilege => "Insufficient privilege.",
280            LimitExceeded => "Too much of a good thing.",
281            Revoked => "Access has been revoked (used for revoked guest passes.)",
282            Expired => "License/Guest pass the user is trying to access is expired.",
283            AlreadyRedeemed => {
284                "Guest pass has already been redeemed by account, cannot be used again."
285            }
286            DuplicateRequest => {
287                "The request is a duplicate and the action has already occurred in the past, ignored this time."
288            }
289            AlreadyOwned => {
290                "All the games in this guest pass redemption request are already owned by the user."
291            }
292            IPNotFound => "IP address not found.",
293            PersistFailed => "Failed to write change to the data store.",
294            LockingFailed => "Failed to acquire access lock for this operation.",
295            LogonSessionReplaced => "The logon session has been replaced.",
296            ConnectFailed => "Failed to connect.",
297            HandshakeFailed => "The authentication handshake has failed.",
298            IOFailure => "There has been a generic IO failure.",
299            RemoteDisconnect => "The remote server has disconnected.",
300            ShoppingCartNotFound => "Failed to find the shopping cart requested.",
301            Blocked => "A user blocked the action.",
302            Ignored => "The target is ignoring sender.",
303            NoMatch => "Nothing matching the request found.",
304            AccountDisabled => "The account is disabled.",
305            ServiceReadOnly => "This service is not accepting content changes right now.",
306            AccountNotFeatured => "Account doesn't have value, so this feature isn't available.",
307            AdministratorOK => "Allowed to take this action, but only because requester is admin.",
308            ContentVersion => {
309                "A Version mismatch in content transmitted within the Steam protocol."
310            }
311            TryAnotherCM => {
312                "The current CM can't service the user making a request, user should try another."
313            }
314            PasswordRequiredToKickSession => {
315                "You are already logged in elsewhere, this cached credential login has failed."
316            }
317            AlreadyLoggedInElsewhere => {
318                "The user is logged in elsewhere. (Use k_EResultLoggedInElsewhere instead!)"
319            }
320            Suspended => "Long running operation has suspended/paused. (eg. content download.)",
321            Cancelled => {
322                "Operation has been canceled, typically by user. (eg. a content download.)"
323            }
324            DataCorruption => "Operation canceled because data is ill formed or unrecoverable.",
325            DiskFull => "Operation canceled - not enough disk space.",
326            RemoteCallFailed => "The remote or IPC call has failed.",
327            PasswordUnset => "Password could not be verified as it's unset server side.",
328            ExternalAccountUnlinked => {
329                "External account (PSN, Facebook...) is not linked to a Steam account."
330            }
331            PSNTicketInvalid => "PSN ticket was invalid.",
332            ExternalAccountAlreadyLinked => {
333                "External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first."
334            }
335            RemoteFileConflict => {
336                "The sync cannot resume due to a conflict between the local and remote files."
337            }
338            IllegalPassword => "The requested new password is not allowed.",
339            SameAsPreviousValue => {
340                "New value is the same as the old one. This is used for secret question and answer."
341            }
342            AccountLogonDenied => "Account login denied due to 2nd factor authentication failure.",
343            CannotUseOldPassword => "The requested new password is not legal.",
344            InvalidLoginAuthCode => "Account login denied due to auth code invalid.",
345            AccountLogonDeniedNoMail => {
346                "Account login denied due to 2nd factor auth failure - and no mail has been sent."
347            }
348            HardwareNotCapableOfIPT => {
349                "The users hardware does not support Intel's Identity Protection Technology (IPT)."
350            }
351            IPTInitError => {
352                "Intel's Identity Protection Technology (IPT) has failed to initialize."
353            }
354            ParentalControlRestricted => {
355                "Operation failed due to parental control restrictions for current user."
356            }
357            FacebookQueryError => "Facebook query returned an error.",
358            ExpiredLoginAuthCode => "Account login denied due to an expired auth code.",
359            IPLoginRestrictionFailed => "The login failed due to an IP restriction.",
360            AccountLockedDown => {
361                "The current users account is currently locked for use. This is likely due to a hijacking and pending ownership verification."
362            }
363            AccountLogonDeniedVerifiedEmailRequired => {
364                "The logon failed because the accounts email is not verified."
365            }
366            NoMatchingURL => "There is no URL matching the provided values.",
367            BadResponse => "Bad Response due to a Parse failure, missing field, etc.",
368            RequirePasswordReEntry => {
369                "The user cannot complete the action until they re-enter their password."
370            }
371            ValueOutOfRange => "The value entered is outside the acceptable range.",
372            UnexpectedError => "Something happened that we didn't expect to ever happen.",
373            Disabled => "The requested service has been configured to be unavailable.",
374            InvalidCEGSubmission => "The files submitted to the CEG server are not valid.",
375            RestrictedDevice => "The device being used is not allowed to perform this action.",
376            RegionLocked => "The action could not be complete because it is region restricted.",
377            RateLimitExceeded => {
378                "Temporary rate limit exceeded, try again later, different from k_EResultLimitExceeded which may be permanent."
379            }
380            AccountLoginDeniedNeedTwoFactor => "Need two-factor code to login.",
381            ItemDeleted => "The thing we're trying to access has been deleted.",
382            AccountLoginDeniedThrottle => {
383                "Login attempt failed, try to throttle response to possible attacker."
384            }
385            TwoFactorCodeMismatch => "Two factor authentication (Steam Guard) code is incorrect.",
386            TwoFactorActivationCodeMismatch => {
387                "The activation code for two-factor authentication (Steam Guard) didn't match."
388            }
389            AccountAssociatedToMultiplePartners => {
390                "The current account has been associated with multiple partners."
391            }
392            NotModified => "The data has not been modified.",
393            NoMobileDevice => "The account does not have a mobile device associated with it.",
394            TimeNotSynced => "The time presented is out of range or tolerance.",
395            SmsCodeFailed => "SMS code failure - no match, none pending, etc.",
396            AccountLimitExceeded => "Too many accounts access this resource.",
397            AccountActivityLimitExceeded => "Too many changes to this account.",
398            PhoneActivityLimitExceeded => "Too many changes to this phone.",
399            RefundToWallet => "Cannot refund to payment method, must use wallet.",
400            EmailSendFailure => "Cannot send an email.",
401            NotSettled => "Can't perform operation until payment has settled.",
402            NeedCaptcha => "The user needs to provide a valid captcha.",
403            GSLTDenied => "A game server login token owned by this token's owner has been banned.",
404            GSOwnerDenied => {
405                "Game server owner is denied for some other reason such as account locked, community ban, vac ban, missing phone, etc."
406            }
407            InvalidItemType => "The type of thing we were requested to act on is invalid.",
408            IPBanned => "The IP address has been banned from taking this action.",
409            GSLTExpired => {
410                "This Game Server Login Token (GSLT) has expired from disuse; it can be reset for use."
411            }
412            InsufficientFunds => "user doesn't have enough wallet funds to complete the action",
413            TooManyPending => "There are too many of this thing pending already",
414            NoSiteLicensesFound => "No site licenses found",
415            WGNetworkSendExceeded => {
416                "the WG couldn't send a response because we exceeded max network send size"
417            }
418            AccountNotFriends => "the user is not mutually friends",
419            LimitedUserAccount => "the user is limited",
420            CantRemoveItem => "item can't be removed",
421        };
422
423        write!(f, "{error_string}")
424    }
425}