Skip to main content

steamkit/steam/handlers/steam_master_server/
steam_master_server.rs

1//! Querying the game master server for server lists.
2
3use std::net::IpAddr;
4use std::sync::{Arc, Weak};
5
6use async_trait::async_trait;
7use steamkit_protos::steam::CMsgClientGmsServerQuery;
8
9use crate::base::client_msg::{ClientMsgProtobuf, IClientMsg};
10use crate::base::generated::steam_language::{EMsg, ERegionCode};
11use crate::base::packet_base::IPacketMsg;
12use crate::error::{SteamError, SteamResult};
13use crate::steam::handlers::client_msg_handler::ClientMsgHandler;
14use crate::steam::steam_client::callback_mgr::callback_msg::CallbackMsg;
15use crate::steam::steam_client::steam_client::SteamClient;
16use crate::util::net_helpers::NetHelpers;
17
18use super::callbacks::QueryCallback;
19
20/// What to query the master server for.
21#[derive(Clone, Debug)]
22pub struct QueryDetails {
23    /// The app id used when querying servers.
24    pub app_id: u32,
25
26    /// The query filter. See
27    /// <https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol>
28    /// for the format.
29    pub filter: Option<String>,
30
31    /// The region to return servers from.
32    pub region: ERegionCode,
33
34    /// An IP address to GeoIP-locate, to get servers close to it.
35    pub geo_located_ip: Option<IpAddr>,
36
37    /// The maximum number of servers to return.
38    pub max_servers: u32,
39}
40
41impl Default for QueryDetails {
42    fn default() -> Self {
43        Self {
44            app_id: 0,
45            filter: None,
46            // The default region.
47            region: ERegionCode::USEast,
48            geo_located_ip: None,
49            max_servers: 0,
50        }
51    }
52}
53
54/// Queries the game master server for server lists.
55pub struct SteamMasterServer {
56    client: Weak<SteamClient>,
57}
58
59impl std::fmt::Debug for SteamMasterServer {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        f.debug_struct("SteamMasterServer").finish()
62    }
63}
64
65impl SteamMasterServer {
66    /// Creates the handler and stores a weak back-reference to the client.
67    pub fn new(client: &Arc<SteamClient>) -> Self {
68        Self {
69            client: Arc::downgrade(client),
70        }
71    }
72
73    /// Upgrades the weak client reference.
74    fn client(&self) -> SteamResult<Arc<SteamClient>> {
75        self.client
76            .upgrade()
77            .ok_or_else(|| SteamError::Connection("SteamClient has been dropped".to_string()))
78    }
79
80    /// Requests a list of servers from the game master server.
81    pub async fn server_query(&self, details: &QueryDetails) -> SteamResult<QueryCallback> {
82        let client = self.client()?;
83
84        let job_id = client.get_next_job_id();
85        let job = client.start_job::<QueryCallback>(job_id);
86
87        let mut query =
88            ClientMsgProtobuf::<CMsgClientGmsServerQuery>::new(EMsg::ClientGMSServerQuery);
89        query.set_source_job_id(job_id);
90
91        query.body.app_id = Some(details.app_id);
92
93        if let Some(geo_located_ip) = &details.geo_located_ip {
94            query.body.geo_location_ip = Some(NetHelpers::get_ip_address_as_uint(geo_located_ip)?);
95        }
96
97        query.body.filter_text = details.filter.clone();
98        query.body.region_code = Some(u32::from(details.region.value()));
99        query.body.max_servers = Some(details.max_servers);
100
101        client.send(&query)?;
102
103        job.wait().await
104    }
105}
106
107#[async_trait]
108impl ClientMsgHandler for SteamMasterServer {
109    async fn handle_msg(
110        &self,
111        client: &SteamClient,
112        packet_msg: &dyn IPacketMsg,
113    ) -> SteamResult<()> {
114        let callback: Option<Box<dyn CallbackMsg>> = match packet_msg.msg_type() {
115            EMsg::GMSClientServerQueryResponse => {
116                Some(Box::new(QueryCallback::from_packet(packet_msg)?))
117            }
118            // Not a message we handle.
119            _ => None,
120        };
121
122        if let Some(callback) = callback {
123            client.post_callback(callback);
124        }
125
126        Ok(())
127    }
128}