vapour-protocol
Rust implementation of the Steam client protocol for building native Steam applications.
vapour-protocol speaks directly to Steam CM servers over WebSocket and exposes higher-level
Rust APIs for client-style Steam features — authentication, friends, chat, and your game library —
without depending on the Steam client or Web API key. It was built for, and is used by,
Vapour, but it is a standalone crate you can use on its own.
Features
- Steam authentication: QR, username/password (with Steam Guard), and refresh-token reuse.
- CM server discovery and WebSocket transport.
- Friends/persona state and real-time friend presence events.
- 1-on-1 friend chat: send, receive, typing indicators, and history.
- Owned/recently-played library loading through protocol messages.
- Per-user achievements and PICS app metadata (type, install dir, launch options).
- Service method calls for authenticated unified Steam services.
Status
This crate is usable and evolving toward a stable public API. It is 0.x: expect breaking changes
between minor releases while protocol coverage grows. Talking to Steam requires a real Steam account,
and you are responsible for using it in line with the Steam Subscriber Agreement.
Requirements
- Rust 1.85+ (the crate uses edition 2024).
- A Tokio runtime — the API is async.
- A real Steam account to authenticate against.
Install
vapour-protocol pulls in tokio, so add it too if you don't already depend on it:
Quickstart
Log in with a QR code, then load your owned games and echo any incoming chat message. Every type used here is re-exported from the crate root.
use mpsc;
use ;
async
Session model
A SteamClient has three stages:
SteamClient::new()— create the client.begin_auth(method)— connect and start an auth flow. Returns a receiver ofAuthEvents; wait forAuthEvent::Success(LoggedOn).run(commands, events)— take over the connection. You drive the session by sendingRunCommands on thecommandschannel and reacting toFriendsEvents on theeventschannel.runsets your status online, requests friend data as it arrives, and resolves until the connection closes.
Commands you send (RunCommand)
| Command | Effect | Result event |
|---|---|---|
SetPersonaState(PersonaState) |
Set your online status | — |
RequestFriendData(Vec<u64>) |
Request persona data for SteamIDs | PersonaStates |
GetLibrary |
Load owned + recently-played games | RecentlyPlayedGames, then OwnedGames |
GetPlayerAchievements(u32) |
Achievements for an appid | PlayerAchievements |
SendMessage { steamid, message } |
Send a 1-on-1 chat message | MessageSent |
SendTyping { steamid } |
Send a typing indicator (best-effort) | — |
GetRecentMessages { steamid } |
Fetch recent chat history | RecentMessages |
Events you receive (FriendsEvent)
| Event | Meaning |
|---|---|
FriendsList(Vec<Friend>) |
Your friends list (pushed after login) |
PersonaStates(Vec<Persona>) |
Presence/profile updates |
RecentlyPlayedGames(Vec<ProtocolGame>) |
Recently-played subset of the library |
OwnedGames(Vec<ProtocolGame>) |
Full owned-game catalog |
PlayerAchievements { appid, achievements } |
Achievements for one app |
IncomingMessage(ChatMessage) |
A 1-on-1 message arrived |
MessageSent(ChatMessage) |
Your send confirmed (with server timestamp) |
TypingNotification { steamid } |
A friend is typing |
RecentMessages { steamid, messages } |
History for one conversation |
Authentication
begin_auth takes one of three AuthMethods and reports progress as AuthEvents
(QrChallenge, GuardRequired, Success, Failure).
-
AuthMethod::Qr— emitsQrChallenge(url); render it as a QR code and scan it in the Steam Mobile app. -
AuthMethod::Credentials { account, password }— may emitGuardRequired(kind). ForEmailCode/DeviceCode, collect the code and callsubmit_guard_code; forDeviceConfirmation, approve the prompt in the Steam Mobile app (no code needed):let mut auth = client .begin_auth .await?; while let Some = auth.recv.await -
AuthMethod::RefreshToken(token)— reuse therefresh_tokenfrom a previousLoggedOnto sign in non-interactively. Pair it withset_account_name_hintif you have the account name. Store refresh tokens securely; they grant access to the account.
Development
Run the same checks as CI before opening a PR:
If you are developing vapour-protocol alongside Vapour, point Vapour at your local checkout with a
path dependency so changes to both land together:
= { = "../vapour-protocol" }
Release steps are documented in how_to_release.md.
License
This repository is licensed under MIT.