diff --git a/Cargo.lock b/Cargo.lock index 9fe904a..288b432 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,6 +471,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "home" version = "0.5.9" @@ -1612,9 +1618,11 @@ dependencies = [ "color-eyre", "env_logger", "futures", + "hex", "keyvalues-serde", "log", "protobuf", + "rand", "reqwest", "serde", "tokio", diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index 19fffcc..0f01284 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -8,9 +8,11 @@ async-tungstenite = { version = "0.27.0", features = ["tokio-rustls-native-certs color-eyre.workspace = true env_logger.workspace = true futures = "0.3.30" +hex = "0.4.3" keyvalues-serde.workspace = true log.workspace = true protobuf.workspace = true +rand = "0.8.5" reqwest = { version = "0.12", features = ["rustls-tls-native-roots"], default-features = false} serde = { version = "1.0.209", features = ["derive"] } tokio = { version = "1.39", features = ["rt", "rt-multi-thread", "macros"]} diff --git a/daemon/src/connection.rs b/daemon/src/connection.rs index 96d48ff..792b8bb 100644 --- a/daemon/src/connection.rs +++ b/daemon/src/connection.rs @@ -41,6 +41,7 @@ pub async fn bootstrap_find_servers() -> eyre::Result> { } /// A message sent over the socket. Can be either sent or recieved +#[derive(Debug, Clone)] pub struct CMProtoBufMessage { pub action: enums_clientserver::EMsg, pub header: steammessages_base::CMsgProtoBufHeader, @@ -54,7 +55,7 @@ impl CMProtoBufMessage { let length = 4 + 4 + self.header.compute_size() + self.body.compute_size(); let mut out = Vec::with_capacity(length.try_into()?); - out.extend_from_slice(&self.action.value().to_le_bytes()); + out.extend_from_slice(&(self.action.value() as u32 | 0x80000000).to_le_bytes()); out.extend_from_slice(&self.header.cached_size().to_le_bytes()); self.header.write_to_vec(&mut out)?; self.body.write_to_vec(&mut out)?; @@ -74,6 +75,7 @@ impl CMProtoBufMessage { } /// A message sent over the socket, but the body is still serialized +#[derive(Debug, Clone)] pub struct CMRawProtoBufMessage { pub action: enums_clientserver::EMsg, pub header: steammessages_base::CMsgProtoBufHeader, @@ -232,6 +234,33 @@ impl CMSession { CMProtoBufMessage::::deserialize(response_raw) } + /// Send a message without a jobid + pub async fn send_notification( + &mut self, + action: enums_clientserver::EMsg, + body: T, + ) -> eyre::Result<()> { + let header = steammessages_base::CMsgProtoBufHeader { + steamid: self.steam_id, + realm: Some(self.realm), + client_sessionid: Some(self.client_session_id), + ..Default::default() + }; + let message = CMProtoBufMessage { + action, + header, + body, + }; + + let serialized = message.serialize()?; + + self.socket + .send(tungstenite::protocol::Message::Binary(serialized)) + .await?; + + Ok(()) + } + /// Whether the current session is authenticated pub fn is_authed(&self) -> bool { self.steam_id.is_some() diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 9850620..34a5100 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -1,4 +1,14 @@ use color_eyre::eyre; +use rand::RngCore; +use vapore_proto::{ + enums_clientserver::EMsg, + steammessages_auth_steamclient::{ + CAuthentication_BeginAuthSessionViaQR_Request, + CAuthentication_BeginAuthSessionViaQR_Response, CAuthentication_DeviceDetails, + EAuthTokenPlatformType, + }, + steammessages_clientserver_login::CMsgClientHello, +}; mod connection; @@ -12,7 +22,66 @@ pub async fn main() -> eyre::Result<()> { let servers = connection::bootstrap_find_servers().await?; log::debug!("Found servers: {:?}", servers); - let session = connection::CMSession::connect(&servers[0]).await?; + let mut session = connection::CMSession::connect(&servers[0]).await?; + + session + .send_notification( + EMsg::k_EMsgClientHello, + CMsgClientHello { + protocol_version: Some(0x1002c), + ..Default::default() + }, + ) + .await?; + + 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(161); + 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 10 + os_type: Some(16), + // Unknown + gaming_device_type: Some(1), + machine_id: Some(machine_id), + ..Default::default() + }), + ..Default::default() + }, + ) + .await?; + + log::debug!("Got response {:#x?}", response); Ok(()) }