steamkit/steam/handlers/steam_game_server/
steam_game_server.rs1use std::fmt;
4use std::net::IpAddr;
5use std::sync::{Arc, Weak};
6
7use async_trait::async_trait;
8use steamkit_protos::steam::{CMsgClientLogOff, CMsgClientLogon, CMsgGsServerType};
9
10use crate::base::client_msg::{ClientMsgProtobuf, IClientMsg};
11use crate::base::generated::steam_language::internal::MsgClientLogon;
12use crate::base::generated::steam_language::{EAccountType, EMsg, EResult, EServerFlags};
13use crate::base::packet_base::IPacketMsg;
14use crate::error::{SteamError, SteamResult};
15use crate::steam::handlers::client_msg_handler::ClientMsgHandler;
16use crate::steam::handlers::steam_user::callbacks::LoggedOnCallback;
17use crate::steam::steam_client::callback_mgr::callback_msg::CallbackMsg;
18use crate::steam::steam_client::steam_client::SteamClient;
19use crate::types::steam_id::SteamID;
20use crate::util::net_helpers::NetHelpers;
21
22use super::callbacks::{StatusReplyCallback, TicketAuthCallback};
23
24#[derive(Clone, Debug, Default)]
26pub struct LogOnDetails {
27 pub token: Option<String>,
29 pub app_id: u32,
31}
32
33#[derive(Clone, Debug)]
35pub struct StatusDetails {
36 pub app_id: u32,
38 pub server_flags: EServerFlags,
40 pub game_directory: Option<String>,
42 pub address: Option<IpAddr>,
44 pub port: u32,
46 pub query_port: u32,
48 pub version: Option<String>,
50}
51
52impl Default for StatusDetails {
53 fn default() -> Self {
54 Self {
55 app_id: 0,
56 server_flags: EServerFlags::None,
57 game_directory: None,
58 address: None,
59 port: 0,
60 query_port: 0,
61 version: None,
62 }
63 }
64}
65
66pub struct SteamGameServer {
68 client: Weak<SteamClient>,
69}
70
71impl fmt::Debug for SteamGameServer {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 f.debug_struct("SteamGameServer").finish_non_exhaustive()
74 }
75}
76
77impl SteamGameServer {
78 pub fn new(client: &Arc<SteamClient>) -> Self {
80 Self {
81 client: Arc::downgrade(client),
82 }
83 }
84
85 fn client(&self) -> SteamResult<Arc<SteamClient>> {
87 self.client
88 .upgrade()
89 .ok_or_else(|| SteamError::Connection("SteamClient has been dropped".to_string()))
90 }
91
92 pub fn expect_disconnection(&self) -> bool {
94 self.client().map(|client| client.expect_disconnection()).unwrap_or(false)
95 }
96
97 pub fn log_on(&self, details: &LogOnDetails) -> SteamResult<()> {
100 let client = self.client()?;
101
102 let token = details.token.as_deref().unwrap_or_default();
103 if token.is_empty() {
104 return Err(SteamError::InvalidArgument(
105 "LogOn requires a game server token to be set in 'details'.".to_string(),
106 ));
107 }
108
109 if !client.is_connected() {
110 client.post_callback(Box::new(LoggedOnCallback::from_result(
111 EResult::NoConnection,
112 )));
113 return Ok(());
114 }
115
116 let universe = client.configuration().universe();
117 let gs_id = SteamID::from_parts_instanced(EAccountType::GameServer, universe, 0, 0);
118
119 let mut logon = ClientMsgProtobuf::<CMsgClientLogon>::new(EMsg::ClientLogonGameServer);
120
121 logon.set_session_id(0);
122 logon.proto_header_mut().steamid = Some(gs_id.convert_to_u64());
126
127 let local_ip = client.local_ip().ok_or_else(|| {
128 SteamError::Connection("the local IP address is not known yet".to_string())
129 })?;
130 let mut obfuscated_ip = NetHelpers::get_msg_ip_address(&local_ip);
131 NetHelpers::obfuscate_private_ip(&mut obfuscated_ip);
132 logon.body.obfuscated_private_ip = Some(obfuscated_ip);
133
134 logon.body.protocol_version = Some(MsgClientLogon::CURRENT_PROTOCOL);
135 logon.body.client_os_type = Some(crate::util::utils::get_os_type() as u32);
136 logon.body.game_server_app_id = Some(details.app_id as i32);
137 logon.body.machine_id = crate::util::hardware_utils::get_machine_id(
138 client.configuration().machine_info_provider(),
139 );
140 logon.body.game_server_token = Some(token.to_string());
141
142 client.send(&logon)?;
143
144 Ok(())
145 }
146
147 pub fn log_on_anonymous(&self, app_id: u32) -> SteamResult<()> {
152 let client = self.client()?;
153
154 if !client.is_connected() {
155 client.post_callback(Box::new(LoggedOnCallback::from_result(
156 EResult::NoConnection,
157 )));
158 return Ok(());
159 }
160
161 let universe = client.configuration().universe();
162 let gs_id = SteamID::from_parts_instanced(EAccountType::AnonGameServer, universe, 0, 0);
163
164 let mut logon = ClientMsgProtobuf::<CMsgClientLogon>::new(EMsg::ClientLogon);
165
166 logon.set_session_id(0);
167 logon.proto_header_mut().steamid = Some(gs_id.convert_to_u64());
171
172 let local_ip = client.local_ip().ok_or_else(|| {
173 SteamError::Connection("the local IP address is not known yet".to_string())
174 })?;
175 let mut obfuscated_ip = NetHelpers::get_msg_ip_address(&local_ip);
176 NetHelpers::obfuscate_private_ip(&mut obfuscated_ip);
177 logon.body.obfuscated_private_ip = Some(obfuscated_ip);
178
179 logon.body.protocol_version = Some(MsgClientLogon::CURRENT_PROTOCOL);
180 logon.body.client_os_type = Some(crate::util::utils::get_os_type() as u32);
181 logon.body.game_server_app_id = Some(app_id as i32);
182 logon.body.machine_id = crate::util::hardware_utils::get_machine_id(
183 client.configuration().machine_info_provider(),
184 );
185
186 client.send(&logon)?;
187
188 Ok(())
189 }
190
191 pub fn log_off(&self) -> SteamResult<()> {
194 let client = self.client()?;
195
196 client.set_expect_disconnection(true);
197
198 let log_off = ClientMsgProtobuf::<CMsgClientLogOff>::new(EMsg::ClientLogOff);
199 client.send(&log_off)?;
200
201 Ok(())
202 }
203
204 pub fn send_status(&self, details: &StatusDetails) -> SteamResult<()> {
207 let client = self.client()?;
208
209 let game_ip_address = match details.address {
210 Some(IpAddr::V4(v4)) => Some(u32::from_be_bytes(v4.octets())),
211 Some(IpAddr::V6(_)) => {
212 return Err(SteamError::InvalidArgument(
213 "Only IPv4 addresses are supported.".to_string(),
214 ))
215 }
216 None => None,
217 };
218
219 let mut status = ClientMsgProtobuf::<CMsgGsServerType>::new(EMsg::GSServerType);
220
221 status.body.app_id_served = Some(details.app_id);
222 status.body.flags = Some(details.server_flags.value() as u32);
223 status.body.game_dir = details.game_directory.clone();
224 status.body.game_port = Some(details.port);
225 status.body.game_query_port = Some(details.query_port);
226 status.body.game_version = details.version.clone();
227 status.body.deprecated_game_ip_address = game_ip_address;
228
229 client.send(&status)?;
230
231
232 Ok(())
233 }
234}
235
236#[async_trait]
237impl ClientMsgHandler for SteamGameServer {
238 async fn handle_msg(
239 &self,
240 client: &SteamClient,
241 packet_msg: &dyn IPacketMsg,
242 ) -> SteamResult<()> {
243 let callback: Option<Box<dyn CallbackMsg>> = match packet_msg.msg_type() {
244 EMsg::GSStatusReply => Some(Box::new(StatusReplyCallback::from_packet(packet_msg)?)),
245 EMsg::ClientTicketAuthComplete => {
246 Some(Box::new(TicketAuthCallback::from_packet(packet_msg)?))
247 }
248 _ => None,
250 };
251
252 if let Some(callback) = callback {
253 client.post_callback(callback);
254 }
255
256 Ok(())
257 }
258}