use crate::connection::Connection; use crate::eresult::EResult; use crate::net::{NetMessageHeader, NetworkError}; use crate::proto::steammessages_base::CMsgIPAddress; use crate::proto::steammessages_clientserver_login::{ CMsgClientHello, CMsgClientLogon, CMsgClientLogonResponse, }; use crate::serverlist::ServerDiscoveryError; use protobuf::MessageField; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; use steam_vent_crypto::CryptError; use steamid_ng::{AccountType, Instance, SteamID, Universe}; use thiserror::Error; use tracing::debug; type Result = std::result::Result; #[derive(Debug, Error)] pub enum ConnectionError { #[error("Network error: {0:#}")] Network(#[from] NetworkError), #[error("Login failed: {0:#}")] LoginError(#[from] LoginError), #[error(transparent)] Discovery(#[from] ServerDiscoveryError), } #[derive(Debug, Error)] pub enum LoginError { #[error("invalid credentials")] InvalidCredentials, #[error("unknown error {0:?}")] Unknown(EResult), #[error("steam guard required")] SteamGuardRequired, #[error("steam returned an invalid public key: {0:#}")] InvalidPubKey(CryptError), #[error("account not available")] UnavailableAccount, #[error("rate limited")] RateLimited, } impl From for LoginError { fn from(value: EResult) -> Self { match value { EResult::InvalidPassword => LoginError::InvalidCredentials, EResult::AccountDisabled | EResult::AccountLockedDown | EResult::AccountHasBeenDeleted | EResult::AccountNotFound => LoginError::InvalidCredentials, EResult::RateLimitExceeded | EResult::AccountActivityLimitExceeded | EResult::LimitExceeded | EResult::AccountLimitExceeded => LoginError::RateLimited, EResult::AccountLoginDeniedNeedTwoFactor => LoginError::SteamGuardRequired, value => LoginError::Unknown(value), } } } #[derive(Default, Debug)] pub struct JobIdCounter(AtomicU64); impl JobIdCounter { #[allow(clippy::should_implement_trait)] pub fn next(&self) -> u64 { self.0.fetch_add(1, Ordering::SeqCst) } } #[derive(Debug)] pub struct Session { pub session_id: i32, pub job_id: JobIdCounter, pub steam_id: SteamID, pub heartbeat_interval: Duration, } impl Default for Session { fn default() -> Self { Session { session_id: 0, job_id: JobIdCounter::default(), steam_id: SteamID::from(0), heartbeat_interval: Duration::from_secs(15), } } } impl Session { pub fn header(&self) -> NetMessageHeader { NetMessageHeader { session_id: self.session_id, source_job_id: self.job_id.next(), target_job_id: u64::MAX, steam_id: self.steam_id, ..NetMessageHeader::default() } } } pub async fn anonymous(connection: &mut Connection) -> Result { let mut ip = CMsgIPAddress::new(); ip.set_v4(0); let logon = CMsgClientLogon { protocol_version: Some(65580), client_os_type: Some(203), anon_user_target_account_name: Some(String::from("anonymous")), account_name: Some(String::from("anonymous")), supports_rate_limit_response: Some(false), obfuscated_private_ip: MessageField::some(ip), client_language: Some(String::new()), chat_mode: Some(2), client_package_version: Some(1771), ..CMsgClientLogon::default() }; send_logon( connection, logon, SteamID::new(0, Instance::All, AccountType::AnonUser, Universe::Public), ) .await } pub async fn login( connection: &mut Connection, account: &str, steam_id: SteamID, access_token: &str, ) -> Result { let mut ip = CMsgIPAddress::new(); ip.set_v4(0); let logon = CMsgClientLogon { protocol_version: Some(65580), client_os_type: Some(203), account_name: Some(String::from(account)), supports_rate_limit_response: Some(false), obfuscated_private_ip: MessageField::some(ip), client_language: Some(String::new()), machine_name: Some(String::new()), steamguard_dont_remember_computer: Some(false), chat_mode: Some(2), access_token: Some(access_token.into()), client_package_version: Some(1771), ..CMsgClientLogon::default() }; send_logon(connection, logon, steam_id).await } async fn send_logon( connection: &mut Connection, logon: CMsgClientLogon, steam_id: SteamID, ) -> Result { let header = NetMessageHeader { session_id: 0, source_job_id: u64::MAX, target_job_id: u64::MAX, steam_id, ..NetMessageHeader::default() }; let fut = connection.one::(); connection.send(header, logon).await?; let (header, response) = fut.await?; EResult::from_result(response.eresult()).map_err(LoginError::from)?; debug!(steam_id = u64::from(steam_id), "session started"); Ok(Session { session_id: header.session_id, steam_id: header.steam_id, job_id: JobIdCounter::default(), heartbeat_interval: Duration::from_secs(response.heartbeat_seconds() as u64), }) } pub async fn hello(conn: &mut Connection) -> Result<(), NetworkError> { const PROTOCOL_VERSION: u32 = 65580; let req = CMsgClientHello { protocol_version: Some(PROTOCOL_VERSION), ..CMsgClientHello::default() }; let header = NetMessageHeader { session_id: 0, source_job_id: u64::MAX, target_job_id: u64::MAX, steam_id: SteamID::from(0), ..NetMessageHeader::default() }; conn.send(header, req).await?; Ok(()) }