mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
[Protocol] Add GameSpy 2 support. (#47)
* [Protocol] Add initial files * [Protocol] Add test to test the request * [Protocol] Add initial query response type * [Protocol] Parse teams * [Protocol] Add players parse and add nice macro * [Protocol] Add proper derives to structs * [Protocol] Change to get all informations from one request * [Protocol] Add Halo: CE support and update CHANGELOG.md * [Protocol] Remove a .clone usage * [Protocol] Add todo comment regarding code performance * [Protocol] Use iterator instead of index range
This commit is contained in:
parent
80637f2398
commit
26ad1f5d19
10 changed files with 250 additions and 7 deletions
|
|
@ -1,2 +1,3 @@
|
|||
pub mod one;
|
||||
pub mod three;
|
||||
pub mod two;
|
||||
|
|
|
|||
5
src/protocols/gamespy/protocols/two/mod.rs
Normal file
5
src/protocols/gamespy/protocols/two/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod protocol;
|
||||
pub mod types;
|
||||
|
||||
pub use protocol::*;
|
||||
pub use types::*;
|
||||
185
src/protocols/gamespy/protocols/two/protocol.rs
Normal file
185
src/protocols/gamespy/protocols/two/protocol.rs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
use crate::bufferer::{Bufferer, Endianess};
|
||||
use crate::protocols::gamespy::two::{Player, Response, Team};
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::socket::{Socket, UdpSocket};
|
||||
use crate::{GDError, GDResult};
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
struct GameSpy2 {
|
||||
socket: UdpSocket,
|
||||
}
|
||||
|
||||
macro_rules! table_extract {
|
||||
($table:expr, $name:literal, $index:expr) => {
|
||||
$table
|
||||
.get($name)
|
||||
.ok_or(GDError::PacketBad)?
|
||||
.get($index)
|
||||
.ok_or(GDError::PacketBad)?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! table_extract_parse {
|
||||
($table:expr, $name:literal, $index:expr) => {
|
||||
table_extract!($table, $name, $index)
|
||||
.parse()
|
||||
.map_err(|_| GDError::PacketBad)?
|
||||
};
|
||||
}
|
||||
|
||||
fn data_as_table(data: &mut Bufferer) -> GDResult<(HashMap<String, Vec<String>>, usize)> {
|
||||
if data.get_u8()? != 0 {
|
||||
Err(GDError::PacketBad)?
|
||||
}
|
||||
|
||||
let rows = data.get_u8()? as usize;
|
||||
|
||||
if rows == 0 {
|
||||
return Ok((HashMap::new(), 0));
|
||||
}
|
||||
|
||||
let mut column_heads = Vec::new();
|
||||
|
||||
let mut current_column = data.get_string_utf8()?;
|
||||
while !current_column.is_empty() {
|
||||
column_heads.push(current_column);
|
||||
current_column = data.get_string_utf8()?;
|
||||
}
|
||||
|
||||
let columns = column_heads.len();
|
||||
let mut table = HashMap::with_capacity(columns);
|
||||
for head in &column_heads {
|
||||
table.insert(head.clone(), Vec::new()); // TODO: This doesn't look good nor it is performant, fix later
|
||||
}
|
||||
|
||||
for _ in 0 .. rows {
|
||||
for column in column_heads.iter() {
|
||||
let value = data.get_string_utf8()?;
|
||||
table.get_mut(column).ok_or(GDError::PacketBad)?.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((table, rows))
|
||||
}
|
||||
|
||||
impl GameSpy2 {
|
||||
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
|
||||
let socket = UdpSocket::new(address)?;
|
||||
socket.apply_timeout(timeout_settings)?;
|
||||
|
||||
Ok(Self { socket })
|
||||
}
|
||||
|
||||
fn request_data(&mut self) -> GDResult<Bufferer> {
|
||||
self.socket
|
||||
.send(&[0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF])?;
|
||||
|
||||
let received = self.socket.receive(None)?;
|
||||
let mut buf = Bufferer::new_with_data(Endianess::Big, &received);
|
||||
|
||||
if buf.get_u8()? != 0 {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
if buf.get_u32()? != 1 {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_server_vars(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String>> {
|
||||
let mut values = HashMap::new();
|
||||
|
||||
let mut done_processing_vars = false;
|
||||
while !done_processing_vars && bufferer.remaining_length() > 0 {
|
||||
let key = bufferer.get_string_utf8()?;
|
||||
let value = bufferer.get_string_utf8_optional()?;
|
||||
|
||||
if key.is_empty() {
|
||||
if value.is_empty() {
|
||||
bufferer.move_position_backward(1);
|
||||
done_processing_vars = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
values.insert(key, value);
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
fn get_teams(bufferer: &mut Bufferer) -> GDResult<Vec<Team>> {
|
||||
let mut teams = Vec::new();
|
||||
|
||||
let (table, entries) = data_as_table(bufferer)?;
|
||||
|
||||
for index in 0 .. entries {
|
||||
teams.push(Team {
|
||||
name: table_extract!(table, "team_t", index).clone(),
|
||||
score: table_extract_parse!(table, "score_t", index),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(teams)
|
||||
}
|
||||
|
||||
fn get_players(bufferer: &mut Bufferer) -> GDResult<Vec<Player>> {
|
||||
let mut players = Vec::new();
|
||||
|
||||
let (table, entries) = data_as_table(bufferer)?;
|
||||
|
||||
for index in 0 .. entries {
|
||||
players.push(Player {
|
||||
name: table_extract!(table, "player_", index).clone(),
|
||||
score: table_extract_parse!(table, "score_", index),
|
||||
ping: table_extract_parse!(table, "ping_", index),
|
||||
team_index: table_extract_parse!(table, "team_", index),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(players)
|
||||
}
|
||||
|
||||
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response> {
|
||||
let mut client = GameSpy2::new(address, timeout_settings)?;
|
||||
let mut data = client.request_data()?;
|
||||
|
||||
let mut server_vars = get_server_vars(&mut data)?;
|
||||
let players = get_players(&mut data)?;
|
||||
|
||||
let players_online = match server_vars.remove("numplayers") {
|
||||
None => players.len(),
|
||||
Some(v) => {
|
||||
let reported_players = v.parse().map_err(|_| GDError::TypeParse)?;
|
||||
match reported_players < players.len() {
|
||||
true => players.len(),
|
||||
false => reported_players,
|
||||
}
|
||||
}
|
||||
};
|
||||
let players_minimum = match server_vars.remove("minplayers") {
|
||||
None => None,
|
||||
Some(v) => Some(v.parse::<u8>().map_err(|_| GDError::TypeParse)?),
|
||||
};
|
||||
|
||||
Ok(Response {
|
||||
name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?,
|
||||
map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?,
|
||||
has_password: server_vars.remove("password").ok_or(GDError::PacketBad)? == "1",
|
||||
teams: get_teams(&mut data)?,
|
||||
players_maximum: server_vars
|
||||
.remove("maxplayers")
|
||||
.ok_or(GDError::PacketBad)?
|
||||
.parse()
|
||||
.map_err(|_| GDError::PacketBad)?,
|
||||
players_online,
|
||||
players_minimum,
|
||||
players,
|
||||
unused_entries: server_vars,
|
||||
})
|
||||
}
|
||||
34
src/protocols/gamespy/protocols/two/types.rs
Normal file
34
src/protocols/gamespy/protocols/two/types.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Team {
|
||||
pub name: String,
|
||||
pub score: u16,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Player {
|
||||
pub name: String,
|
||||
pub score: u16,
|
||||
pub ping: u16,
|
||||
pub team_index: u16,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Response {
|
||||
pub name: String,
|
||||
pub map: String,
|
||||
pub has_password: bool,
|
||||
pub teams: Vec<Team>,
|
||||
pub players_maximum: usize,
|
||||
pub players_online: usize,
|
||||
pub players_minimum: Option<u8>,
|
||||
pub players: Vec<Player>,
|
||||
pub unused_entries: HashMap<String, String>,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue