Skip to content

Commit 39ab702

Browse files
Enable user_stats module for achievements and leaderboards
- Uncomment user_stats module declaration and exports in lib.rs - Add user_stats() method to Client implementation - Update user_stats.rs to use dynamic library pattern (self.inner.lib.*) - Update stats.rs achievement helper to use dynamic library pattern - Add missing callback struct definitions to steamworks-sys: - UserStatsReceived_t, UserStatsStored_t, UserAchievementStored_t - UserAchievementIconFetched_t, LeaderboardFindResult_t - LeaderboardScoreUploaded_t, LeaderboardScoresDownloaded_t - GlobalAchievementPercentagesReady_t Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a95e3ea commit 39ab702

File tree

4 files changed

+123
-31
lines changed

4 files changed

+123
-31
lines changed

src/lib.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use sys::{ESteamAPIInitResult, SteamErrMsg};
1212

1313
use core::ffi::c_void;
1414
use std::collections::HashMap;
15-
use std::ffi::CStr;
15+
use std::ffi::{CStr, CString};
1616
use std::fmt::Debug;
1717
use std::sync::{Arc, Mutex};
1818

@@ -33,7 +33,7 @@ pub use crate::error::*;
3333
// pub use crate::timeline::*;
3434
// pub use crate::ugc::*;
3535
// pub use crate::user::*;
36-
// pub use crate::user_stats::*;
36+
pub use crate::user_stats::*;
3737
// pub use crate::utils::*;
3838

3939
mod app;
@@ -56,7 +56,7 @@ mod error;
5656
// pub mod timeline;
5757
// mod ugc;
5858
// mod user;
59-
// mod user_stats;
59+
mod user_stats;
6060
// mod utils;
6161

6262
pub type SResult<T> = Result<T, SteamError>;
@@ -349,6 +349,18 @@ where
349349
}
350350
}
351351

352+
/// Returns an accessor to the steam user stats interface
353+
pub fn user_stats(&self) -> UserStats<Manager> {
354+
unsafe {
355+
let us = self.inner.lib.SteamAPI_SteamUserStats_v013();
356+
debug_assert!(!us.is_null());
357+
UserStats {
358+
user_stats: us,
359+
inner: self.inner.clone(),
360+
}
361+
}
362+
}
363+
352364
// /// Returns an accessor to the steam friends interface
353365
// pub fn friends(&self) -> Friends<Manager> {
354366
// unsafe {

