Start work on connection
This commit is contained in:
parent
a0732e3489
commit
57125d541e
1351
Cargo.lock
generated
1351
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -10,6 +10,8 @@ members = [
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
color-eyre = "0.6"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
keyvalues-serde = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
protobuf = "3.5.1"
|
||||||
|
|
|
@ -4,5 +4,14 @@ edition = "2021"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-tungstenite = { version = "0.27.0", features = ["tokio-rustls-native-certs"] }
|
||||||
|
color-eyre.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
futures = "0.3.30"
|
||||||
|
keyvalues-serde.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
protobuf.workspace = true
|
||||||
|
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"]}
|
||||||
|
vapore-proto.path = "../proto"
|
||||||
|
|
145
daemon/src/connection.rs
Normal file
145
daemon/src/connection.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use async_tungstenite::{
|
||||||
|
tokio::{connect_async, ConnectStream},
|
||||||
|
tungstenite, WebSocketStream,
|
||||||
|
};
|
||||||
|
use color_eyre::eyre::WrapErr;
|
||||||
|
use color_eyre::eyre::{self, bail, OptionExt};
|
||||||
|
use futures::{SinkExt as _, TryStreamExt};
|
||||||
|
use protobuf::Enum as _;
|
||||||
|
use protobuf::Message as _;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use vapore_proto::{enums_clientserver, steammessages_base};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct GetCMListResult<'a> {
|
||||||
|
// No need to check servers, we're only implementing websockets
|
||||||
|
serverlist_websockets: BTreeMap<u32, &'a str>,
|
||||||
|
result: u32,
|
||||||
|
message: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn bootstrap_find_servers() -> eyre::Result<Vec<String>> {
|
||||||
|
// TODO: Use GetCMListForConnect, seems to be new API
|
||||||
|
let result_text = reqwest::get("https://api.steampowered.com/ISteamDirectory/GetCMList/v1/?cellid=0&maxcount=10&format=vdf").await?.text().await?;
|
||||||
|
let result: GetCMListResult = keyvalues_serde::from_str(&result_text)?;
|
||||||
|
|
||||||
|
if result.result != 1 {
|
||||||
|
eyre::bail!(
|
||||||
|
"GetCMList returned bad result {} wtih message {}",
|
||||||
|
result.result,
|
||||||
|
result.message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result
|
||||||
|
.serverlist_websockets
|
||||||
|
.values()
|
||||||
|
.map(|host| format!("wss://{}/cmsocket/", host))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CMProtoBufMessage<'a, T: protobuf::Message> {
|
||||||
|
pub action: enums_clientserver::EMsg,
|
||||||
|
pub header: &'a steammessages_base::CMsgProtoBufHeader,
|
||||||
|
pub body: &'a T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: protobuf::Message> CMProtoBufMessage<'_, T> {
|
||||||
|
pub fn serialize(&self) -> eyre::Result<Vec<u8>> {
|
||||||
|
// 4 bytes for type, 4 bytes for header length, then header and body
|
||||||
|
// No alignment requirements
|
||||||
|
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.header.cached_size().to_le_bytes());
|
||||||
|
self.header.write_to_vec(&mut out)?;
|
||||||
|
self.body.write_to_vec(&mut out)?;
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CMSession {
|
||||||
|
socket: WebSocketStream<ConnectStream>,
|
||||||
|
/// Steam ID of current user. When set to None we are not logged in
|
||||||
|
steam_id: Option<u64>,
|
||||||
|
/// Next jobid to use for messages that start a "job"
|
||||||
|
next_jobid: u64,
|
||||||
|
/// Realm we're connecting to. AIUI this corresponds to account universe.
|
||||||
|
/// Should normally be 1 for Public
|
||||||
|
realm: u32,
|
||||||
|
/// Session ID for our socket, assigned by the server after login.
|
||||||
|
/// Should be 0 before we login
|
||||||
|
client_session_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CMSession {
|
||||||
|
pub async fn connect(server: &str) -> eyre::Result<Self> {
|
||||||
|
let (socket, _) = connect_async(server)
|
||||||
|
.await
|
||||||
|
.wrap_err("Connecting to Steam server")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
socket,
|
||||||
|
steam_id: None,
|
||||||
|
next_jobid: 0,
|
||||||
|
realm: 1,
|
||||||
|
client_session_id: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn call_service_method<Request: protobuf::Message, Response: protobuf::Message>(
|
||||||
|
&mut self,
|
||||||
|
method: String,
|
||||||
|
body: &Request,
|
||||||
|
) -> eyre::Result<Response> {
|
||||||
|
log::trace!("Calling service method `{}`", method);
|
||||||
|
|
||||||
|
let action = if self.is_authed() {
|
||||||
|
enums_clientserver::EMsg::k_EMsgServiceMethodCallFromClient
|
||||||
|
} else {
|
||||||
|
enums_clientserver::EMsg::k_EMsgServiceMethodCallFromClientNonAuthed
|
||||||
|
};
|
||||||
|
|
||||||
|
let jobid = self.next_jobid;
|
||||||
|
self.next_jobid += 1;
|
||||||
|
|
||||||
|
let header = steammessages_base::CMsgProtoBufHeader {
|
||||||
|
steamid: self.steam_id,
|
||||||
|
target_job_name: Some(method),
|
||||||
|
realm: Some(self.realm),
|
||||||
|
client_sessionid: Some(self.client_session_id),
|
||||||
|
jobid_source: Some(jobid),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = CMProtoBufMessage {
|
||||||
|
action,
|
||||||
|
header: &header,
|
||||||
|
body,
|
||||||
|
};
|
||||||
|
let serialized = message.serialize()?;
|
||||||
|
self.socket
|
||||||
|
.send(tungstenite::protocol::Message::Binary(serialized))
|
||||||
|
.await?;
|
||||||
|
let response = self
|
||||||
|
.socket
|
||||||
|
.try_next()
|
||||||
|
.await?
|
||||||
|
.ok_or_eyre("No message recieved")?;
|
||||||
|
|
||||||
|
let tungstenite::protocol::Message::Binary(response_binary) = response else {
|
||||||
|
bail!("Message recieved was not binary")
|
||||||
|
};
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the current session is authenticated
|
||||||
|
pub fn is_authed(&self) -> bool {
|
||||||
|
self.steam_id.is_some()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,18 @@
|
||||||
pub fn main() {
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
mod connection;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() -> eyre::Result<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
log::info!("Starting vapored");
|
log::info!("Starting vapored");
|
||||||
|
|
||||||
|
let servers = connection::bootstrap_find_servers().await?;
|
||||||
|
log::debug!("Found servers: {:?}", servers);
|
||||||
|
|
||||||
|
let session = connection::CMSession::connect(&servers[0]).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ edition = "2021"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
protobuf = "3.5.1"
|
protobuf.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
protobuf-codegen = "3.5.1"
|
protobuf-codegen = "3.5.1"
|
||||||
|
|
|
@ -9,9 +9,8 @@ pub fn main() -> io::Result<()> {
|
||||||
files.push(path);
|
files.push(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protobuf_codegen::Codegen::new()
|
protobuf_codegen::Codegen::new()
|
||||||
.includes(&["proto/steam", "proto"])
|
.includes(["proto/steam", "proto"])
|
||||||
.inputs(files)
|
.inputs(files)
|
||||||
.cargo_out_dir("gen")
|
.cargo_out_dir("gen")
|
||||||
.run_from_script();
|
.run_from_script();
|
||||||
|
|
Loading…
Reference in a new issue