steamkit/steam/handlers/steam_apps/
steam_apps.rs1use std::sync::{Arc, Weak};
8
9use async_trait::async_trait;
10use steamkit_protos::steam::{
11 c_msg_client_pics_product_info_request, CMsgClientCheckAppBetaPassword,
12 CMsgClientGetAppOwnershipTicket, CMsgClientGetDepotDecryptionKey,
13 CMsgClientPicsAccessTokenRequest, CMsgClientPicsChangesSinceRequest,
14 CMsgClientPicsPrivateBetaRequest, CMsgClientPicsProductInfoRequest,
15 CMsgClientRequestFreeLicense,
16};
17
18use crate::base::client_msg::{ClientMsg, ClientMsgProtobuf, IClientMsg};
19use crate::base::generated::steam_language::internal::MsgClientGetLegacyGameKey;
20use crate::base::generated::steam_language::EMsg;
21use crate::base::packet_base::IPacketMsg;
22use crate::error::{SteamError, SteamResult};
23use crate::steam::handlers::client_msg_handler::ClientMsgHandler;
24use crate::steam::steam_client::callback_mgr::callback_msg::CallbackMsg;
25use crate::steam::steam_client::steam_client::SteamClient;
26use crate::types::job_id::ResultSet;
27
28use super::callbacks::{
29 AppOwnershipTicketCallback, CheckAppBetaPasswordCallback, DepotKeyCallback,
30 FreeLicenseCallback, GameConnectTokensCallback, GuestPassListCallback, LegacyGameKeyCallback,
31 LicenseListCallback, PICSChangesCallback, PICSProductInfoCallback, PICSTokensCallback,
32 PrivateBetaCallback, PurchaseResponseCallback, RedeemGuestPassResponseCallback,
33 VACStatusCallback,
34};
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub struct PICSRequest {
39 id: u32,
40 access_token: u64,
41}
42
43impl PICSRequest {
44 pub fn new(id: u32, access_token: u64) -> Self {
46 Self { id, access_token }
47 }
48
49 pub fn id(&self) -> u32 {
51 self.id
52 }
53
54 pub fn set_id(&mut self, id: u32) {
56 self.id = id;
57 }
58
59 pub fn access_token(&self) -> u64 {
61 self.access_token
62 }
63
64 pub fn set_access_token(&mut self, access_token: u64) {
66 self.access_token = access_token;
67 }
68}
69
70pub struct SteamApps {
72 client: Weak<SteamClient>,
73}
74
75impl std::fmt::Debug for SteamApps {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("SteamApps").finish()
78 }
79}
80
81impl SteamApps {
82 pub fn new(client: &Arc<SteamClient>) -> Self {
84 Self {
85 client: Arc::downgrade(client),
86 }
87 }
88
89 fn client(&self) -> SteamResult<Arc<SteamClient>> {
91 self.client
92 .upgrade()
93 .ok_or_else(|| SteamError::Connection("SteamClient has been dropped".to_string()))
94 }
95
96 pub async fn get_app_ownership_ticket(
100 &self,
101 appid: u32,
102 ) -> SteamResult<AppOwnershipTicketCallback> {
103 let client = self.client()?;
104
105 let mut request = ClientMsgProtobuf::<CMsgClientGetAppOwnershipTicket>::new(
106 EMsg::ClientGetAppOwnershipTicket,
107 );
108
109 request.body_mut().app_id = Some(appid);
110
111 let job_id = client.get_next_job_id();
112 request.set_source_job_id(job_id);
113
114 let job = client.start_job::<AppOwnershipTicketCallback>(job_id);
115
116 client.send(&request)?;
117
118 job.wait().await
119 }
120
121 pub async fn get_depot_decryption_key(
125 &self,
126 depot_id: u32,
127 app_id: u32,
128 ) -> SteamResult<DepotKeyCallback> {
129 let client = self.client()?;
130
131 let mut request = ClientMsgProtobuf::<CMsgClientGetDepotDecryptionKey>::new(
132 EMsg::ClientGetDepotDecryptionKey,
133 );
134
135 request.body_mut().depot_id = Some(depot_id);
136 request.body_mut().app_id = Some(app_id);
137
138 let job_id = client.get_next_job_id();
139 request.set_source_job_id(job_id);
140
141 let job = client.start_job::<DepotKeyCallback>(job_id);
142
143 client.send(&request)?;
144
145 job.wait().await
146 }
147
148 pub async fn pics_get_access_tokens(
152 &self,
153 app: Option<u32>,
154 package: Option<u32>,
155 ) -> SteamResult<PICSTokensCallback> {
156 let apps: Vec<u32> = app.into_iter().collect();
157 let packages: Vec<u32> = package.into_iter().collect();
158
159 self.pics_get_access_tokens_for_ids(&apps, &packages).await
160 }
161
162 pub async fn pics_get_access_tokens_for_ids(
166 &self,
167 app_ids: &[u32],
168 package_ids: &[u32],
169 ) -> SteamResult<PICSTokensCallback> {
170 let client = self.client()?;
171
172 let mut request = ClientMsgProtobuf::<CMsgClientPicsAccessTokenRequest>::new(
173 EMsg::ClientPICSAccessTokenRequest,
174 );
175
176 request.body_mut().appids = app_ids.to_vec();
177 request.body_mut().packageids = package_ids.to_vec();
178
179 let job_id = client.get_next_job_id();
180 request.set_source_job_id(job_id);
181
182 let job = client.start_job::<PICSTokensCallback>(job_id);
183
184 client.send(&request)?;
185
186 job.wait().await
187 }
188
189 pub async fn pics_get_changes_since(
193 &self,
194 last_change_number: u32,
195 send_app_changelist: bool,
196 send_package_changelist: bool,
197 ) -> SteamResult<PICSChangesCallback> {
198 let client = self.client()?;
199
200 let mut request = ClientMsgProtobuf::<CMsgClientPicsChangesSinceRequest>::new(
201 EMsg::ClientPICSChangesSinceRequest,
202 );
203
204 request.body_mut().since_change_number = Some(last_change_number);
205 request.body_mut().send_app_info_changes = Some(send_app_changelist);
206 request.body_mut().send_package_info_changes = Some(send_package_changelist);
207
208 let job_id = client.get_next_job_id();
209 request.set_source_job_id(job_id);
210
211 let job = client.start_job::<PICSChangesCallback>(job_id);
212
213 client.send(&request)?;
214
215 job.wait().await
216 }
217
218 pub async fn pics_get_product_info(
222 &self,
223 app: Option<&PICSRequest>,
224 package: Option<&PICSRequest>,
225 meta_data_only: bool,
226 ) -> SteamResult<ResultSet<PICSProductInfoCallback>> {
227 let apps: Vec<PICSRequest> = app.into_iter().copied().collect();
228 let packages: Vec<PICSRequest> = package.into_iter().copied().collect();
229
230 self.pics_get_product_info_for_ids(&apps, &packages, meta_data_only)
231 .await
232 }
233
234 pub async fn pics_get_product_info_for_ids(
238 &self,
239 apps: &[PICSRequest],
240 packages: &[PICSRequest],
241 meta_data_only: bool,
242 ) -> SteamResult<ResultSet<PICSProductInfoCallback>> {
243 let client = self.client()?;
244
245 let mut request = ClientMsgProtobuf::<CMsgClientPicsProductInfoRequest>::new(
246 EMsg::ClientPICSProductInfoRequest,
247 );
248
249 {
250 let body = request.body_mut();
251
252 for app in apps {
253 body.apps
254 .push(c_msg_client_pics_product_info_request::AppInfo {
255 appid: Some(app.id()),
256 access_token: Some(app.access_token()),
257 only_public_obsolete: Some(false),
258 });
259 }
260
261 for package in packages {
262 body.packages
263 .push(c_msg_client_pics_product_info_request::PackageInfo {
264 packageid: Some(package.id()),
265 access_token: Some(package.access_token()),
266 });
267 }
268
269 body.meta_data_only = Some(meta_data_only);
270 }
271
272 let job_id = client.get_next_job_id();
273 request.set_source_job_id(job_id);
274
275 let handle = client
276 .job_manager()
277 .start_job_multiple::<PICSProductInfoCallback>(job_id, |callback| {
278 !callback.response_pending()
279 });
280
281 if !client.is_connected() {
282 client.job_manager().fail_job(job_id);
283 }
284
285 client.send(&request)?;
286
287 let result = handle.wait().await?;
288
289 Ok(result)
290 }
291
292 pub async fn request_free_license(&self, app: u32) -> SteamResult<FreeLicenseCallback> {
296 self.request_free_licenses(&[app]).await
297 }
298
299 pub async fn request_free_licenses(&self, apps: &[u32]) -> SteamResult<FreeLicenseCallback> {
303 let client = self.client()?;
304
305 let mut request =
306 ClientMsgProtobuf::<CMsgClientRequestFreeLicense>::new(EMsg::ClientRequestFreeLicense);
307
308 request.body_mut().appids = apps.to_vec();
309
310 let job_id = client.get_next_job_id();
311 request.set_source_job_id(job_id);
312
313 let job = client.start_job::<FreeLicenseCallback>(job_id);
314
315 client.send(&request)?;
316
317 job.wait().await
318 }
319
320 pub async fn check_app_beta_password(
324 &self,
325 app: u32,
326 password: &str,
327 ) -> SteamResult<CheckAppBetaPasswordCallback> {
328 let client = self.client()?;
329
330 let mut request = ClientMsgProtobuf::<CMsgClientCheckAppBetaPassword>::new(
331 EMsg::ClientCheckAppBetaPassword,
332 );
333
334 request.body_mut().app_id = Some(app);
335 request.body_mut().betapassword = Some(password.to_string());
336
337 let job_id = client.get_next_job_id();
338 request.set_source_job_id(job_id);
339
340 let job = client.start_job::<CheckAppBetaPasswordCallback>(job_id);
341
342 client.send(&request)?;
343
344 job.wait().await
345 }
346
347 pub async fn get_legacy_game_key(&self, appid: u32) -> SteamResult<LegacyGameKeyCallback> {
351 let client = self.client()?;
352
353 let mut request = ClientMsg::<MsgClientGetLegacyGameKey>::new(EMsg::ClientGetLegacyGameKey);
354
355 request.body_mut().app_id = appid;
356
357 let job_id = client.get_next_job_id();
358 request.set_source_job_id(job_id);
359
360 let job = client.start_job::<LegacyGameKeyCallback>(job_id);
361
362 client.send(&request)?;
363
364 job.wait().await
365 }
366
367 pub async fn pics_get_private_beta(
371 &self,
372 app: u32,
373 access_token: u64,
374 branch: &str,
375 branch_password_hash: &[u8],
376 ) -> SteamResult<PrivateBetaCallback> {
377 let client = self.client()?;
378
379 let mut request = ClientMsgProtobuf::<CMsgClientPicsPrivateBetaRequest>::new(
380 EMsg::ClientPICSPrivateBetaRequest,
381 );
382
383 request.body_mut().appid = Some(app);
384 request.body_mut().access_token = Some(access_token);
385 request.body_mut().beta_name = Some(branch.to_string());
386 request.body_mut().password_hash = Some(branch_password_hash.to_vec());
387
388 let job_id = client.get_next_job_id();
389 request.set_source_job_id(job_id);
390
391 let job = client.start_job::<PrivateBetaCallback>(job_id);
392
393 client.send(&request)?;
394
395 job.wait().await
396 }
397}
398
399#[async_trait]
400impl ClientMsgHandler for SteamApps {
401 async fn handle_msg(
402 &self,
403 client: &SteamClient,
404 packet_msg: &dyn IPacketMsg,
405 ) -> SteamResult<()> {
406 let callback: Option<Box<dyn CallbackMsg>> = match packet_msg.msg_type() {
407 EMsg::ClientLicenseList => {
408 Some(Box::new(LicenseListCallback::from_packet(packet_msg)?))
409 }
410 EMsg::ClientRequestFreeLicenseResponse => {
411 Some(Box::new(FreeLicenseCallback::from_packet(packet_msg)?))
412 }
413 EMsg::ClientPurchaseResponse => {
414 Some(Box::new(PurchaseResponseCallback::from_packet(packet_msg)?))
415 }
416 EMsg::ClientRedeemGuestPassResponse => Some(Box::new(
417 RedeemGuestPassResponseCallback::from_packet(packet_msg)?,
418 )),
419 EMsg::ClientGameConnectTokens => Some(Box::new(
420 GameConnectTokensCallback::from_packet(packet_msg)?,
421 )),
422 EMsg::ClientVACBanStatus => Some(Box::new(VACStatusCallback::from_packet(packet_msg)?)),
423 EMsg::ClientGetAppOwnershipTicketResponse => Some(Box::new(
424 AppOwnershipTicketCallback::from_packet(packet_msg)?,
425 )),
426 EMsg::ClientGetDepotDecryptionKeyResponse => {
427 Some(Box::new(DepotKeyCallback::from_packet(packet_msg)?))
428 }
429 EMsg::ClientGetLegacyGameKeyResponse => {
430 Some(Box::new(LegacyGameKeyCallback::from_packet(packet_msg)?))
431 }
432 EMsg::ClientPICSAccessTokenResponse => {
433 Some(Box::new(PICSTokensCallback::from_packet(packet_msg)?))
434 }
435 EMsg::ClientPICSChangesSinceResponse => {
436 Some(Box::new(PICSChangesCallback::from_packet(packet_msg)?))
437 }
438 EMsg::ClientPICSProductInfoResponse => {
439 Some(Box::new(PICSProductInfoCallback::from_packet(packet_msg)?))
440 }
441 EMsg::ClientUpdateGuestPassesList => {
442 Some(Box::new(GuestPassListCallback::from_packet(packet_msg)?))
443 }
444 EMsg::ClientCheckAppBetaPasswordResponse => Some(Box::new(
445 CheckAppBetaPasswordCallback::from_packet(packet_msg)?,
446 )),
447 EMsg::ClientPICSPrivateBetaResponse => {
448 Some(Box::new(PrivateBetaCallback::from_packet(packet_msg)?))
449 }
450 _ => None,
452 };
453
454 if let Some(callback) = callback {
455 client.post_callback(callback);
456 }
457
458 Ok(())
459 }
460}