mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
[Protocol] Add quake protocols. (#35)
* [Protocol] Initial packet receive implementation * [Protocol] Add key extraction * [Protocol] Fix new Ipv4Addr query address and get string with unended * [Protocol] Properly parse the received data * [Protocol] Add parse players * [Protocol] Add bots * [Protocol] Extract into functions * [Protocol] Remove quotes from player name * [Protocol] Add two and three files * [Protocol] Make quake queries very modular * [Protocol] Remove the need of a client instance * [Protocol] Revesed if statement * [Protocol] Apply clippy fixes and replace String by &str in get send header * [Protocol] Add one and three implementations * [Protocol] Add quake2 and quake3 to master_querant * [Protocol] Fix Q2 implementation * [Protocol] Change from Ipv4Addr to IpAddr * [Protocol] Fix Q3 response header * [Protocol] Fix Q3 response * [Crate] Add Q1, 2 and 3 to changelog and protocols * [Protocol] Extract client into separate file and add some docs
This commit is contained in:
parent
3dbc6498ed
commit
d302d1173f
11 changed files with 339 additions and 8 deletions
|
|
@ -5,6 +5,7 @@ Who knows what the future holds...
|
||||||
Protocols:
|
Protocols:
|
||||||
- Valve:
|
- Valve:
|
||||||
1. Added standard and serde derives to `GatheringSettings`.
|
1. Added standard and serde derives to `GatheringSettings`.
|
||||||
|
- Quake 1, 2 and 3 support.
|
||||||
|
|
||||||
### Breaking:
|
### Breaking:
|
||||||
- Every function that used `&str` for the address has been changed to `&IpAddr` (thanks [@Douile](https://github.com/Douile) for the re-re-write).
|
- Every function that used `&str` for the address has been changed to `&IpAddr` (thanks [@Douile](https://github.com/Douile) for the re-re-write).
|
||||||
|
|
|
||||||
11
PROTOCOLS.md
11
PROTOCOLS.md
|
|
@ -1,11 +1,12 @@
|
||||||
A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft).
|
A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft).
|
||||||
|
|
||||||
# Supported protocols:
|
# Supported protocols:
|
||||||
| Name | For | Proprietary? | Documentation reference | Notes |
|
| Name | For | Proprietary? | Documentation reference | Notes |
|
||||||
|----------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|----------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | In some cases, the players details query might contain some 0-length named players. Multi-packet decompression not tested. |
|
| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | In some cases, the players details query might contain some 0-length named players. Multi-packet decompression not tested. |
|
||||||
| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping) <br> Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | |
|
| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping) <br> Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | |
|
||||||
| GameSpy | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy3.js) | These protocols are not really standardized, gamedig tries to get the most common fields amongst its supported games, if there are parsing problems, use the `query_vars` function. |
|
| GameSpy | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy3.js) | These protocols are not really standardized, gamedig tries to get the most common fields amongst its supported games, if there are parsing problems, use the `query_vars` function. |
|
||||||
|
| Quake | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake3.js) | |
|
||||||
|
|
||||||
## Planned to add support:
|
## Planned to add support:
|
||||||
_
|
_
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use gamedig::protocols::gamespy;
|
use gamedig::protocols::{gamespy, quake};
|
||||||
use gamedig::protocols::minecraft::LegacyGroup;
|
use gamedig::protocols::minecraft::LegacyGroup;
|
||||||
use gamedig::protocols::valve;
|
use gamedig::protocols::valve;
|
||||||
use gamedig::protocols::valve::Engine;
|
use gamedig::protocols::valve::Engine;
|
||||||
|
|
@ -123,6 +123,9 @@ fn main() -> GDResult<()> {
|
||||||
"_gamespy3_vars" => println!("{:#?}", gamespy::three::query_vars(ip, port.unwrap(), None)),
|
"_gamespy3_vars" => println!("{:#?}", gamespy::three::query_vars(ip, port.unwrap(), None)),
|
||||||
"ffow" => println!("{:#?}", ffow::query(ip, port)),
|
"ffow" => println!("{:#?}", ffow::query(ip, port)),
|
||||||
"cw" => println!("{:#?}", cw::query(ip, port)),
|
"cw" => println!("{:#?}", cw::query(ip, port)),
|
||||||
|
"_quake1" => println!("{:#?}", quake::one::query(ip, port.unwrap(), None)),
|
||||||
|
"_quake2" => println!("{:#?}", quake::two::query(ip, port.unwrap(), None)),
|
||||||
|
"_quake3" => println!("{:#?}", quake::three::query(ip, port.unwrap(), None)),
|
||||||
_ => panic!("Undefined game: {}", args[1]),
|
_ => panic!("Undefined game: {}", args[1]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,13 @@ impl Bufferer {
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_string_utf8(&mut self) -> GDResult<String> {
|
fn get_string_utf8_until(&mut self, until: u8) -> GDResult<String> {
|
||||||
let sub_buf = self.remaining_data();
|
let sub_buf = self.remaining_data();
|
||||||
if sub_buf.is_empty() {
|
if sub_buf.is_empty() {
|
||||||
return Err(PacketUnderflow);
|
return Err(PacketUnderflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
let first_null_position = sub_buf.iter().position(|&x| x == 0).ok_or(PacketBad)?;
|
let first_null_position = sub_buf.iter().position(|&x| x == until).ok_or(PacketBad)?;
|
||||||
let value = std::str::from_utf8(&sub_buf[.. first_null_position])
|
let value = std::str::from_utf8(&sub_buf[.. first_null_position])
|
||||||
.map_err(|_| PacketBad)?
|
.map_err(|_| PacketBad)?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -108,6 +108,14 @@ impl Bufferer {
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_string_utf8(&mut self) -> GDResult<String> {
|
||||||
|
self.get_string_utf8_until(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_string_utf8_newline(&mut self) -> GDResult<String> {
|
||||||
|
self.get_string_utf8_until(10)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_string_utf8_optional(&mut self) -> GDResult<String> {
|
pub fn get_string_utf8_optional(&mut self) -> GDResult<String> {
|
||||||
match self.get_string_utf8() {
|
match self.get_string_utf8() {
|
||||||
Ok(data) => Ok(data),
|
Ok(data) => Ok(data),
|
||||||
|
|
@ -168,6 +176,8 @@ impl Bufferer {
|
||||||
|
|
||||||
pub fn remaining_length(&self) -> usize { self.data_length() - self.position }
|
pub fn remaining_length(&self) -> usize { self.data_length() - self.position }
|
||||||
|
|
||||||
|
pub fn is_remaining_empty(&self) -> bool { self.remaining_length() == 0 }
|
||||||
|
|
||||||
pub fn as_endianess(&self, endianess: Endianess) -> Self {
|
pub fn as_endianess(&self, endianess: Endianess) -> Self {
|
||||||
Bufferer {
|
Bufferer {
|
||||||
data: self.data.clone(),
|
data: self.data.clone(),
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,5 @@ pub mod minecraft;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries)
|
/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries)
|
||||||
pub mod valve;
|
pub mod valve;
|
||||||
|
/// Reference: [node-GameDig](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js)
|
||||||
|
pub mod quake;
|
||||||
|
|
|
||||||
115
src/protocols/quake/client.rs
Normal file
115
src/protocols/quake/client.rs
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::slice::Iter;
|
||||||
|
use crate::bufferer::{Bufferer, Endianess};
|
||||||
|
use crate::{GDError, GDResult};
|
||||||
|
use crate::protocols::quake::types::Response;
|
||||||
|
use crate::protocols::types::TimeoutSettings;
|
||||||
|
use crate::socket::{Socket, UdpSocket};
|
||||||
|
|
||||||
|
pub(crate) trait QuakeClient {
|
||||||
|
type Player;
|
||||||
|
|
||||||
|
fn get_send_header<'a>() -> &'a str;
|
||||||
|
fn get_response_header<'a>() -> &'a[u8];
|
||||||
|
fn parse_player_string(data: Iter<&str>) -> GDResult<Self::Player>;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data<Client: QuakeClient>(address: &IpAddr, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Bufferer> {
|
||||||
|
let mut socket = UdpSocket::new(address, port)?;
|
||||||
|
socket.apply_timeout(timeout_settings)?;
|
||||||
|
|
||||||
|
socket.send(&[&[0xFF, 0xFF, 0xFF, 0xFF], Client::get_send_header().as_bytes(), &[0x00]].concat())?;
|
||||||
|
|
||||||
|
let data = socket.receive(None)?;
|
||||||
|
let mut bufferer = Bufferer::new_with_data(Endianess::Little, &data);
|
||||||
|
|
||||||
|
if bufferer.get_u32()? != 4294967295 {
|
||||||
|
return Err(GDError::PacketBad);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_header = Client::get_response_header();
|
||||||
|
if !bufferer.remaining_data().starts_with(response_header) {
|
||||||
|
Err(GDError::PacketBad)?
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferer.move_position_ahead(response_header.len());
|
||||||
|
|
||||||
|
Ok(bufferer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_server_values(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String>> {
|
||||||
|
let data = bufferer.get_string_utf8_newline()?;
|
||||||
|
let mut data_split = data.split('\\').collect::<Vec<&str>>();
|
||||||
|
if let Some(first) = data_split.first() {
|
||||||
|
if first == &"" {
|
||||||
|
data_split.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let values = data_split.chunks(2);
|
||||||
|
|
||||||
|
let mut vars: HashMap<String, String> = HashMap::new();
|
||||||
|
for data in values {
|
||||||
|
let key = data.first();
|
||||||
|
let value = data.get(1);
|
||||||
|
|
||||||
|
if let Some(k) = key {
|
||||||
|
if let Some(v) = value {
|
||||||
|
vars.insert(k.to_string(), v.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_players<Client: QuakeClient>(bufferer: &mut Bufferer) -> GDResult<Vec<Client::Player>> {
|
||||||
|
let mut players: Vec<Client::Player> = Vec::new();
|
||||||
|
|
||||||
|
while !bufferer.is_remaining_empty() {
|
||||||
|
let data = bufferer.get_string_utf8_newline()?;
|
||||||
|
let data_split = data.split(' ').collect::<Vec<&str>>();
|
||||||
|
let data_iter = data_split.iter();
|
||||||
|
|
||||||
|
players.push(Client::parse_player_string(data_iter)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(players)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn client_query<Client: QuakeClient>(address: &IpAddr, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response<Client::Player>> {
|
||||||
|
let mut bufferer = get_data::<Client>(address, port, timeout_settings)?;
|
||||||
|
|
||||||
|
let mut server_vars = get_server_values(&mut bufferer)?;
|
||||||
|
let players = get_players::<Client>(&mut bufferer)?;
|
||||||
|
|
||||||
|
Ok(Response {
|
||||||
|
name: server_vars.remove("hostname")
|
||||||
|
.or(server_vars.remove("sv_hostname"))
|
||||||
|
.ok_or(GDError::PacketBad)?,
|
||||||
|
map: server_vars.remove("mapname")
|
||||||
|
.ok_or(GDError::PacketBad)?,
|
||||||
|
players_online: players.len() as u8,
|
||||||
|
players_maximum: server_vars.remove("maxclients")
|
||||||
|
.or(server_vars.remove("sv_maxclients"))
|
||||||
|
.ok_or(GDError::PacketBad)?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| GDError::TypeParse)?,
|
||||||
|
has_password: server_vars.remove("needpass")
|
||||||
|
.or(server_vars.remove("g_needpass"))
|
||||||
|
.ok_or(GDError::PacketBad)? == "1",
|
||||||
|
players,
|
||||||
|
frag_limit: server_vars.remove("fraglimit")
|
||||||
|
.ok_or(GDError::PacketBad)?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| GDError::TypeParse)?,
|
||||||
|
time_limit: server_vars.remove("timelimit")
|
||||||
|
.ok_or(GDError::PacketBad)?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| GDError::TypeParse)?,
|
||||||
|
version: server_vars.remove("version")
|
||||||
|
.ok_or(GDError::PacketBad)?,
|
||||||
|
unused_entries: server_vars,
|
||||||
|
})
|
||||||
|
}
|
||||||
10
src/protocols/quake/mod.rs
Normal file
10
src/protocols/quake/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
pub mod one;
|
||||||
|
pub mod two;
|
||||||
|
pub mod three;
|
||||||
|
|
||||||
|
/// All types used by the implementation.
|
||||||
|
pub mod types;
|
||||||
|
pub use types::*;
|
||||||
|
|
||||||
|
mod client;
|
||||||
77
src/protocols/quake/one.rs
Normal file
77
src/protocols/quake/one.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::slice::Iter;
|
||||||
|
use crate::{GDError, GDResult};
|
||||||
|
use crate::protocols::quake::Response;
|
||||||
|
use crate::protocols::quake::client::{QuakeClient, client_query};
|
||||||
|
use crate::protocols::types::TimeoutSettings;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Quake 1 player data.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct Player {
|
||||||
|
/// Player's server id.
|
||||||
|
pub id: u8,
|
||||||
|
pub score: u8,
|
||||||
|
pub time: u8,
|
||||||
|
pub ping: u8,
|
||||||
|
pub name: String,
|
||||||
|
pub skin: String,
|
||||||
|
pub color_primary: u8,
|
||||||
|
pub color_secondary: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct QuakeOne;
|
||||||
|
impl QuakeClient for QuakeOne {
|
||||||
|
type Player = Player;
|
||||||
|
|
||||||
|
fn get_send_header<'a>() -> &'a str {
|
||||||
|
"status"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_response_header<'a>() -> &'a [u8] {
|
||||||
|
&[0x6E]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_player_string(mut data: Iter<&str>) -> GDResult<Self::Player> {
|
||||||
|
Ok(Player {
|
||||||
|
id: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?
|
||||||
|
},
|
||||||
|
score: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?
|
||||||
|
},
|
||||||
|
time: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?
|
||||||
|
},
|
||||||
|
ping: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?
|
||||||
|
},
|
||||||
|
name: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.to_string()
|
||||||
|
},
|
||||||
|
skin: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.to_string()
|
||||||
|
},
|
||||||
|
color_primary: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?
|
||||||
|
},
|
||||||
|
color_secondary: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query(address: &IpAddr, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response<Player>> {
|
||||||
|
client_query::<QuakeOne>(address, port, timeout_settings)
|
||||||
|
}
|
||||||
28
src/protocols/quake/three.rs
Normal file
28
src/protocols/quake/three.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::slice::Iter;
|
||||||
|
use crate::GDResult;
|
||||||
|
use crate::protocols::quake::two::{Player, QuakeTwo};
|
||||||
|
use crate::protocols::quake::Response;
|
||||||
|
use crate::protocols::quake::client::{QuakeClient, client_query};
|
||||||
|
use crate::protocols::types::TimeoutSettings;
|
||||||
|
|
||||||
|
struct QuakeThree;
|
||||||
|
impl QuakeClient for QuakeThree {
|
||||||
|
type Player = Player;
|
||||||
|
|
||||||
|
fn get_send_header<'a>() -> &'a str {
|
||||||
|
"getstatus"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_response_header<'a>() -> &'a [u8] {
|
||||||
|
&[0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x0A]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_player_string(data: Iter<&str>) -> GDResult<Self::Player> {
|
||||||
|
QuakeTwo::parse_player_string(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query(address: &IpAddr, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response<Player>> {
|
||||||
|
client_query::<QuakeThree>(address, port, timeout_settings)
|
||||||
|
}
|
||||||
55
src/protocols/quake/two.rs
Normal file
55
src/protocols/quake/two.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::slice::Iter;
|
||||||
|
use crate::{GDError, GDResult};
|
||||||
|
use crate::protocols::quake::one::QuakeOne;
|
||||||
|
use crate::protocols::quake::Response;
|
||||||
|
use crate::protocols::quake::client::{QuakeClient, client_query};
|
||||||
|
use crate::protocols::types::TimeoutSettings;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Quake 2 player data.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct Player {
|
||||||
|
pub frags: u8,
|
||||||
|
pub ping: u8,
|
||||||
|
pub name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct QuakeTwo;
|
||||||
|
impl QuakeClient for QuakeTwo {
|
||||||
|
type Player = Player;
|
||||||
|
|
||||||
|
fn get_send_header<'a>() -> &'a str {
|
||||||
|
QuakeOne::get_send_header()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_response_header<'a>() -> &'a [u8] {
|
||||||
|
&[0x70, 0x72, 0x69, 0x6E, 0x74, 0x0A]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_player_string(mut data: Iter<&str>) -> GDResult<Self::Player> {
|
||||||
|
Ok(Player {
|
||||||
|
frags: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?
|
||||||
|
},
|
||||||
|
ping: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => v.parse().map_err(|_| GDError::PacketBad)?
|
||||||
|
},
|
||||||
|
name: match data.next() {
|
||||||
|
None => Err(GDError::PacketBad)?,
|
||||||
|
Some(v) => match v.starts_with('\"') && v.ends_with('\"') {
|
||||||
|
false => v,
|
||||||
|
true => &v[1..v.len() - 1]
|
||||||
|
}.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query(address: &IpAddr, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response<Player>> {
|
||||||
|
client_query::<QuakeTwo>(address, port, timeout_settings)
|
||||||
|
}
|
||||||
29
src/protocols/quake/types.rs
Normal file
29
src/protocols/quake/types.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// General server information's.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Response<P> {
|
||||||
|
/// Name of the server.
|
||||||
|
pub name: String,
|
||||||
|
/// Map name.
|
||||||
|
pub map: String,
|
||||||
|
/// Current online players.
|
||||||
|
pub players: Vec<P>,
|
||||||
|
/// Number of players on the server.
|
||||||
|
pub players_online: u8,
|
||||||
|
/// Maximum number of players the server reports it can hold.
|
||||||
|
pub players_maximum: u8,
|
||||||
|
/// Indicates whether the server requires a password.
|
||||||
|
pub has_password: bool,
|
||||||
|
/// Maximum server frags.
|
||||||
|
pub frag_limit: u8,
|
||||||
|
/// Maximum server time.
|
||||||
|
pub time_limit: u8,
|
||||||
|
/// The server version.
|
||||||
|
pub version: String,
|
||||||
|
/// Other server entries that weren't used.
|
||||||
|
pub unused_entries: HashMap<String, String>,
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue