use std::io::Write;
use byteorder::{LittleEndian, WriteBytesExt};
use steamid::SteamID;
use crate::{error::SteamError, SteamClient};
#[derive(Debug, Clone)]
pub struct AuthSessionTicket {
pub ticket: Vec<u8>,
pub handle: u32,
pub appid: u32,
pub steam_id: u64,
pub ticket_crc: u32,
pub estate: u32,
}
#[derive(Debug, Clone)]
pub struct AuthSessionResult {
pub steamid: SteamID,
pub auth_session_response: u32,
}
impl SteamClient {
pub async fn create_encrypted_app_ticket(&mut self, appid: u32, user_data: Option<&[u8]>) -> Result<Vec<u8>, SteamError> {
if !self.is_logged_in() {
return Err(SteamError::NotLoggedOn);
}
let msg = steam_protos::CMsgClientRequestEncryptedAppTicket { app_id: Some(appid), userdata: user_data.map(|d| d.to_vec()) };
let response: steam_protos::CMsgClientEncryptedAppTicketResponse = self.send_request_and_wait(steam_enums::EMsg::ClientRequestEncryptedAppTicket, &msg).await?;
if response.eresult.unwrap_or(1) != 1 {
return Err(SteamError::SteamResult(steam_enums::EResult::from_i32(response.eresult.unwrap_or(2)).unwrap_or(steam_enums::EResult::Fail)));
}
Ok(response.encrypted_ticket.and_then(|t| t.encrypted_ticket).unwrap_or_default())
}
pub async fn get_app_ownership_ticket(&mut self, appid: u32) -> Result<Vec<u8>, SteamError> {
if !self.is_logged_in() {
return Err(SteamError::NotLoggedOn);
}
let msg = steam_protos::CMsgClientGetAppOwnershipTicket { app_id: Some(appid) };
let response: steam_protos::CMsgClientGetAppOwnershipTicketResponse = self.send_request_and_wait(steam_enums::EMsg::ClientGetAppOwnershipTicket, &msg).await?;
if response.eresult.unwrap_or(1) != 1 {
return Err(SteamError::SteamResult(steam_enums::EResult::from_i32(response.eresult.unwrap_or(2) as i32).unwrap_or(steam_enums::EResult::Fail)));
}
Ok(response.ticket.unwrap_or_default())
}
pub async fn create_auth_session_ticket(&mut self, appid: u32) -> Result<AuthSessionTicket, SteamError> {
if !self.is_logged_in() {
return Err(SteamError::NotLoggedOn);
}
if self.gc_tokens.is_empty() {
return Err(SteamError::Other("No GC tokens available. Wait for connection to establish fully.".to_string()));
}
let ownership_ticket = self.get_app_ownership_ticket(appid).await?;
let gc_token = self.gc_tokens.remove(0);
let mut buffer = Vec::new();
buffer.write_u32::<LittleEndian>(gc_token.len() as u32)?;
buffer.write_all(&gc_token)?;
buffer.write_u32::<LittleEndian>(24)?;
buffer.write_u32::<LittleEndian>(1)?; buffer.write_u32::<LittleEndian>(2)?;
let ip_int = if let Some(ip_str) = self.account.read().public_ip.clone() {
match ip_str.parse::<std::net::Ipv4Addr>() {
Ok(ip) => u32::from(ip).swap_bytes(), Err(_) => 0,
}
} else {
0
};
buffer.write_u32::<LittleEndian>(ip_int)?;
buffer.write_u32::<LittleEndian>(0)?;
let timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis() as u64;
let connect_time_ms = self.connect_time; let session_time = (timestamp.saturating_sub(connect_time_ms)) as u32;
buffer.write_u32::<LittleEndian>(session_time)?;
self.connection_count += 1;
buffer.write_u32::<LittleEndian>(self.connection_count)?;
buffer.write_u32::<LittleEndian>(ownership_ticket.len() as u32)?;
buffer.write_all(&ownership_ticket)?;
let mut crc = flate2::Crc::new();
crc.update(&buffer);
let crc32 = crc.sum();
let ticket = AuthSessionTicket {
ticket: buffer,
handle: 0,
appid,
steam_id: 0, ticket_crc: crc32,
estate: 0,
};
self.activate_auth_session_tickets(appid, vec![ticket.clone()]).await?;
Ok(ticket)
}
pub async fn cancel_auth_session_ticket(&mut self, ticket: AuthSessionTicket) -> Result<(), SteamError> {
if !self.is_logged_in() {
return Err(SteamError::NotLoggedOn);
}
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) {
self.active_tickets.remove(pos);
}
self.send_auth_list(Some(ticket.appid)).await
}
pub async fn activate_auth_session_tickets(&mut self, appid: u32, tickets: Vec<AuthSessionTicket>) -> Result<Vec<AuthSessionResult>, SteamError> {
if !self.is_logged_in() {
return Err(SteamError::NotLoggedOn);
}
for mut ticket in tickets {
if self.active_tickets.iter().any(|t| t.steam_id == ticket.steam_id && t.appid == ticket.appid && t.ticket_crc == ticket.ticket_crc) {
continue;
}
if ticket.steam_id != 0 {
if let Some(pos) = self.active_tickets.iter().position(|t| t.steam_id == ticket.steam_id && t.appid == ticket.appid) {
self.active_tickets.remove(pos);
}
}
ticket.estate = if ticket.steam_id == 0 { 0 } else { 1 };
self.active_tickets.push(ticket);
}
self.send_auth_list(Some(appid)).await?;
Ok(Vec::new())
}
pub async fn end_auth_sessions(&mut self, appid: u32, steamids: Vec<SteamID>) -> Result<(), SteamError> {
if !self.is_logged_in() {
return Err(SteamError::NotLoggedOn);
}
let steamids_u64: Vec<u64> = steamids.iter().map(|s| s.steam_id64()).collect();
self.active_tickets.retain(|t| !(t.appid == appid && steamids_u64.contains(&t.steam_id)));
self.send_auth_list(Some(appid)).await
}
async fn send_auth_list(&mut self, force_appid: Option<u32>) -> Result<(), SteamError> {
let mut app_ids: Vec<u32> = self.active_tickets.iter().map(|t| t.appid).collect();
app_ids.sort();
app_ids.dedup();
if let Some(aid) = force_appid {
if !app_ids.contains(&aid) {
app_ids.push(aid);
}
}
let mut msg = steam_protos::CMsgClientAuthList {
tokens_left: Some(self.gc_tokens.len() as u32),
last_request_seq: Some(self.auth.read().auth_seq_me),
last_request_seq_from_server: Some(self.auth.read().auth_seq_them),
app_ids: app_ids.clone(),
message_sequence: Some(self.auth.read().auth_seq_me + 1),
..Default::default()
};
for ticket in &self.active_tickets {
let ticket_msg = steam_protos::CMsgAuthTicket {
gameid: Some(ticket.appid as u64),
ticket: Some(ticket.ticket.clone()),
h_steam_pipe: Some(self.h_steam_pipe),
ticket_crc: Some(ticket.ticket_crc),
steamid: Some(ticket.steam_id),
..Default::default()
};
msg.tickets.push(ticket_msg);
}
self.auth.write().auth_seq_me += 1;
self.send_message(steam_enums::EMsg::ClientAuthList, &msg).await
}
}