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
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,
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue