1use std::net::Ipv4Addr;
2use std::ptr;
3use std::rc::Rc;
4use std::time::Duration;
5
6use super::*;
7
8macro_rules! matchmaking_servers_callback {
9 (
10 $name:ident;
11 $self:ident;
12 ($($additional_name:ident : $additional_type:ty where $additional_content:block),*);
13 $(
14 $fn_name:ident($clear_after_call:tt): ( $( $fn_arg_name:ident: $cpp_fn_arg:ty => $rust_fn_arg:ty where $normalize:tt ),* )
15 ),*
16 ) => {
17 paste::item! {
18 $(
19 #[allow(unused_variables)]
20 extern "C" fn [<$name:lower _ $fn_name _virtual>]($self: *mut [<$name CallbacksReal>] $(, $fn_arg_name: $cpp_fn_arg)*) {
21 unsafe {
22 $(
23 #[allow(unused_parens)]
24 let [<$fn_arg_name _norm>]: $rust_fn_arg = $normalize;
25 )*
26 let rc_fn = Rc::clone(&(*(*$self).rust_callbacks).$fn_name);
28 (*rc_fn)($([<$fn_arg_name _norm>]),*);
29 $clear_after_call;
30 }
31 }
32 )*
33
34 pub struct [<$name Callbacks>] {
35 $(
36 pub (self) $fn_name: Rc<Box<dyn Fn($($rust_fn_arg),*)>>,
37 )*
38 $(
39 pub (self) $additional_name: $additional_type,
40 )*
41 }
42
43 impl [<$name Callbacks>] {
44 pub fn new($($fn_name: Box<dyn Fn($($rust_fn_arg),*)>),*) -> Self {
46 Self {
47 $($fn_name: Rc::new($fn_name),)*
48 $($additional_name: $additional_content,)*
49 }
50 }
51 }
52
53 #[repr(C)]
54 struct [<$name CallbacksReal>] {
55 pub vtable: *mut [<$name CallbacksVirtual>],
56 pub rust_callbacks: *mut [<$name Callbacks>],
57 }
58
59 #[repr(C)]
60 struct [<$name CallbacksVirtual>] {
61 $(
62 pub $fn_name: extern "C" fn(*mut [<$name CallbacksReal>] $(, $cpp_fn_arg)*)
63 ),*
64 }
65
66 unsafe fn [<create_ $name:lower>](rust_callbacks: [<$name Callbacks>]) -> *mut [<$name CallbacksReal>] {
67 let rust_callbacks = Box::into_raw(Box::new(rust_callbacks));
68 let vtable = Box::into_raw(Box::new([<$name CallbacksVirtual>] {
69 $(
70 $fn_name: [<$name:lower _ $fn_name _virtual>]
71 ),*
72 }));
73 let real = Box::into_raw(Box::new([<$name CallbacksReal>] {
74 vtable,
75 rust_callbacks,
76 }));
77
78 real
79 }
80
81 unsafe fn [<free_ $name:lower>](real: *mut [<$name CallbacksReal>]) {
82 drop(Box::from_raw((*real).rust_callbacks));
83 drop(Box::from_raw((*real).vtable));
84 drop(Box::from_raw(real));
85 }
86 }
87 };
88}
89
90macro_rules! gen_server_list_fn {
91 (
92 $name:ident, $sys_method:ident
93 ) => {
94 pub fn $name<ID: Into<AppId>>(
109 &self,
110 app_id: ID,
111 filters: &HashMap<&str, &str>,
112 callbacks: ServerListCallbacks,
113 ) -> Result<Arc<Mutex<ServerListRequest>>, ()> {
114 let app_id = app_id.into().0;
115 let mut filters = {
116 let mut vec = Vec::with_capacity(filters.len());
117 for i in filters {
118 let key_bytes = i.0.as_bytes();
119 let value_bytes = i.1.as_bytes();
120
121 if key_bytes.len() >= 256 || value_bytes.len() >= 256 {
123 return Err(());
124 }
125
126 let mut key = [0i8; 256];
127 let mut value = [0i8; 256];
128
129 unsafe {
130 key.as_mut_ptr()
131 .copy_from(key_bytes.as_ptr().cast(), key_bytes.len());
132 value
133 .as_mut_ptr()
134 .copy_from(value_bytes.as_ptr().cast(), value_bytes.len());
135 }
136
137 vec.push(sys::MatchMakingKeyValuePair_t {
138 m_szKey: key,
139 m_szValue: value,
140 });
141 }
142 vec.shrink_to_fit();
143
144 vec
145 };
146
147 unsafe {
148 let callbacks = create_serverlist(callbacks);
149
150 let request_arc = ServerListRequest::get(callbacks);
151 let mut request = request_arc.lock().unwrap();
152
153 let handle = sys::$sys_method(
154 self.mms,
155 app_id,
156 &mut filters.as_mut_ptr().cast(),
157 filters.len().try_into().unwrap(),
158 callbacks.cast(),
159 );
160
161 request.mms = self.mms;
162 request.real = callbacks;
163 request.h_req = handle;
164
165 drop(request);
166
167 Ok(request_arc)
168 }
169 }
170 };
171}
172
173pub struct GameServerItem {
174 pub appid: u32,
175 pub players: i32,
176 pub do_not_refresh: bool,
177 pub successful_response: bool,
178 pub have_password: bool,
179 pub secure: bool,
180 pub bot_players: i32,
181 pub ping: Duration,
182 pub max_players: i32,
183 pub server_version: i32,
184 pub steamid: u64,
185 pub last_time_played: Duration,
186 pub addr: Ipv4Addr,
187 pub query_port: u16,
188 pub connection_port: u16,
189 pub game_description: String,
190 pub server_name: String,
191 pub game_dir: String,
192 pub map: String,
193 pub tags: String,
194}
195
196impl GameServerItem {
197 unsafe fn from_ptr(raw: *const sys::gameserveritem_t) -> Self {
198 let raw = *raw;
199 Self {
200 appid: raw.m_nAppID,
201 players: raw.m_nPlayers,
202 bot_players: raw.m_nBotPlayers,
203 ping: Duration::from_millis(raw.m_nPing.try_into().unwrap()),
204 max_players: raw.m_nMaxPlayers,
205 server_version: raw.m_nServerVersion,
206 steamid: raw.m_steamID.m_steamid.m_unAll64Bits,
207
208 do_not_refresh: raw.m_bDoNotRefresh,
209 successful_response: raw.m_bHadSuccessfulResponse,
210 have_password: raw.m_bPassword,
211 secure: raw.m_bSecure,
212
213 addr: Ipv4Addr::from(raw.m_NetAdr.m_unIP),
214 query_port: raw.m_NetAdr.m_usQueryPort,
215 connection_port: raw.m_NetAdr.m_usConnectionPort,
216
217 game_description: CStr::from_ptr(raw.m_szGameDescription.as_ptr())
218 .to_string_lossy()
219 .into_owned(),
220 server_name: CStr::from_ptr(raw.m_szServerName.as_ptr())
221 .to_string_lossy()
222 .into_owned(),
223 game_dir: CStr::from_ptr(raw.m_szGameDir.as_ptr())
224 .to_string_lossy()
225 .into_owned(),
226 map: CStr::from_ptr(raw.m_szMap.as_ptr())
227 .to_string_lossy()
228 .into_owned(),
229 tags: CStr::from_ptr(raw.m_szGameTags.as_ptr())
230 .to_string_lossy()
231 .into_owned(),
232
233 last_time_played: Duration::from_secs(raw.m_ulTimeLastPlayed.into()),
234 }
235 }
236}
237
238matchmaking_servers_callback!(
239 Ping;
240 _self;
241 ();
242 responded({}): (info: *const sys::gameserveritem_t => GameServerItem where { GameServerItem::from_ptr(info) }),
243 failed({ free_ping(_self) }): ()
244);
245
246matchmaking_servers_callback!(
247 PlayerDetails;
248 _self;
249 ();
250 add_player({}): (
251 name: *const std::os::raw::c_char => &CStr where { CStr::from_ptr(name) },
252 score: i32 => i32 where {score},
253 time_played: f32 => f32 where {time_played}
254 ),
255 failed({ free_playerdetails(_self) }): (),
256 refresh_complete({ free_playerdetails(_self) }): ()
257);
258
259matchmaking_servers_callback!(
260 ServerRules;
261 _self;
262 ();
263 add_rule({}): (
264 rule: *const std::os::raw::c_char => &CStr where { CStr::from_ptr(rule) },
265 value: *const std::os::raw::c_char => &CStr where { CStr::from_ptr(value) }
266 ),
267 failed({ free_serverrules(_self) }): (),
268 refresh_complete({ free_serverrules(_self) }): ()
269);
270
271matchmaking_servers_callback!(
272 ServerList;
273 _self;
274 (
275 req: Arc<Mutex<ServerListRequest>> where {
276 Arc::new(Mutex::new(ServerListRequest {
277 h_req: ptr::null_mut(),
278 released: false,
279 mms: ptr::null_mut(),
280 real: ptr::null_mut(),
281 }))
282 }
283 );
284 responded({}): (
285 request: sys::HServerListRequest => Arc<Mutex<ServerListRequest>> where { ServerListRequest::get(_self) },
286 server: i32 => i32 where {server}
287 ),
288 failed({}): (
289 request: sys::HServerListRequest => Arc<Mutex<ServerListRequest>> where { ServerListRequest::get(_self) },
290 server: i32 => i32 where {server}
291 ),
292 refresh_complete({}): (
293 request: sys::HServerListRequest => Arc<Mutex<ServerListRequest>> where { ServerListRequest::get(_self) },
294 response: ServerResponse => ServerResponse where {response}
295 )
296);
297
298#[repr(u32)]
299#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
300pub enum ServerResponse {
301 ServerResponded = 0,
302 ServerFailedToRespond = 1,
303 NoServersListedOnMasterServer = 2,
304}
305
306#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
307pub enum ReleaseError {
308 Released,
311 Refreshing,
315}
316
317pub struct ServerListRequest {
318 pub(self) h_req: sys::HServerListRequest,
319 pub(self) released: bool,
320 pub(self) mms: *mut sys::ISteamMatchmakingServers,
321 pub(self) real: *mut ServerListCallbacksReal,
322}
323
324unsafe impl Send for ServerListRequest {}
325
326impl ServerListRequest {
327 pub(self) unsafe fn get(_self: *mut ServerListCallbacksReal) -> Arc<Mutex<Self>> {
328 let rust_callbacks = &*(*_self).rust_callbacks;
329 Arc::clone(&rust_callbacks.req)
330 }
331
332 pub fn release(&mut self) -> Result<(), ReleaseError> {
346 unsafe {
347 if self.released {
348 return Err(ReleaseError::Released);
349 }
350 if sys::SteamAPI_ISteamMatchmakingServers_IsRefreshing(self.mms, self.h_req) {
351 return Err(ReleaseError::Refreshing);
352 }
353
354 self.released = true;
355 sys::SteamAPI_ISteamMatchmakingServers_ReleaseRequest(self.mms, self.h_req);
356
357 free_serverlist(self.real);
358
359 Ok(())
360 }
361 }
362
363 fn released(&self) -> Result<(), ()> {
364 if self.released {
365 Err(())
366 } else {
367 Ok(())
368 }
369 }
370
371 pub fn get_server_count(&self) -> Result<i32, ()> {
375 unsafe {
376 self.released()?;
377
378 Ok(sys::SteamAPI_ISteamMatchmakingServers_GetServerCount(
379 self.mms, self.h_req,
380 ))
381 }
382 }
383
384 pub fn get_server_details(&self, server: i32) -> Result<GameServerItem, ()> {
388 unsafe {
389 self.released()?;
390
391 let server_item = sys::SteamAPI_ISteamMatchmakingServers_GetServerDetails(
393 self.mms, self.h_req, server,
394 );
395
396 Ok(GameServerItem::from_ptr(server_item))
397 }
398 }
399
400 pub fn refresh_query(&self) -> Result<(), ()> {
404 unsafe {
405 self.released()?;
406
407 sys::SteamAPI_ISteamMatchmakingServers_RefreshQuery(self.mms, self.h_req);
408
409 Ok(())
410 }
411 }
412
413 pub fn refresh_server(&self, server: i32) -> Result<(), ()> {
417 unsafe {
418 self.released()?;
419
420 sys::SteamAPI_ISteamMatchmakingServers_RefreshServer(self.mms, self.h_req, server);
421
422 Ok(())
423 }
424 }
425
426 pub fn is_refreshing(&self) -> Result<bool, ()> {
430 unsafe {
431 self.released()?;
432
433 Ok(sys::SteamAPI_ISteamMatchmakingServers_IsRefreshing(
434 self.mms, self.h_req,
435 ))
436 }
437 }
438}
439
440pub struct MatchmakingServers {
442 pub(crate) mms: *mut sys::ISteamMatchmakingServers,
443 pub(crate) _inner: Arc<Inner>,
444}
445
446impl MatchmakingServers {
447 pub fn ping_server(&self, ip: std::net::Ipv4Addr, port: u16, callbacks: PingCallbacks) {
448 unsafe {
449 let callbacks = create_ping(callbacks);
450
451 sys::SteamAPI_ISteamMatchmakingServers_PingServer(
452 self.mms,
453 ip.into(),
454 port,
455 callbacks.cast(),
456 );
457 }
458 }
459
460 pub fn player_details(
461 &self,
462 ip: std::net::Ipv4Addr,
463 port: u16,
464 callbacks: PlayerDetailsCallbacks,
465 ) {
466 unsafe {
467 let callbacks = create_playerdetails(callbacks);
468
469 sys::SteamAPI_ISteamMatchmakingServers_PlayerDetails(
470 self.mms,
471 ip.into(),
472 port,
473 callbacks.cast(),
474 );
475 }
476 }
477
478 pub fn server_rules(&self, ip: std::net::Ipv4Addr, port: u16, callbacks: ServerRulesCallbacks) {
479 unsafe {
480 let callbacks = create_serverrules(callbacks);
481
482 sys::SteamAPI_ISteamMatchmakingServers_ServerRules(
483 self.mms,
484 ip.into(),
485 port,
486 callbacks.cast(),
487 );
488 }
489 }
490
491 pub fn lan_server_list<ID: Into<AppId>>(
499 &self,
500 app_id: ID,
501 callbacks: ServerListCallbacks,
502 ) -> Arc<Mutex<ServerListRequest>> {
503 unsafe {
504 let app_id = app_id.into().0;
505
506 let callbacks = create_serverlist(callbacks);
507
508 let request_arc = ServerListRequest::get(callbacks);
509 let mut request = request_arc.lock().unwrap();
510
511 let handle = sys::SteamAPI_ISteamMatchmakingServers_RequestLANServerList(
512 self.mms,
513 app_id,
514 callbacks.cast(),
515 );
516
517 request.mms = self.mms;
518 request.real = callbacks;
519 request.h_req = handle;
520
521 drop(request);
522
523 request_arc
524 }
525 }
526
527 gen_server_list_fn!(
528 internet_server_list,
529 SteamAPI_ISteamMatchmakingServers_RequestInternetServerList
530 );
531 gen_server_list_fn!(
532 favorites_server_list,
533 SteamAPI_ISteamMatchmakingServers_RequestFavoritesServerList
534 );
535 gen_server_list_fn!(
536 history_server_list,
537 SteamAPI_ISteamMatchmakingServers_RequestHistoryServerList
538 );
539 gen_server_list_fn!(
540 friends_server_list,
541 SteamAPI_ISteamMatchmakingServers_RequestFriendsServerList
542 );
543}
544
545#[test]
546#[serial_test::serial]
547fn test_internet_servers() {
548 let client = Client::init_app(304930).unwrap();
549
550 let data = std::rc::Rc::new(Mutex::new(0));
551 let data2 = std::rc::Rc::clone(&data);
552 let data3 = std::rc::Rc::clone(&data);
553 let callbacks = ServerListCallbacks::new(
554 Box::new(move |list, server| {
555 let details = list.lock().unwrap().get_server_details(server).unwrap();
556 println!("{} : {}", details.server_name, details.map);
557 *data.lock().unwrap() += 1;
558 }),
559 Box::new(move |_list, _server| {
560 *data2.lock().unwrap() += 1;
561 }),
562 Box::new(move |list, _response| {
563 list.lock().unwrap().release().unwrap();
564 println!("{}", data3.lock().unwrap());
565 }),
566 );
567
568 let mut map = HashMap::new();
569 map.insert("map", "PEI");
570 let _ = client
571 .matchmaking_servers()
572 .internet_server_list(304930, &map, callbacks)
573 .unwrap();
574
575 for _ in 0..2000 {
576 client.run_callbacks();
577 std::thread::sleep(std::time::Duration::from_millis(10));
578 }
579}