192 lines
6.8 KiB
Rust
192 lines
6.8 KiB
Rust
use color_eyre::eyre;
|
|
use rand::RngCore;
|
|
use vapore::message::CMProtoBufMessage;
|
|
use vapore_proto::{
|
|
enums_clientserver::EMsg,
|
|
steammessages_auth_steamclient::{
|
|
CAuthentication_BeginAuthSessionViaQR_Request,
|
|
CAuthentication_BeginAuthSessionViaQR_Response, CAuthentication_DeviceDetails,
|
|
CAuthentication_PollAuthSessionStatus_Request,
|
|
CAuthentication_PollAuthSessionStatus_Response, EAuthTokenPlatformType,
|
|
},
|
|
steammessages_base::{cmsg_ipaddress, CMsgIPAddress},
|
|
steammessages_clientserver::CMsgClientLicenseList,
|
|
steammessages_clientserver_login::{CMsgClientHello, CMsgClientLogon, CMsgClientLogonResponse},
|
|
};
|
|
|
|
#[tokio::main]
|
|
pub async fn main() -> eyre::Result<()> {
|
|
env_logger::init();
|
|
color_eyre::install()?;
|
|
|
|
let servers = vapore::selection::bootstrap_find_servers().await?;
|
|
log::debug!("Found servers: {:?}", servers);
|
|
|
|
let (session, context) = vapore::connection::CMSession::connect(&servers[0]).await?;
|
|
|
|
tokio::spawn(context);
|
|
|
|
session.send_notification(
|
|
EMsg::k_EMsgClientHello,
|
|
CMsgClientHello {
|
|
protocol_version: Some(0x1002c),
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
log::debug!("Sent hello");
|
|
|
|
// machine_id is supposed to be a binary key/value with the SHA1 of the machine's
|
|
// BB3: Machine GUID
|
|
// FF2: MAC address
|
|
// 3B3: Disk ID
|
|
// We should probably make these consistent so Valve doesn't get suspicious,
|
|
// but for now let's make them random
|
|
// TODO: Find a more generic way to make this
|
|
let mut machine_id = Vec::with_capacity(155);
|
|
machine_id.extend_from_slice(b"\x00MessageObject\x00");
|
|
for key in [b"BB3", b"FF2", b"3B3"] {
|
|
let mut data = [0u8; 20];
|
|
rand::thread_rng().fill_bytes(&mut data);
|
|
let hex_bytes = hex::encode(data).into_bytes();
|
|
|
|
// Type is string
|
|
machine_id.push(b'\x01');
|
|
machine_id.extend_from_slice(key);
|
|
machine_id.push(b'\x00');
|
|
machine_id.extend_from_slice(&hex_bytes);
|
|
machine_id.push(b'\x00');
|
|
}
|
|
// suitable end bytes
|
|
machine_id.extend_from_slice(b"\x08\x08");
|
|
|
|
let response = session
|
|
.call_service_method::<_, CAuthentication_BeginAuthSessionViaQR_Response>(
|
|
"Authentication.BeginAuthSessionViaQR#1".to_string(),
|
|
CAuthentication_BeginAuthSessionViaQR_Request {
|
|
device_details: protobuf::MessageField::some(CAuthentication_DeviceDetails {
|
|
device_friendly_name: Some("vapore".to_string()),
|
|
platform_type: Some(protobuf::EnumOrUnknown::new(
|
|
EAuthTokenPlatformType::k_EAuthTokenPlatformType_SteamClient,
|
|
)),
|
|
// Windows 11
|
|
os_type: Some(20),
|
|
machine_id: Some(machine_id.clone()),
|
|
..Default::default()
|
|
}),
|
|
..Default::default()
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
log::debug!("Got response {:#x?}", response);
|
|
|
|
let code = qrcode::QrCode::new(response.body.challenge_url()).unwrap();
|
|
log::info!(
|
|
"Got new QR code:\n{}",
|
|
code.render::<qrcode::render::unicode::Dense1x2>().build()
|
|
);
|
|
|
|
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs_f32(
|
|
response.body.interval(),
|
|
));
|
|
|
|
let (refresh_token, account_name) = loop {
|
|
interval.tick().await;
|
|
let request = CAuthentication_PollAuthSessionStatus_Request {
|
|
client_id: response.body.client_id,
|
|
request_id: response.body.request_id.clone(),
|
|
..Default::default()
|
|
};
|
|
let response: CMProtoBufMessage<CAuthentication_PollAuthSessionStatus_Response> = session
|
|
.call_service_method(
|
|
"Authentication.PollAuthSessionStatus#1".to_string(),
|
|
request,
|
|
)
|
|
.await?;
|
|
|
|
log::debug!("Got auth poll status {:#?}", response.body);
|
|
|
|
if let Some(access_token) = response.body.refresh_token {
|
|
let account_name = response.body.account_name.unwrap_or_default();
|
|
break (access_token, account_name);
|
|
}
|
|
|
|
if let Some(new_url) = response.body.new_challenge_url {
|
|
let code = qrcode::QrCode::new(new_url)?;
|
|
log::info!(
|
|
"Got new QR code:\n{}",
|
|
code.render::<qrcode::render::unicode::Dense1x2>().build()
|
|
)
|
|
};
|
|
};
|
|
|
|
log::debug!(
|
|
"Got account name {}, access token {}",
|
|
account_name,
|
|
refresh_token
|
|
);
|
|
|
|
// normal user, desktop instance, public universe
|
|
session.set_steam_id(0x0110_0001_0000_0000);
|
|
|
|
session.send_notification(
|
|
EMsg::k_EMsgClientLogon,
|
|
CMsgClientLogon {
|
|
account_name: Some(account_name),
|
|
access_token: Some(refresh_token),
|
|
machine_name: Some("vapore".to_string()),
|
|
machine_id: Some(machine_id),
|
|
client_language: Some("english".to_string()),
|
|
protocol_version: Some(0x1002c),
|
|
client_os_type: Some(20),
|
|
client_package_version: Some(1771),
|
|
supports_rate_limit_response: Some(true),
|
|
should_remember_password: Some(true),
|
|
obfuscated_private_ip: protobuf::MessageField::some(CMsgIPAddress {
|
|
ip: Some(cmsg_ipaddress::Ip::V4(0xc0a8_0102 ^ 0xbaad_f00d)),
|
|
..Default::default()
|
|
}),
|
|
deprecated_obfustucated_private_ip: Some(0xc0a8_0102 ^ 0xbaad_f00d),
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
let session_cloned = session.clone();
|
|
tokio::spawn(async move {
|
|
let license_list_raw = session_cloned
|
|
.subscribe_message_type(EMsg::k_EMsgClientLicenseList)
|
|
.recv()
|
|
.await
|
|
.unwrap();
|
|
let license_list: CMProtoBufMessage<CMsgClientLicenseList> =
|
|
CMProtoBufMessage::deserialize(license_list_raw).unwrap();
|
|
for license in &license_list.body.licenses {
|
|
log::info!("Own package ID: {}", license.package_id());
|
|
}
|
|
});
|
|
|
|
let mut finish_receiver = session.subscribe_message_type(EMsg::k_EMsgClientLogOnResponse);
|
|
|
|
let raw_response = finish_receiver.recv().await?;
|
|
let response: CMProtoBufMessage<CMsgClientLogonResponse> =
|
|
CMProtoBufMessage::deserialize(raw_response)?;
|
|
|
|
log::debug!("Got logon response: {:#x?}", response);
|
|
|
|
if response.body.eresult != Some(0x01) {
|
|
eyre::bail!("Login failed");
|
|
};
|
|
|
|
session.set_steam_id(response.header.steamid());
|
|
session.set_client_session_id(response.header.client_sessionid());
|
|
|
|
if let Some(heartbeat_seconds) = response.body.heartbeat_seconds {
|
|
if heartbeat_seconds >= 5 {
|
|
session.begin_heartbeat(heartbeat_seconds as u32);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|