1use steam_enums::{EAppType, ELicenseFlags};
10
11use crate::{error::SteamError, SteamClient};
12
13const NON_STEAM_GAME_ID: u64 = 15190414816125648896;
17
18#[derive(Debug, Clone)]
20pub struct AppInfo {
21 pub app_id: u32,
23 pub name: String,
25 pub app_type: EAppType,
27 pub developer: Option<String>,
29 pub publisher: Option<String>,
31 pub icon_hash: Option<String>,
33 pub logo_hash: Option<String>,
35}
36
37#[derive(Debug, Clone)]
39pub struct PackageInfo {
40 pub package_id: u32,
42 pub name: Option<String>,
44 pub app_ids: Vec<u32>,
46}
47
48#[derive(Debug, Clone)]
50pub struct OwnedApp {
51 pub app_id: u32,
53 pub package_id: u32,
55 pub time_created: u32,
57 pub license_type: u32,
59 pub flags: u32,
61}
62
63#[derive(Debug, Clone, Default)]
65pub struct AppInfoRequest {
66 pub app_id: u32,
68 pub access_token: Option<u64>,
70}
71
72impl From<u32> for AppInfoRequest {
73 fn from(app_id: u32) -> Self {
74 Self { app_id, access_token: None }
75 }
76}
77
78#[derive(Debug, Clone, Default)]
80pub struct PackageInfoRequest {
81 pub package_id: u32,
83 pub access_token: Option<u64>,
85}
86
87impl From<u32> for PackageInfoRequest {
88 fn from(package_id: u32) -> Self {
89 Self { package_id, access_token: None }
90 }
91}
92
93impl SteamClient {
94 pub fn get_owned_apps(&self) -> Vec<OwnedApp> {
99 Vec::new()
102 }
103
104 pub async fn get_product_info(&mut self, app_ids: Vec<u32>) -> Result<(), SteamError> {
111 let requests: Vec<AppInfoRequest> = app_ids.into_iter().map(Into::into).collect();
112 self.get_product_info_with_tokens(requests, Vec::new()).await
113 }
114
115 pub async fn get_product_info_with_tokens(&mut self, apps: Vec<AppInfoRequest>, packages: Vec<PackageInfoRequest>) -> Result<(), SteamError> {
123 if !self.is_logged_in() {
124 return Err(SteamError::NotLoggedOn);
125 }
126
127 let msg = steam_protos::CMsgClientPICSProductInfoRequest {
128 apps: apps.iter().map(|req| steam_protos::cmsg_client_pics_product_info_request::AppInfo { appid: Some(req.app_id), access_token: req.access_token, only_public_obsolete: None }).collect(),
129 packages: packages.iter().map(|req| steam_protos::cmsg_client_pics_product_info_request::PackageInfo { packageid: Some(req.package_id), access_token: req.access_token }).collect(),
130 meta_data_only: Some(false),
131 ..Default::default()
132 };
133
134 self.send_message(steam_enums::EMsg::ClientPICSProductInfoRequest, &msg).await
135 }
136
137 pub async fn get_package_info(&mut self, package_ids: Vec<u32>) -> Result<(), SteamError> {
144 let packages: Vec<PackageInfoRequest> = package_ids.into_iter().map(Into::into).collect();
145 self.get_product_info_with_tokens(Vec::new(), packages).await
146 }
147
148 pub async fn get_access_tokens(&mut self, app_ids: Vec<u32>, package_ids: Vec<u32>) -> Result<(), SteamError> {
158 if !self.is_logged_in() {
159 return Err(SteamError::NotLoggedOn);
160 }
161
162 let msg = steam_protos::CMsgClientPICSAccessTokenRequest { appids: app_ids, packageids: package_ids };
163
164 self.send_message(steam_enums::EMsg::ClientPICSAccessTokenRequest, &msg).await
165 }
166
167 pub async fn get_product_access_tokens(&mut self, app_ids: Vec<u32>) -> Result<(), SteamError> {
172 self.get_access_tokens(app_ids, Vec::new()).await
173 }
174
175 pub async fn get_product_changes(&mut self, since_change_number: u32) -> Result<(), SteamError> {
185 if !self.is_logged_in() {
186 return Err(SteamError::NotLoggedOn);
187 }
188
189 let msg = steam_protos::CMsgClientPICSChangesSinceRequest {
190 since_change_number: Some(since_change_number),
191 send_app_info_changes: Some(true),
192 send_package_info_changes: Some(true),
193 ..Default::default()
194 };
195
196 self.send_message(steam_enums::EMsg::ClientPICSChangesSinceRequest, &msg).await
197 }
198
199 pub async fn get_player_count(&mut self, app_id: u32) -> Result<(), SteamError> {
206 if !self.is_logged_in() {
207 return Err(SteamError::NotLoggedOn);
208 }
209
210 let msg = steam_protos::CMsgDpGetNumberOfCurrentPlayers { appid: Some(app_id) };
211
212 self.send_message(steam_enums::EMsg::ClientGetNumberOfCurrentPlayersDP, &msg).await
213 }
214
215 pub async fn kick_playing_session(&mut self) -> Result<(), SteamError> {
220 if !self.is_logged_in() {
221 return Err(SteamError::NotLoggedOn);
222 }
223
224 let msg = steam_protos::CMsgClientKickPlayingSession::default();
225 self.send_message(steam_enums::EMsg::ClientKickPlayingSession, &msg).await
226 }
227
228 pub async fn games_played(&mut self, app_ids: Vec<u32>) -> Result<(), SteamError> {
233 self.games_played_with_extra(app_ids, None).await
234 }
235
236 pub async fn games_played_with_extra(&mut self, app_ids: Vec<u32>, custom_game: Option<String>) -> Result<(), SteamError> {
242 if !self.is_logged_in() {
243 return Err(SteamError::NotLoggedOn);
244 }
245
246 let mut msg = steam_protos::CMsgClientGamesPlayed::default();
247
248 let mut games: Vec<steam_protos::cmsg_client_games_played::GamePlayed> = app_ids.iter().take(32).map(|&id| steam_protos::cmsg_client_games_played::GamePlayed { game_id: Some(id as u64), ..Default::default() }).collect();
250
251 if let Some(ref name) = custom_game {
253 games.push(steam_protos::cmsg_client_games_played::GamePlayed { game_id: Some(NON_STEAM_GAME_ID), game_extra_info: Some(name.clone()), ..Default::default() });
255 }
256
257 msg.games_played = games;
258
259 self.session_recovery.record_playing(app_ids, custom_game);
261
262 self.send_message(steam_enums::EMsg::ClientGamesPlayedWithDataBlob, &msg).await
263 }
264
265 pub async fn redeem_key(&mut self, key: String) -> Result<(), SteamError> {
270 if !self.is_logged_in() {
271 return Err(SteamError::NotLoggedOn);
272 }
273
274 let msg = steam_protos::CMsgClientRegisterKey { key: Some(key) };
275
276 self.send_message(steam_enums::EMsg::ClientRegisterKey, &msg).await
277 }
278
279 pub async fn request_free_license(&mut self, app_ids: Vec<u32>) -> Result<steam_protos::CMsgClientRequestFreeLicenseResponse, SteamError> {
284 if !self.is_logged_in() {
285 return Err(SteamError::NotLoggedOn);
286 }
287
288 let msg = steam_protos::CMsgClientRequestFreeLicense { app_ids };
289
290 self.send_request_and_wait(steam_enums::EMsg::ClientRequestFreeLicense, &msg).await
291 }
292
293 pub async fn auto_request_free_license(&mut self, free_app_list: Vec<u32>, max_limit: usize) -> Result<steam_protos::CMsgClientRequestFreeLicenseResponse, SteamError> {
300 use rand::seq::SliceRandom;
301
302 if !self.is_logged_in() {
303 return Err(SteamError::NotLoggedOn);
304 }
305
306 let mut needed_apps: Vec<u32> = free_app_list.into_iter().filter(|&app_id| !self.owns_package(app_id)).collect();
309
310 let mut rng = rand::rng();
313 needed_apps.shuffle(&mut rng);
314
315 let request_apps: Vec<u32> = needed_apps.into_iter().take(max_limit).collect();
317
318 if request_apps.is_empty() {
319 return Ok(steam_protos::CMsgClientRequestFreeLicenseResponse { eresult: Some(steam_enums::EResult::OK as u32), granted_packageids: vec![], granted_appids: vec![] });
321 }
322
323 self.request_free_license(request_apps).await
324 }
325
326 pub async fn get_legacy_game_key(&mut self, app_id: u32) -> Result<(), SteamError> {
331 if !self.is_logged_in() {
332 return Err(SteamError::NotLoggedOn);
333 }
334
335 let msg = steam_protos::CMsgClientGetLegacyGameKey { app_id: Some(app_id) };
336
337 self.send_message(steam_enums::EMsg::ClientGetLegacyGameKey, &msg).await
338 }
339
340 pub fn get_owned_packages(&self) -> Vec<u32> {
344 self.apps.read().licenses.iter().filter(|l| (l.flags & ELicenseFlags::Expired as u32) == 0).map(|l| l.package_id).collect()
345 }
346
347 pub fn owns_package(&self, package_id: u32) -> bool {
349 self.get_owned_packages().contains(&package_id)
350 }
351}