steam_client/services/
appauth.rs1use std::io::Write;
7
8use byteorder::{LittleEndian, WriteBytesExt};
9use steamid::SteamID;
10
11use crate::{error::SteamError, SteamClient};
12
13#[derive(Debug, Clone)]
15pub struct AuthSessionTicket {
16 pub ticket: Vec<u8>,
18 pub handle: u32,
20 pub appid: u32,
22 pub steam_id: u64,
24 pub ticket_crc: u32,
26 pub estate: u32,
28}
29
30#[derive(Debug, Clone)]
32pub struct AuthSessionResult {
33 pub steamid: SteamID,
35 pub auth_session_response: u32,
37}
38
39impl SteamClient {
40 pub async fn create_encrypted_app_ticket(&mut self, appid: u32, user_data: Option<&[u8]>) -> Result<Vec<u8>, SteamError> {
51 if !self.is_logged_in() {
52 return Err(SteamError::NotLoggedOn);
53 }
54
55 let msg = steam_protos::CMsgClientRequestEncryptedAppTicket { app_id: Some(appid), userdata: user_data.map(|d| d.to_vec()) };
56
57 let response: steam_protos::CMsgClientEncryptedAppTicketResponse = self.send_request_and_wait(steam_enums::EMsg::ClientRequestEncryptedAppTicket, &msg).await?;
59
60 if response.eresult.unwrap_or(1) != 1 {
61 return Err(SteamError::SteamResult(steam_enums::EResult::from_i32(response.eresult.unwrap_or(2)).unwrap_or(steam_enums::EResult::Fail)));
62 }
63
64 Ok(response.encrypted_ticket.and_then(|t| t.encrypted_ticket).unwrap_or_default())
65 }
66
67 pub async fn get_app_ownership_ticket(&mut self, appid: u32) -> Result<Vec<u8>, SteamError> {
75 if !self.is_logged_in() {
76 return Err(SteamError::NotLoggedOn);
77 }
78
79 let msg = steam_protos::CMsgClientGetAppOwnershipTicket { app_id: Some(appid) };
80
81 let response: steam_protos::CMsgClientGetAppOwnershipTicketResponse = self.send_request_and_wait(steam_enums::EMsg::ClientGetAppOwnershipTicket, &msg).await?;
83
84 if response.eresult.unwrap_or(1) != 1 {
85 return Err(SteamError::SteamResult(steam_enums::EResult::from_i32(response.eresult.unwrap_or(2) as i32).unwrap_or(steam_enums::EResult::Fail)));
86 }
87
88 Ok(response.ticket.unwrap_or_default())
89 }
90
91 pub async fn create_auth_session_ticket(&mut self, appid: u32) -> Result<AuthSessionTicket, SteamError> {
102 if !self.is_logged_in() {
103 return Err(SteamError::NotLoggedOn);
104 }
105
106 if self.gc_tokens.is_empty() {
107 return Err(SteamError::Other("No GC tokens available. Wait for connection to establish fully.".to_string()));
108 }
109
110 let ownership_ticket = self.get_app_ownership_ticket(appid).await?;
117
118 let gc_token = self.gc_tokens.remove(0);
120
121 let mut buffer = Vec::new();
123
124 buffer.write_u32::<LittleEndian>(gc_token.len() as u32)?;
126 buffer.write_all(&gc_token)?;
127
128 buffer.write_u32::<LittleEndian>(24)?;
130 buffer.write_u32::<LittleEndian>(1)?; buffer.write_u32::<LittleEndian>(2)?; let ip_int = if let Some(ip_str) = self.account.read().public_ip.clone() {
135 match ip_str.parse::<std::net::Ipv4Addr>() {
136 Ok(ip) => u32::from(ip).swap_bytes(), Err(_) => 0,
138 }
139 } else {
140 0
141 };
142 buffer.write_u32::<LittleEndian>(ip_int)?;
143
144 buffer.write_u32::<LittleEndian>(0)?; let timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis() as u64;
147 let connect_time_ms = self.connect_time; let session_time = (timestamp.saturating_sub(connect_time_ms)) as u32;
149 buffer.write_u32::<LittleEndian>(session_time)?;
150
151 self.connection_count += 1;
152 buffer.write_u32::<LittleEndian>(self.connection_count)?;
153
154 buffer.write_u32::<LittleEndian>(ownership_ticket.len() as u32)?;
156 buffer.write_all(&ownership_ticket)?;
157
158 let mut crc = flate2::Crc::new();
160 crc.update(&buffer);
161 let crc32 = crc.sum();
162
163 let ticket = AuthSessionTicket {
165 ticket: buffer,
166 handle: 0,
167 appid,
168 steam_id: 0, ticket_crc: crc32,
170 estate: 0,
171 };
172
173 self.activate_auth_session_tickets(appid, vec![ticket.clone()]).await?;
175
176 Ok(ticket)
177 }
178
179 pub async fn cancel_auth_session_ticket(&mut self, ticket: AuthSessionTicket) -> Result<(), SteamError> {
186 if !self.is_logged_in() {
187 return Err(SteamError::NotLoggedOn);
188 }
189
190 if let Some(pos) = self.active_tickets.iter().position(|t| t.steam_id == ticket.steam_id && t.appid == ticket.appid && t.ticket_crc == ticket.ticket_crc) {
192 self.active_tickets.remove(pos);
193 }
194
195 self.send_auth_list(Some(ticket.appid)).await
197 }
198
199 pub async fn activate_auth_session_tickets(&mut self, appid: u32, tickets: Vec<AuthSessionTicket>) -> Result<Vec<AuthSessionResult>, SteamError> {
207 if !self.is_logged_in() {
208 return Err(SteamError::NotLoggedOn);
209 }
210
211 for mut ticket in tickets {
212 if self.active_tickets.iter().any(|t| t.steam_id == ticket.steam_id && t.appid == ticket.appid && t.ticket_crc == ticket.ticket_crc) {
214 continue;
215 }
216
217 if ticket.steam_id != 0 {
220 if let Some(pos) = self.active_tickets.iter().position(|t| t.steam_id == ticket.steam_id && t.appid == ticket.appid) {
221 self.active_tickets.remove(pos);
222 }
223 }
224
225 ticket.estate = if ticket.steam_id == 0 { 0 } else { 1 };
227 self.active_tickets.push(ticket);
228 }
229
230 self.send_auth_list(Some(appid)).await?;
231
232 Ok(Vec::new())
234 }
235
236 pub async fn end_auth_sessions(&mut self, appid: u32, steamids: Vec<SteamID>) -> Result<(), SteamError> {
242 if !self.is_logged_in() {
243 return Err(SteamError::NotLoggedOn);
244 }
245
246 let steamids_u64: Vec<u64> = steamids.iter().map(|s| s.steam_id64()).collect();
248 self.active_tickets.retain(|t| !(t.appid == appid && steamids_u64.contains(&t.steam_id)));
249
250 self.send_auth_list(Some(appid)).await
251 }
252
253 async fn send_auth_list(&mut self, force_appid: Option<u32>) -> Result<(), SteamError> {
255 let mut app_ids: Vec<u32> = self.active_tickets.iter().map(|t| t.appid).collect();
256 app_ids.sort();
257 app_ids.dedup();
258
259 if let Some(aid) = force_appid {
260 if !app_ids.contains(&aid) {
261 app_ids.push(aid);
262 }
263 }
264
265 let mut msg = steam_protos::CMsgClientAuthList {
266 tokens_left: Some(self.gc_tokens.len() as u32),
267 last_request_seq: Some(self.auth.read().auth_seq_me),
268 last_request_seq_from_server: Some(self.auth.read().auth_seq_them),
269 app_ids: app_ids.clone(),
270 message_sequence: Some(self.auth.read().auth_seq_me + 1),
271 ..Default::default()
272 };
273
274 for ticket in &self.active_tickets {
275 let ticket_msg = steam_protos::CMsgAuthTicket {
276 gameid: Some(ticket.appid as u64),
277 ticket: Some(ticket.ticket.clone()),
278 h_steam_pipe: Some(self.h_steam_pipe),
279 ticket_crc: Some(ticket.ticket_crc),
280 steamid: Some(ticket.steam_id),
281 ..Default::default()
282 };
283 msg.tickets.push(ticket_msg);
284 }
285
286 self.auth.write().auth_seq_me += 1;
287
288 self.send_message(steam_enums::EMsg::ClientAuthList, &msg).await
289 }
290}