src/user_stats.rs

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl<Manager> UserStats<Manager> {
2121
{
2222
unsafe {
2323
let name = CString::new(name).unwrap();
24-
let api_call = sys::SteamAPI_ISteamUserStats_FindLeaderboard(
24+
let api_call = self.inner.lib.SteamAPI_ISteamUserStats_FindLeaderboard(
2525
self.user_stats,
2626
name.as_ptr() as *const _,
2727
);
@@ -78,7 +78,7 @@ impl<Manager> UserStats<Manager> {
7878
}
7979
};
8080

81-
let api_call = sys::SteamAPI_ISteamUserStats_FindOrCreateLeaderboard(
81+
let api_call = self.inner.lib.SteamAPI_ISteamUserStats_FindOrCreateLeaderboard(
8282
self.user_stats,
8383
name.as_ptr() as *const _,
8484
sort_method,
@@ -122,7 +122,7 @@ impl<Manager> UserStats<Manager> {
122122
sys::ELeaderboardUploadScoreMethod::k_ELeaderboardUploadScoreMethodForceUpdate
123123
}
124124
};
125-
let api_call = sys::SteamAPI_ISteamUserStats_UploadLeaderboardScore(
125+
let api_call = self.inner.lib.SteamAPI_ISteamUserStats_UploadLeaderboardScore(
126126
self.user_stats,
127127
leaderboard.0,
128128
method,
@@ -177,14 +177,15 @@ impl<Manager> UserStats<Manager> {
177177
sys::ELeaderboardDataRequest::k_ELeaderboardDataRequestFriends
178178
}
179179
};
180-
let api_call = sys::SteamAPI_ISteamUserStats_DownloadLeaderboardEntries(
180+
let api_call = self.inner.lib.SteamAPI_ISteamUserStats_DownloadLeaderboardEntries(
181181
self.user_stats,
182182
leaderboard.0,
183183
request,
184184
start as _,
185185
end as _,
186186
);
187187
let user_stats = self.user_stats as isize;
188+
let lib = self.inner.lib.clone();
188189
register_call_result::<sys::LeaderboardScoresDownloaded_t, _, _>(
189190
&self.inner,
190191
api_call,
@@ -199,7 +200,7 @@ impl<Manager> UserStats<Manager> {
199200
let mut entry: sys::LeaderboardEntry_t = std::mem::zeroed();
200201
let mut details = Vec::with_capacity(max_details_len);
201202

202-
sys::SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry(
203+
lib.SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry(
203204
user_stats as *mut _,
204205
v.m_hSteamLeaderboardEntries,
205206
idx,
@@ -229,7 +230,7 @@ impl<Manager> UserStats<Manager> {
229230
leaderboard: &Leaderboard,
230231
) -> Option<LeaderboardDisplayType> {
231232
unsafe {
232-
match sys::SteamAPI_ISteamUserStats_GetLeaderboardDisplayType(
233+
match self.inner.lib.SteamAPI_ISteamUserStats_GetLeaderboardDisplayType(
233234
self.user_stats,
234235
leaderboard.0,
235236
) {
@@ -253,7 +254,7 @@ impl<Manager> UserStats<Manager> {
253254
leaderboard: &Leaderboard,
254255
) -> Option<LeaderboardSortMethod> {
255256
unsafe {
256-
match sys::SteamAPI_ISteamUserStats_GetLeaderboardSortMethod(
257+
match self.inner.lib.SteamAPI_ISteamUserStats_GetLeaderboardSortMethod(
257258
self.user_stats,
258259
leaderboard.0,
259260
) {
@@ -271,7 +272,7 @@ impl<Manager> UserStats<Manager> {
271272
/// Returns the name of a leaderboard handle. Returns an empty string if the leaderboard handle is invalid.
272273
pub fn get_leaderboard_name(&self, leaderboard: &Leaderboard) -> String {
273274
unsafe {
274-
let name = CStr::from_ptr(sys::SteamAPI_ISteamUserStats_GetLeaderboardName(
275+
let name = CStr::from_ptr(self.inner.lib.SteamAPI_ISteamUserStats_GetLeaderboardName(
275276
self.user_stats,
276277
leaderboard.0,
277278
));
@@ -282,14 +283,14 @@ impl<Manager> UserStats<Manager> {
282283
/// Returns the total number of entries in a leaderboard. Returns 0 if the leaderboard handle is invalid.
283284
pub fn get_leaderboard_entry_count(&self, leaderboard: &Leaderboard) -> i32 {
284285
unsafe {
285-
sys::SteamAPI_ISteamUserStats_GetLeaderboardEntryCount(self.user_stats, leaderboard.0)
286+
self.inner.lib.SteamAPI_ISteamUserStats_GetLeaderboardEntryCount(self.user_stats, leaderboard.0)
286287
}
287288
}
288289

289290
/// Triggers a [`UserStatsReceived`](./struct.UserStatsReceived.html) callback.
290291
pub fn request_user_stats(&self, steam_user_id: u64) {
291292
unsafe {
292-
sys::SteamAPI_ISteamUserStats_RequestUserStats(self.user_stats, steam_user_id);
293+
self.inner.lib.SteamAPI_ISteamUserStats_RequestUserStats(self.user_stats, steam_user_id);
293294
}
294295
}
295296

@@ -308,7 +309,7 @@ impl<Manager> UserStats<Manager> {
308309
{
309310
unsafe {
310311
let api_call =
311-
sys::SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages(self.user_stats);
312+
self.inner.lib.SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages(self.user_stats);
312313
register_call_result::<sys::GlobalAchievementPercentagesReady_t, _, _>(
313314
&self.inner,
314315
api_call,
@@ -335,7 +336,7 @@ impl<Manager> UserStats<Manager> {
335336
/// Requires [`request_current_stats()`](#method.request_current_stats) to have been called
336337
/// and a successful [`UserStatsReceived`](./struct.UserStatsReceived.html) callback processed.
337338
pub fn store_stats(&self) -> Result<(), ()> {
338-
let success = unsafe { sys::SteamAPI_ISteamUserStats_StoreStats(self.user_stats) };
339+
let success = unsafe { self.inner.lib.SteamAPI_ISteamUserStats_StoreStats(self.user_stats) };
339340
if success {
340341
Ok(())
341342
} else {
@@ -346,7 +347,7 @@ impl<Manager> UserStats<Manager> {
346347
/// Resets the current users stats and, optionally achievements.
347348
pub fn reset_all_stats(&self, achievements_too: bool) -> Result<(), ()> {
348349
let success = unsafe {
349-
sys::SteamAPI_ISteamUserStats_ResetAllStats(self.user_stats, achievements_too)
350+
self.inner.lib.SteamAPI_ISteamUserStats_ResetAllStats(self.user_stats, achievements_too)
350351
};
351352
if success {
352353
Ok(())
@@ -366,7 +367,7 @@ impl<Manager> UserStats<Manager> {
366367

367368
let mut value: i32 = 0;
368369
let success = unsafe {
369-
sys::SteamAPI_ISteamUserStats_GetStatInt32(
370+
self.inner.lib.SteamAPI_ISteamUserStats_GetStatInt32(
370371
self.user_stats,
371372
name.as_ptr() as *const _,
372373
&mut value,
@@ -392,7 +393,7 @@ impl<Manager> UserStats<Manager> {
392393
let name = CString::new(name).unwrap();
393394

394395
let success = unsafe {
395-
sys::SteamAPI_ISteamUserStats_SetStatInt32(
396+
self.inner.lib.SteamAPI_ISteamUserStats_SetStatInt32(
396397
self.user_stats,
397398
name.as_ptr() as *const _,
398399
stat,
@@ -416,7 +417,7 @@ impl<Manager> UserStats<Manager> {
416417

417418
let mut value: f32 = 0.0;
418419
let success = unsafe {
419-
sys::SteamAPI_ISteamUserStats_GetStatFloat(
420+
self.inner.lib.SteamAPI_ISteamUserStats_GetStatFloat(
420421
self.user_stats,
421422
name.as_ptr() as *const _,
422423
&mut value,
@@ -442,7 +443,7 @@ impl<Manager> UserStats<Manager> {
442443
let name = CString::new(name).unwrap();
443444

444445
let success = unsafe {
445-
sys::SteamAPI_ISteamUserStats_SetStatFloat(
446+
self.inner.lib.SteamAPI_ISteamUserStats_SetStatFloat(
446447
self.user_stats,
447448
name.as_ptr() as *const _,
448449
stat,
@@ -477,7 +478,7 @@ impl<Manager> UserStats<Manager> {
477478
/// *Note: Returns an error for AppId `480` (Spacewar)!*
478479
pub fn get_num_achievements(&self) -> Result<u32, ()> {
479480
unsafe {
480-
let num = sys::SteamAPI_ISteamUserStats_GetNumAchievements(self.user_stats);
481+
let num = self.inner.lib.SteamAPI_ISteamUserStats_GetNumAchievements(self.user_stats);
481482
if num != 0 {
482483
Ok(num)
483484
} else {
@@ -498,7 +499,7 @@ impl<Manager> UserStats<Manager> {
498499

499500
for i in 0..num {
500501
unsafe {
501-
let name = sys::SteamAPI_ISteamUserStats_GetAchievementName(self.user_stats, i);
502+
let name = self.inner.lib.SteamAPI_ISteamUserStats_GetAchievementName(self.user_stats, i);
502503

503504
let c_str = CStr::from_ptr(name).to_string_lossy().into_owned();
504505

src/user_stats/stats.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl<M> AchievementHelper<'_, M> {
3333
pub fn get(&self) -> Result<bool, ()> {
3434
unsafe {
3535
let mut achieved = false;
36-
let success = sys::SteamAPI_ISteamUserStats_GetAchievement(
36+
let success = self.parent.inner.lib.SteamAPI_ISteamUserStats_GetAchievement(
3737
self.parent.user_stats,
3838
self.name.as_ptr() as *const _,
3939
&mut achieved as *mut _,
@@ -56,7 +56,7 @@ impl<M> AchievementHelper<'_, M> {
5656
/// [`UserStatsReceived`](../struct.UserStatsReceived.html).
5757
pub fn set(&self) -> Result<(), ()> {
5858
let success = unsafe {
59-
sys::SteamAPI_ISteamUserStats_SetAchievement(
59+
self.parent.inner.lib.SteamAPI_ISteamUserStats_SetAchievement(
6060
self.parent.user_stats,
6161
self.name.as_ptr() as *const _,
6262
)
@@ -78,7 +78,7 @@ impl<M> AchievementHelper<'_, M> {
7878
/// [`UserStatsReceived`](../struct.UserStatsReceived.html).
7979
pub fn clear(&self) -> Result<(), ()> {
8080
let success = unsafe {
81-
sys::SteamAPI_ISteamUserStats_ClearAchievement(
81+
self.parent.inner.lib.SteamAPI_ISteamUserStats_ClearAchievement(
8282
self.parent.user_stats,
8383
self.name.as_ptr() as *const _,
8484
)
@@ -120,7 +120,7 @@ impl<M> AchievementHelper<'_, M> {
120120
pub fn get_achievement_achieved_percent(&self) -> Result<f32, ()> {
121121
unsafe {
122122
let mut percent = 0.0;
123-
let success = sys::SteamAPI_ISteamUserStats_GetAchievementAchievedPercent(
123+
let success = self.parent.inner.lib.SteamAPI_ISteamUserStats_GetAchievementAchievedPercent(
124124
self.parent.user_stats,
125125
self.name.as_ptr() as *const _,
126126
&mut percent as *mut _,
@@ -170,7 +170,7 @@ impl<M> AchievementHelper<'_, M> {
170170
let key_c_str = CString::new(key).expect("Failed to create c_str from key parameter");
171171
let ptr = key_c_str.as_ptr() as *const i8;
172172

173-
let str = sys::SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(
173+
let str = self.parent.inner.lib.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(
174174
self.parent.user_stats,
175175
self.name.as_ptr() as *const _,
176176
ptr,
@@ -200,8 +200,9 @@ impl<M> AchievementHelper<'_, M> {
200200

201201
fn internal_get_achievement_icon(&self, avoid_big_icons: bool) -> Option<(Vec<u8>, u32, u32)> {
202202
unsafe {
203-
let utils: *mut sys::ISteamUtils = sys::SteamAPI_SteamUtils_v010();
204-
let img = sys::SteamAPI_ISteamUserStats_GetAchievementIcon(
203+
let lib = &self.parent.inner.lib;
204+
let utils: *mut sys::ISteamUtils = lib.SteamAPI_SteamUtils_v010();
205+
let img = lib.SteamAPI_ISteamUserStats_GetAchievementIcon(
205206
self.parent.user_stats,
206207
self.name.as_ptr() as *const _,
207208
);
@@ -210,14 +211,14 @@ impl<M> AchievementHelper<'_, M> {
210211
}
211212
let mut width = 0;
212213
let mut height = 0;
213-
if !sys::SteamAPI_ISteamUtils_GetImageSize(utils, img, &mut width, &mut height) {
214+
if !lib.SteamAPI_ISteamUtils_GetImageSize(utils, img, &mut width, &mut height) {
214215
return None;
215216
}
216217
if avoid_big_icons && (width != 64 || height != 64) {
217218
return None;
218219
}
219220
let mut dest = vec![0; (width * height * 4).try_into().unwrap()];
220-
if !sys::SteamAPI_ISteamUtils_GetImageRGBA(
221+
if !lib.SteamAPI_ISteamUtils_GetImageRGBA(
221222
utils,
222223
img,
223224
dest.as_mut_ptr(),

steamworks-sys/src/lib.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,81 @@ include!("macos_bindings.rs");
1010

1111
#[cfg(target_os = "linux")]
1212
include!("linux_bindings.rs");
13+
14+
// User stats callback structures
15+
// These are used for receiving callback data from Steam
16+
17+
/// Callback for RequestCurrentStats
18+
#[repr(C, packed(4))]
19+
#[derive(Copy, Clone)]
20+
pub struct UserStatsReceived_t {
21+
pub m_nGameID: u64,
22+
pub m_eResult: EResult,
23+
pub m_steamIDUser: CSteamID,
24+
}
25+
26+
/// Callback for StoreStats
27+
#[repr(C, packed(4))]
28+
#[derive(Debug, Copy, Clone)]
29+
pub struct UserStatsStored_t {
30+
pub m_nGameID: u64,
31+
pub m_eResult: EResult,
32+
}
33+
34+
/// Callback for achievement storage
35+
#[repr(C, packed(4))]
36+
#[derive(Debug, Copy, Clone)]
37+
pub struct UserAchievementStored_t {
38+
pub m_nGameID: u64,
39+
pub m_bGroupAchievement: bool,
40+
pub m_rgchAchievementName: [::std::os::raw::c_char; 128],
41+
pub m_nCurProgress: u32,
42+
pub m_nMaxProgress: u32,
43+
}
44+
45+
/// Callback for achievement icon fetch
46+
#[repr(C, packed(4))]
47+
#[derive(Copy, Clone)]
48+
pub struct UserAchievementIconFetched_t {
49+
pub m_nGameID: CGameID,
50+
pub m_rgchAchievementName: [::std::os::raw::c_char; 128],
51+
pub m_bAchieved: bool,
52+
pub m_nIconHandle: ::std::os::raw::c_int,
53+
}
54+
55+
/// Callback for FindLeaderboard
56+
#[repr(C, packed(4))]
57+
#[derive(Debug, Copy, Clone)]
58+
pub struct LeaderboardFindResult_t {
59+
pub m_hSteamLeaderboard: u64,
60+
pub m_bLeaderboardFound: u8,
61+
}
62+
63+
/// Callback for UploadLeaderboardScore
64+
#[repr(C, packed(4))]
65+
#[derive(Debug, Copy, Clone)]
66+
pub struct LeaderboardScoreUploaded_t {
67+
pub m_bSuccess: u8,
68+
pub m_hSteamLeaderboard: u64,
69+
pub m_nScore: i32,
70+
pub m_bScoreChanged: u8,
71+
pub m_nGlobalRankNew: ::std::os::raw::c_int,
72+
pub m_nGlobalRankPrevious: ::std::os::raw::c_int,
73+
}
74+
75+
/// Callback for DownloadLeaderboardEntries
76+
#[repr(C, packed(4))]
77+
#[derive(Debug, Copy, Clone)]
78+
pub struct LeaderboardScoresDownloaded_t {
79+
pub m_hSteamLeaderboard: u64,
80+
pub m_hSteamLeaderboardEntries: u64,
81+
pub m_cEntryCount: ::std::os::raw::c_int,
82+
}
83+
84+
/// Callback for RequestGlobalAchievementPercentages
85+
#[repr(C, packed(4))]
86+
#[derive(Debug, Copy, Clone)]
87+
pub struct GlobalAchievementPercentagesReady_t {
88+
pub m_nGameID: u64,
89+
pub m_eResult: EResult,
90+
}

0 commit comments

Comments
 (0)