mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
Merge branch 'main' into feat/rootless-capture
This commit is contained in:
commit
36d957ceb4
52 changed files with 1483 additions and 457 deletions
|
|
@ -361,6 +361,55 @@ impl StringDecoder for Utf8Decoder {
|
|||
}
|
||||
}
|
||||
|
||||
/// A decoder for UTF-8 encoded strings prefixed by a single byte denoting the
|
||||
/// string's length.
|
||||
///
|
||||
/// This decoder uses a single null byte (`0x00`) as the default delimiter.
|
||||
pub struct Utf8LengthPrefixedDecoder;
|
||||
|
||||
impl StringDecoder for Utf8LengthPrefixedDecoder {
|
||||
type Delimiter = [u8; 1];
|
||||
|
||||
const DELIMITER: Self::Delimiter = [0x00];
|
||||
|
||||
/// Decodes a UTF-8 string from the given data, updating the cursor position
|
||||
/// accordingly.
|
||||
fn decode_string(data: &[u8], cursor: &mut usize, delimiter: Self::Delimiter) -> GDResult<String> {
|
||||
// Find the maximum length of the string
|
||||
let length = *data
|
||||
.first()
|
||||
.ok_or(PacketBad.context("Length of string not found"))?;
|
||||
|
||||
// Find the position of the delimiter in the data. If the delimiter is not
|
||||
// found, the length is returned.
|
||||
let position = data
|
||||
// Create an iterator over the data.
|
||||
.iter()
|
||||
.skip(1)
|
||||
.take(length as usize)
|
||||
// Find the position of the delimiter
|
||||
.position(|&b| b == delimiter.as_ref()[0])
|
||||
// If the delimiter is not found, use the whole data slice.
|
||||
.unwrap_or(length as usize);
|
||||
|
||||
// Convert the data until the found position into a UTF-8 string.
|
||||
let result = std::str::from_utf8(
|
||||
// Take a slice of data until the position.
|
||||
&data[1 .. position + 1]
|
||||
)
|
||||
// If the data cannot be converted into a UTF-8 string, return an error
|
||||
.map_err(|e| PacketBad.context(e))?
|
||||
// Convert the resulting &str into a String
|
||||
.to_owned();
|
||||
|
||||
// Update the cursor position
|
||||
// The +1 is to skip t length
|
||||
*cursor += position + 1;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// A decoder for UTF-16 encoded strings.
|
||||
///
|
||||
/// This decoder uses a pair of null bytes (`0x00, 0x00`) as the default
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_gdresult_ok() {
|
||||
let result: GDResult<u32> = Ok(42);
|
||||
assert_eq!(result.unwrap(), 42);
|
||||
assert_eq!(result, Ok(42));
|
||||
}
|
||||
|
||||
// Testing Err variant of the GDResult type
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::protocols::valve::GatheringSettings;
|
|||
use phf::{phf_map, Map};
|
||||
|
||||
macro_rules! game {
|
||||
($name: literal, $default_port: literal, $protocol: expr) => {
|
||||
($name: literal, $default_port: expr, $protocol: expr) => {
|
||||
game!(
|
||||
$name,
|
||||
$default_port,
|
||||
|
|
@ -18,7 +18,7 @@ macro_rules! game {
|
|||
)
|
||||
};
|
||||
|
||||
($name: literal, $default_port: literal, $protocol: expr, $extra_request_settings: expr) => {
|
||||
($name: literal, $default_port: expr, $protocol: expr, $extra_request_settings: expr) => {
|
||||
Game {
|
||||
name: $name,
|
||||
default_port: $default_port,
|
||||
|
|
@ -39,13 +39,24 @@ pub static GAMES: Map<&'static str, Game> = phf_map! {
|
|||
"minecraftlegacy16" => game!("Minecraft (legacy 1.6)", 25565, Protocol::PROPRIETARY(ProprietaryProtocol::Minecraft(Some(Server::Legacy(LegacyGroup::V1_6))))),
|
||||
"minecraftlegacy14" => game!("Minecraft (legacy 1.4)", 25565, Protocol::PROPRIETARY(ProprietaryProtocol::Minecraft(Some(Server::Legacy(LegacyGroup::V1_4))))),
|
||||
"minecraftlegacyb18" => game!("Minecraft (legacy b1.8)", 25565, Protocol::PROPRIETARY(ProprietaryProtocol::Minecraft(Some(Server::Legacy(LegacyGroup::VB1_8))))),
|
||||
"aapg" => game!("America's Army: Proving Grounds", 27020, Protocol::Valve(Engine::new(203_290)), GatheringSettings {
|
||||
players: true,
|
||||
rules: false,
|
||||
check_app_id: true,
|
||||
}.into_extra()),
|
||||
"alienswarm" => game!("Alien Swarm", 27015, Protocol::Valve(Engine::new(630))),
|
||||
"aoc" => game!("Age of Chivalry", 27015, Protocol::Valve(Engine::new(17510))),
|
||||
"a2oa" => game!("ARMA 2: Operation Arrowhead", 2304, Protocol::Valve(Engine::new(33930))),
|
||||
"ase" => game!("ARK: Survival Evolved", 27015, Protocol::Valve(Engine::new(346_110))),
|
||||
"asrd" => game!("Alien Swarm: Reactive Drop", 2304, Protocol::Valve(Engine::new(563_560))),
|
||||
"atlas" => game!("ATLAS", 57561, Protocol::Valve(Engine::new(834_910))),
|
||||
"avorion" => game!("Avorion", 27020, Protocol::Valve(Engine::new(445_220))),
|
||||
"barotrauma" => game!("Barotrauma", 27016, Protocol::Valve(Engine::new(602_960))),
|
||||
"basedefense" => game!("Base Defense", 27015, Protocol::Valve(Engine::new(632_730)), GatheringSettings {
|
||||
players: true,
|
||||
rules: false,
|
||||
check_app_id: true,
|
||||
}.into_extra()),
|
||||
"battalion1944" => game!("Battalion 1944", 7780, Protocol::Valve(Engine::new(489_940))),
|
||||
"brainbread2" => game!("BrainBread 2", 27015, Protocol::Valve(Engine::new(346_330))),
|
||||
"battlefield1942" => game!("Battlefield 1942", 23000, Protocol::Gamespy(GameSpyVersion::One)),
|
||||
|
|
@ -81,16 +92,24 @@ pub static GAMES: Map<&'static str, Game> = phf_map! {
|
|||
"l4d2" => game!("Left 4 Dead 2", 27015, Protocol::Valve(Engine::new(550))),
|
||||
"ohd" => game!("Operation: Harsh Doorstop", 27005, Protocol::Valve(Engine::new_with_dedicated(736_590, 950_900))),
|
||||
"onset" => game!("Onset", 7776, Protocol::Valve(Engine::new(1_105_810))),
|
||||
"postscriptum" => game!("Post Scriptum", 10037, Protocol::Valve(Engine::new(736_220))),
|
||||
"projectzomboid" => game!("Project Zomboid", 16261, Protocol::Valve(Engine::new(108_600))),
|
||||
"quake1" => game!("Quake 1", 27500, Protocol::Quake(QuakeVersion::One)),
|
||||
"quake2" => game!("Quake 2", 27910, Protocol::Quake(QuakeVersion::Two)),
|
||||
"q3a" => game!("Quake 3 Arena", 27960, Protocol::Quake(QuakeVersion::Three)),
|
||||
"risingworld" => game!("Rising World", 4254, Protocol::Valve(Engine::new(324_080)), GatheringSettings {
|
||||
players: true,
|
||||
rules: false,
|
||||
check_app_id: true,
|
||||
}.into_extra()),
|
||||
"ror2" => game!("Risk of Rain 2", 27016, Protocol::Valve(Engine::new(632_360))),
|
||||
"rust" => game!("Rust", 27015, Protocol::Valve(Engine::new(252_490))),
|
||||
"savage2" => game!("Savage 2", 11235, Protocol::PROPRIETARY(ProprietaryProtocol::Savage2)),
|
||||
"sco" => game!("Sven Co-op", 27015, Protocol::Valve(Engine::new_gold_src(false))),
|
||||
"sdtd" => game!("7 Days to Die", 26900, Protocol::Valve(Engine::new(251_570))),
|
||||
"sof2" => game!("Soldier of Fortune 2", 20100, Protocol::Quake(QuakeVersion::Three)),
|
||||
"serioussam" => game!("Serious Sam", 25601, Protocol::Gamespy(GameSpyVersion::One)),
|
||||
"squad" => game!("Squad", 27165, Protocol::Valve(Engine::new(393_380))),
|
||||
"theforest" => game!("The Forest", 27016, Protocol::Valve(Engine::new(556_450))),
|
||||
"thefront" => game!("The Front", 27015, Protocol::Valve(Engine::new(2_285_150))),
|
||||
"teamfortress2" => game!("Team Fortress 2", 27015, Protocol::Valve(Engine::new(440))),
|
||||
|
|
@ -106,10 +125,12 @@ pub static GAMES: Map<&'static str, Game> = phf_map! {
|
|||
"vrising" => game!("V Rising", 27016, Protocol::Valve(Engine::new(1_604_030))),
|
||||
"jc2m" => game!("Just Cause 2: Multiplayer", 7777, Protocol::PROPRIETARY(ProprietaryProtocol::JC2M)),
|
||||
"warsow" => game!("Warsow", 44400, Protocol::Quake(QuakeVersion::Three)),
|
||||
"darkesthour" => game!("Darkest Hour: Europe '44-'45 (2008)", 7758, Protocol::Unreal2),
|
||||
"dhe4445" => game!("Darkest Hour: Europe '44-'45 (2008)", 7758, Protocol::Unreal2),
|
||||
"devastation" => game!("Devastation (2003)", 7778, Protocol::Unreal2),
|
||||
"killingfloor" => game!("Killing Floor", 7708, Protocol::Unreal2),
|
||||
"redorchestra" => game!("Red Orchestra", 7759, Protocol::Unreal2),
|
||||
"ut2003" => game!("Unreal Tournament 2003", 7758, Protocol::Unreal2),
|
||||
"ut2004" => game!("Unreal Tournament 2004", 7778, Protocol::Unreal2),
|
||||
"unrealtournament2003" => game!("Unreal Tournament 2003", 7758, Protocol::Unreal2),
|
||||
"unrealtournament2004" => game!("Unreal Tournament 2004", 7778, Protocol::Unreal2),
|
||||
"zps" => game!("Zombie Panic: Source", 27015, Protocol::Valve(Engine::new(17_500))),
|
||||
"mindustry" => game!("Mindustry", crate::games::mindustry::DEFAULT_PORT, Protocol::PROPRIETARY(ProprietaryProtocol::Mindustry)),
|
||||
};
|
||||
|
|
|
|||
8
crates/lib/src/games/ffow/mod.rs
Normal file
8
crates/lib/src/games/ffow/mod.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/// The implementation.
|
||||
/// Reference: [Node-GameGig](https://github.com/gamedig/node-gamedig/blob/master/protocols/ffow.js)
|
||||
pub mod protocol;
|
||||
/// All types used by the implementation.
|
||||
pub mod types;
|
||||
|
||||
pub use protocol::*;
|
||||
pub use types::*;
|
||||
|
|
@ -1,64 +1,11 @@
|
|||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::types::{CommonResponse, TimeoutSettings};
|
||||
use crate::games::ffow::types::Response;
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol};
|
||||
use crate::protocols::GenericResponse;
|
||||
use crate::GDResult;
|
||||
use byteorder::LittleEndian;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
/// The query response.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Response {
|
||||
/// Protocol used by the server.
|
||||
pub protocol_version: u8,
|
||||
/// Name of the server.
|
||||
pub name: String,
|
||||
/// Map name.
|
||||
pub active_mod: String,
|
||||
/// Running game mode.
|
||||
pub game_mode: String,
|
||||
/// The version that the server is running on.
|
||||
pub game_version: String,
|
||||
/// Description of the server.
|
||||
pub description: String,
|
||||
/// Current map.
|
||||
pub map: String,
|
||||
/// Number of players on the server.
|
||||
pub players_online: u8,
|
||||
/// Maximum number of players the server reports it can hold.
|
||||
pub players_maximum: u8,
|
||||
/// Dedicated, NonDedicated or SourceTV
|
||||
pub server_type: Server,
|
||||
/// The Operating System that the server is on.
|
||||
pub environment_type: Environment,
|
||||
/// Indicates whether the server requires a password.
|
||||
pub has_password: bool,
|
||||
/// Indicates whether the server uses VAC.
|
||||
pub vac_secured: bool,
|
||||
/// Current round index.
|
||||
pub round: u8,
|
||||
/// Maximum amount of rounds.
|
||||
pub rounds_maximum: u8,
|
||||
/// Time left for the current round in seconds.
|
||||
pub time_left: u16,
|
||||
}
|
||||
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::FFOW(self) }
|
||||
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.game_version) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u32 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u32 { self.players_online.into() }
|
||||
}
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
56
crates/lib/src/games/ffow/types.rs
Normal file
56
crates/lib/src/games/ffow/types.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use crate::protocols::types::CommonResponse;
|
||||
use crate::protocols::valve::{Environment, Server};
|
||||
use crate::protocols::GenericResponse;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The query response.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Response {
|
||||
/// Protocol used by the server.
|
||||
pub protocol_version: u8,
|
||||
/// Name of the server.
|
||||
pub name: String,
|
||||
/// Map name.
|
||||
pub active_mod: String,
|
||||
/// Running game mode.
|
||||
pub game_mode: String,
|
||||
/// The version that the server is running on.
|
||||
pub game_version: String,
|
||||
/// Description of the server.
|
||||
pub description: String,
|
||||
/// Current map.
|
||||
pub map: String,
|
||||
/// Number of players on the server.
|
||||
pub players_online: u8,
|
||||
/// Maximum number of players the server reports it can hold.
|
||||
pub players_maximum: u8,
|
||||
/// Dedicated, NonDedicated or SourceTV
|
||||
pub server_type: Server,
|
||||
/// The Operating System that the server is on.
|
||||
pub environment_type: Environment,
|
||||
/// Indicates whether the server requires a password.
|
||||
pub has_password: bool,
|
||||
/// Indicates whether the server uses VAC.
|
||||
pub vac_secured: bool,
|
||||
/// Current round index.
|
||||
pub round: u8,
|
||||
/// Maximum amount of rounds.
|
||||
pub rounds_maximum: u8,
|
||||
/// Time left for the current round in seconds.
|
||||
pub time_left: u16,
|
||||
}
|
||||
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::FFOW(self) }
|
||||
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.game_version) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u32 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u32 { self.players_online.into() }
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::gamespy::common::has_password;
|
||||
use crate::protocols::gamespy::three::{data_to_map, GameSpy3};
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings};
|
||||
use crate::protocols::GenericResponse;
|
||||
use crate::GDErrorKind::{PacketBad, TypeParse};
|
||||
use crate::{GDErrorKind, GDResult};
|
||||
use byteorder::BigEndian;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Player {
|
||||
name: String,
|
||||
steam_id: String,
|
||||
ping: u16,
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::JCMP2(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Response {
|
||||
game_version: String,
|
||||
description: String,
|
||||
name: String,
|
||||
has_password: bool,
|
||||
players: Vec<Player>,
|
||||
players_maximum: u32,
|
||||
players_online: u32,
|
||||
}
|
||||
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::JC2M(self) }
|
||||
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.game_version) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u32 { self.players_maximum }
|
||||
fn players_online(&self) -> u32 { self.players_online }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn crate::protocols::types::CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_players_and_teams(packet: &[u8]) -> GDResult<Vec<Player>> {
|
||||
let mut buf = Buffer::<BigEndian>::new(packet);
|
||||
|
||||
let count = buf.read::<u16>()?;
|
||||
let mut players = Vec::with_capacity(count as usize);
|
||||
|
||||
while buf.remaining_length() != 0 {
|
||||
players.push(Player {
|
||||
name: buf.read_string::<Utf8Decoder>(None)?,
|
||||
steam_id: buf.read_string::<Utf8Decoder>(None)?,
|
||||
ping: buf.read::<u16>()?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(players)
|
||||
}
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response> {
|
||||
let mut client = GameSpy3::new_custom(
|
||||
&SocketAddr::new(*address, port.unwrap_or(7777)),
|
||||
timeout_settings,
|
||||
[0xFF, 0xFF, 0xFF, 0x02],
|
||||
true,
|
||||
)?;
|
||||
|
||||
let packets = client.get_server_packets()?;
|
||||
let data = packets
|
||||
.get(0)
|
||||
.ok_or(PacketBad.context("First packet missing"))?;
|
||||
|
||||
let (mut server_vars, remaining_data) = data_to_map(data)?;
|
||||
let players = parse_players_and_teams(&remaining_data)?;
|
||||
|
||||
let players_maximum = server_vars
|
||||
.remove("maxplayers")
|
||||
.ok_or(PacketBad.context("Server variables missing maxplayers"))?
|
||||
.parse()
|
||||
.map_err(|e| TypeParse.context(e))?;
|
||||
let players_online = match server_vars.remove("numplayers") {
|
||||
None => players.len(),
|
||||
Some(v) => {
|
||||
let reported_players = v.parse().map_err(|e| TypeParse.context(e))?;
|
||||
match reported_players < players.len() {
|
||||
true => players.len(),
|
||||
false => reported_players,
|
||||
}
|
||||
}
|
||||
} as u32;
|
||||
|
||||
Ok(Response {
|
||||
game_version: server_vars
|
||||
.remove("version")
|
||||
.ok_or(GDErrorKind::PacketBad)?,
|
||||
description: server_vars
|
||||
.remove("description")
|
||||
.ok_or(GDErrorKind::PacketBad)?,
|
||||
name: server_vars
|
||||
.remove("hostname")
|
||||
.ok_or(GDErrorKind::PacketBad)?,
|
||||
has_password: has_password(&mut server_vars)?,
|
||||
players,
|
||||
players_maximum,
|
||||
players_online,
|
||||
})
|
||||
}
|
||||
8
crates/lib/src/games/jc2m/mod.rs
Normal file
8
crates/lib/src/games/jc2m/mod.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/// The implementation.
|
||||
/// Reference: [Node-GameGig](https://github.com/gamedig/node-gamedig/blob/master/protocols/jc2mp.js)
|
||||
pub mod protocol;
|
||||
/// All types used by the implementation.
|
||||
pub mod types;
|
||||
|
||||
pub use protocol::*;
|
||||
pub use types::*;
|
||||
75
crates/lib/src/games/jc2m/protocol.rs
Normal file
75
crates/lib/src/games/jc2m/protocol.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::jc2m::{Player, Response};
|
||||
use crate::protocols::gamespy::common::has_password;
|
||||
use crate::protocols::gamespy::three::{data_to_map, GameSpy3};
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::GDErrorKind::{PacketBad, TypeParse};
|
||||
use crate::GDResult;
|
||||
use byteorder::BigEndian;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
fn parse_players_and_teams(packet: &[u8]) -> GDResult<Vec<Player>> {
|
||||
let mut buf = Buffer::<BigEndian>::new(packet);
|
||||
|
||||
let count = buf.read::<u16>()?;
|
||||
let mut players = Vec::with_capacity(count as usize);
|
||||
|
||||
while buf.remaining_length() != 0 {
|
||||
players.push(Player {
|
||||
name: buf.read_string::<Utf8Decoder>(None)?,
|
||||
steam_id: buf.read_string::<Utf8Decoder>(None)?,
|
||||
ping: buf.read::<u16>()?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(players)
|
||||
}
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response> {
|
||||
let mut client = GameSpy3::new_custom(
|
||||
&SocketAddr::new(*address, port.unwrap_or(7777)),
|
||||
timeout_settings,
|
||||
[0xFF, 0xFF, 0xFF, 0x02],
|
||||
true,
|
||||
)?;
|
||||
|
||||
let packets = client.get_server_packets()?;
|
||||
let data = packets
|
||||
.get(0)
|
||||
.ok_or_else(|| PacketBad.context("First packet missing"))?;
|
||||
|
||||
let (mut server_vars, remaining_data) = data_to_map(data)?;
|
||||
let players = parse_players_and_teams(&remaining_data)?;
|
||||
|
||||
let players_maximum = server_vars
|
||||
.remove("maxplayers")
|
||||
.ok_or_else(|| PacketBad.context("Server variables missing maxplayers"))?
|
||||
.parse()
|
||||
.map_err(|e| TypeParse.context(e))?;
|
||||
let players_online = match server_vars.remove("numplayers") {
|
||||
None => players.len(),
|
||||
Some(v) => {
|
||||
let reported_players = v.parse().map_err(|e| TypeParse.context(e))?;
|
||||
match reported_players < players.len() {
|
||||
true => players.len(),
|
||||
false => reported_players,
|
||||
}
|
||||
}
|
||||
} as u32;
|
||||
|
||||
Ok(Response {
|
||||
game_version: server_vars.remove("version").ok_or(PacketBad)?,
|
||||
description: server_vars.remove("description").ok_or(PacketBad)?,
|
||||
name: server_vars.remove("hostname").ok_or(PacketBad)?,
|
||||
has_password: has_password(&mut server_vars)?,
|
||||
players,
|
||||
players_maximum,
|
||||
players_online,
|
||||
})
|
||||
}
|
||||
50
crates/lib/src/games/jc2m/types.rs
Normal file
50
crates/lib/src/games/jc2m/types.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
||||
use crate::protocols::GenericResponse;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Player {
|
||||
pub name: String,
|
||||
pub steam_id: String,
|
||||
pub ping: u16,
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::JCMP2(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Response {
|
||||
pub game_version: String,
|
||||
pub description: String,
|
||||
pub name: String,
|
||||
pub has_password: bool,
|
||||
pub players: Vec<Player>,
|
||||
pub players_maximum: u32,
|
||||
pub players_online: u32,
|
||||
}
|
||||
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::JC2M(self) }
|
||||
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.game_version) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u32 { self.players_maximum }
|
||||
fn players_online(&self) -> u32 { self.players_online }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
25
crates/lib/src/games/mindustry/mod.rs
Normal file
25
crates/lib/src/games/mindustry/mod.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//! Mindustry game ping (v146)
|
||||
//!
|
||||
//! [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/ArcNetProvider.java#L225-L259)
|
||||
|
||||
use std::{net::IpAddr, net::SocketAddr};
|
||||
|
||||
use crate::{GDResult, TimeoutSettings};
|
||||
|
||||
use self::types::ServerData;
|
||||
|
||||
pub mod types;
|
||||
|
||||
pub mod protocol;
|
||||
|
||||
/// Default mindustry server port
|
||||
///
|
||||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/Vars.java#L141-L142)
|
||||
pub const DEFAULT_PORT: u16 = 6567;
|
||||
|
||||
/// Query a mindustry server.
|
||||
pub fn query(ip: &IpAddr, port: Option<u16>, timeout_settings: &Option<TimeoutSettings>) -> GDResult<ServerData> {
|
||||
let address = SocketAddr::new(*ip, port.unwrap_or(DEFAULT_PORT));
|
||||
|
||||
protocol::query_with_retries(&address, timeout_settings)
|
||||
}
|
||||
58
crates/lib/src/games/mindustry/protocol.rs
Normal file
58
crates/lib/src/games/mindustry/protocol.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use crate::{
|
||||
buffer::{self, Buffer},
|
||||
socket::{Socket, UdpSocket},
|
||||
utils,
|
||||
GDResult,
|
||||
TimeoutSettings,
|
||||
};
|
||||
|
||||
use super::types::ServerData;
|
||||
|
||||
/// Mindustry max datagram packet size.
|
||||
pub const MAX_BUFFER_SIZE: usize = 500;
|
||||
|
||||
/// Send a ping packet.
|
||||
///
|
||||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/ArcNetProvider.java#L248)
|
||||
pub fn send_ping(socket: &mut UdpSocket) -> GDResult<()> { socket.send(&[-2i8 as u8, 1i8 as u8]) }
|
||||
|
||||
/// Parse server data.
|
||||
///
|
||||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/NetworkIO.java#L122-L135)
|
||||
pub fn parse_server_data<B: byteorder::ByteOrder, D: buffer::StringDecoder>(
|
||||
buffer: &mut Buffer<B>,
|
||||
) -> GDResult<ServerData> {
|
||||
Ok(ServerData {
|
||||
host: buffer.read_string::<D>(None)?,
|
||||
map: buffer.read_string::<D>(None)?,
|
||||
players: buffer.read()?,
|
||||
wave: buffer.read()?,
|
||||
version: buffer.read()?,
|
||||
version_type: buffer.read_string::<D>(None)?,
|
||||
gamemode: buffer.read::<u8>()?.try_into()?,
|
||||
player_limit: buffer.read()?,
|
||||
description: buffer.read_string::<D>(None)?,
|
||||
mode_name: buffer.read_string::<D>(None).ok(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Query a Mindustry server (without retries).
|
||||
pub fn query(address: &SocketAddr, timeout_settings: &Option<TimeoutSettings>) -> GDResult<ServerData> {
|
||||
let mut socket = UdpSocket::new(address, timeout_settings)?;
|
||||
|
||||
send_ping(&mut socket)?;
|
||||
|
||||
let socket_data = socket.receive(Some(MAX_BUFFER_SIZE))?;
|
||||
let mut buffer = Buffer::new(&socket_data);
|
||||
|
||||
parse_server_data::<byteorder::BigEndian, buffer::Utf8LengthPrefixedDecoder>(&mut buffer)
|
||||
}
|
||||
|
||||
/// Query a Mindustry server.
|
||||
pub fn query_with_retries(address: &SocketAddr, timeout_settings: &Option<TimeoutSettings>) -> GDResult<ServerData> {
|
||||
let retries = TimeoutSettings::get_retries_or_default(timeout_settings);
|
||||
|
||||
utils::retry_on_timeout(retries, || query(address, timeout_settings))
|
||||
}
|
||||
108
crates/lib/src/games/mindustry/types.rs
Normal file
108
crates/lib/src/games/mindustry/types.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use crate::{
|
||||
protocols::types::{CommonResponse, GenericResponse},
|
||||
GDErrorKind,
|
||||
};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Mindustry sever data
|
||||
///
|
||||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/NetworkIO.java#L122-L135)
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ServerData {
|
||||
pub host: String,
|
||||
pub map: String,
|
||||
pub players: i32,
|
||||
pub wave: i32,
|
||||
pub version: i32,
|
||||
pub version_type: String,
|
||||
pub gamemode: GameMode,
|
||||
pub player_limit: i32,
|
||||
pub description: String,
|
||||
pub mode_name: Option<String>,
|
||||
}
|
||||
|
||||
/// Mindustry game mode
|
||||
///
|
||||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/game/Gamemode.java)
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum GameMode {
|
||||
Survival,
|
||||
Sandbox,
|
||||
Attack,
|
||||
PVP,
|
||||
Editor,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for GameMode {
|
||||
type Error = GDErrorKind;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
use GameMode::*;
|
||||
Ok(match value {
|
||||
0 => Survival,
|
||||
1 => Sandbox,
|
||||
2 => Attack,
|
||||
3 => PVP,
|
||||
4 => Editor,
|
||||
_ => return Err(GDErrorKind::TypeParse),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GameMode {
|
||||
fn as_str(&self) -> &'static str {
|
||||
use GameMode::*;
|
||||
match self {
|
||||
Survival => "survival",
|
||||
Sandbox => "sandbox",
|
||||
Attack => "attack",
|
||||
PVP => "pvp",
|
||||
Editor => "editor",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonResponse for ServerData {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::Mindustry(self) }
|
||||
|
||||
fn players_online(&self) -> u32 { self.players.try_into().unwrap_or(0) }
|
||||
fn players_maximum(&self) -> u32 { self.player_limit.try_into().unwrap_or(0) }
|
||||
|
||||
fn game_mode(&self) -> Option<&str> { Some(self.gamemode.as_str()) }
|
||||
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::protocols::types::CommonResponse;
|
||||
|
||||
use super::ServerData;
|
||||
|
||||
#[test]
|
||||
fn common_impl() {
|
||||
let data = ServerData {
|
||||
host: String::from("host"),
|
||||
map: String::from("map"),
|
||||
players: 5,
|
||||
wave: 2,
|
||||
version: 142,
|
||||
version_type: String::from("steam"),
|
||||
gamemode: super::GameMode::PVP,
|
||||
player_limit: 20,
|
||||
description: String::from("description"),
|
||||
mode_name: Some(String::from("campaign")),
|
||||
};
|
||||
|
||||
let common: &dyn CommonResponse = &data;
|
||||
|
||||
assert_eq!(common.players_online(), 5);
|
||||
assert_eq!(common.players_maximum(), 20);
|
||||
assert_eq!(common.game_mode(), Some("pvp"));
|
||||
assert_eq!(common.map(), Some("map"));
|
||||
assert_eq!(common.description(), Some("description"));
|
||||
}
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ impl Default for RequestSettings {
|
|||
impl RequestSettings {
|
||||
/// Make a new *RequestSettings* with just the hostname, the protocol
|
||||
/// version defaults to -1
|
||||
pub fn new_just_hostname(hostname: String) -> Self {
|
||||
pub const fn new_just_hostname(hostname: String) -> Self {
|
||||
Self {
|
||||
hostname,
|
||||
protocol_version: -1,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
//! Currently supported games.
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod gamespy;
|
||||
pub mod quake;
|
||||
pub mod unreal2;
|
||||
|
|
@ -19,137 +16,23 @@ pub mod battalion1944;
|
|||
pub mod ffow;
|
||||
/// Just Cause 2: Multiplayer
|
||||
pub mod jc2m;
|
||||
/// Mindustry
|
||||
pub mod mindustry;
|
||||
/// Minecraft
|
||||
pub mod minecraft;
|
||||
/// Savage 2
|
||||
pub mod savage2;
|
||||
/// The Ship
|
||||
pub mod theship;
|
||||
|
||||
use crate::protocols::gamespy::GameSpyVersion;
|
||||
use crate::protocols::quake::QuakeVersion;
|
||||
use crate::protocols::types::{CommonResponse, ExtraRequestSettings, ProprietaryProtocol, TimeoutSettings};
|
||||
use crate::protocols::{self, Protocol};
|
||||
use crate::GDResult;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
pub mod types;
|
||||
pub use types::*;
|
||||
|
||||
/// Definition of a game
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Game {
|
||||
/// Full name of the game
|
||||
pub name: &'static str,
|
||||
/// Default port used by game
|
||||
pub default_port: u16,
|
||||
/// The protocol the game's query uses
|
||||
pub protocol: Protocol,
|
||||
/// Request settings.
|
||||
pub request_settings: ExtraRequestSettings,
|
||||
}
|
||||
pub mod query;
|
||||
pub use query::*;
|
||||
|
||||
#[cfg(feature = "game_defs")]
|
||||
mod definitions;
|
||||
|
||||
#[cfg(feature = "game_defs")]
|
||||
pub use definitions::GAMES;
|
||||
|
||||
/// Make a query given a game definition
|
||||
#[inline]
|
||||
pub fn query(game: &Game, address: &IpAddr, port: Option<u16>) -> GDResult<Box<dyn CommonResponse>> {
|
||||
query_with_timeout_and_extra_settings(game, address, port, None, None)
|
||||
}
|
||||
|
||||
/// Make a query given a game definition and timeout settings
|
||||
#[inline]
|
||||
pub fn query_with_timeout(
|
||||
game: &Game,
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Box<dyn CommonResponse>> {
|
||||
query_with_timeout_and_extra_settings(game, address, port, timeout_settings, None)
|
||||
}
|
||||
|
||||
/// Make a query given a game definition, timeout settings, and extra settings
|
||||
pub fn query_with_timeout_and_extra_settings(
|
||||
game: &Game,
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
extra_settings: Option<ExtraRequestSettings>,
|
||||
) -> GDResult<Box<dyn CommonResponse>> {
|
||||
let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port));
|
||||
Ok(match &game.protocol {
|
||||
Protocol::Valve(engine) => {
|
||||
protocols::valve::query(
|
||||
&socket_addr,
|
||||
*engine,
|
||||
extra_settings
|
||||
.or(Option::from(game.request_settings.clone()))
|
||||
.map(ExtraRequestSettings::into),
|
||||
timeout_settings,
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
Protocol::Gamespy(version) => {
|
||||
match version {
|
||||
GameSpyVersion::One => protocols::gamespy::one::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
GameSpyVersion::Two => protocols::gamespy::two::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
GameSpyVersion::Three => {
|
||||
protocols::gamespy::three::query(&socket_addr, timeout_settings).map(Box::new)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Protocol::Quake(version) => {
|
||||
match version {
|
||||
QuakeVersion::One => protocols::quake::one::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
QuakeVersion::Two => protocols::quake::two::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
QuakeVersion::Three => protocols::quake::three::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
}
|
||||
}
|
||||
Protocol::Unreal2 => {
|
||||
protocols::unreal2::query(
|
||||
&socket_addr,
|
||||
&extra_settings
|
||||
.map(ExtraRequestSettings::into)
|
||||
.unwrap_or_default(),
|
||||
timeout_settings,
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
Protocol::PROPRIETARY(protocol) => {
|
||||
match protocol {
|
||||
ProprietaryProtocol::TheShip => {
|
||||
theship::query_with_timeout(address, port, timeout_settings).map(Box::new)?
|
||||
}
|
||||
ProprietaryProtocol::FFOW => ffow::query_with_timeout(address, port, timeout_settings).map(Box::new)?,
|
||||
ProprietaryProtocol::JC2M => jc2m::query_with_timeout(address, port, timeout_settings).map(Box::new)?,
|
||||
ProprietaryProtocol::Minecraft(version) => {
|
||||
match version {
|
||||
Some(minecraft::Server::Java) => {
|
||||
minecraft::protocol::query_java(
|
||||
&socket_addr,
|
||||
timeout_settings,
|
||||
extra_settings.map(ExtraRequestSettings::into),
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
Some(minecraft::Server::Bedrock) => {
|
||||
minecraft::protocol::query_bedrock(&socket_addr, timeout_settings).map(Box::new)?
|
||||
}
|
||||
Some(minecraft::Server::Legacy(group)) => {
|
||||
minecraft::protocol::query_legacy_specific(*group, &socket_addr, timeout_settings)
|
||||
.map(Box::new)?
|
||||
}
|
||||
None => {
|
||||
minecraft::protocol::query(
|
||||
&socket_addr,
|
||||
timeout_settings,
|
||||
extra_settings.map(ExtraRequestSettings::into),
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
118
crates/lib/src/games/query.rs
Normal file
118
crates/lib/src/games/query.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
//! Generic query functions
|
||||
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
use crate::games::types::Game;
|
||||
use crate::games::{ffow, jc2m, mindustry, minecraft, savage2, theship};
|
||||
use crate::protocols;
|
||||
use crate::protocols::gamespy::GameSpyVersion;
|
||||
use crate::protocols::quake::QuakeVersion;
|
||||
use crate::protocols::types::{CommonResponse, ExtraRequestSettings, ProprietaryProtocol, Protocol, TimeoutSettings};
|
||||
use crate::GDResult;
|
||||
|
||||
/// Make a query given a game definition
|
||||
#[inline]
|
||||
pub fn query(game: &Game, address: &IpAddr, port: Option<u16>) -> GDResult<Box<dyn CommonResponse>> {
|
||||
query_with_timeout_and_extra_settings(game, address, port, None, None)
|
||||
}
|
||||
|
||||
/// Make a query given a game definition and timeout settings
|
||||
#[inline]
|
||||
pub fn query_with_timeout(
|
||||
game: &Game,
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Box<dyn CommonResponse>> {
|
||||
query_with_timeout_and_extra_settings(game, address, port, timeout_settings, None)
|
||||
}
|
||||
|
||||
/// Make a query given a game definition, timeout settings, and extra settings
|
||||
pub fn query_with_timeout_and_extra_settings(
|
||||
game: &Game,
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
extra_settings: Option<ExtraRequestSettings>,
|
||||
) -> GDResult<Box<dyn CommonResponse>> {
|
||||
let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port));
|
||||
Ok(match &game.protocol {
|
||||
Protocol::Valve(engine) => {
|
||||
protocols::valve::query(
|
||||
&socket_addr,
|
||||
*engine,
|
||||
extra_settings
|
||||
.or_else(|| Option::from(game.request_settings.clone()))
|
||||
.map(ExtraRequestSettings::into),
|
||||
timeout_settings,
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
Protocol::Gamespy(version) => {
|
||||
match version {
|
||||
GameSpyVersion::One => protocols::gamespy::one::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
GameSpyVersion::Two => protocols::gamespy::two::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
GameSpyVersion::Three => {
|
||||
protocols::gamespy::three::query(&socket_addr, timeout_settings).map(Box::new)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Protocol::Quake(version) => {
|
||||
match version {
|
||||
QuakeVersion::One => protocols::quake::one::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
QuakeVersion::Two => protocols::quake::two::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
QuakeVersion::Three => protocols::quake::three::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
}
|
||||
}
|
||||
Protocol::Unreal2 => {
|
||||
protocols::unreal2::query(
|
||||
&socket_addr,
|
||||
&extra_settings
|
||||
.map(ExtraRequestSettings::into)
|
||||
.unwrap_or_default(),
|
||||
timeout_settings,
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
Protocol::PROPRIETARY(protocol) => {
|
||||
match protocol {
|
||||
ProprietaryProtocol::Savage2 => {
|
||||
savage2::query_with_timeout(address, port, timeout_settings).map(Box::new)?
|
||||
}
|
||||
ProprietaryProtocol::TheShip => {
|
||||
theship::query_with_timeout(address, port, timeout_settings).map(Box::new)?
|
||||
}
|
||||
ProprietaryProtocol::FFOW => ffow::query_with_timeout(address, port, timeout_settings).map(Box::new)?,
|
||||
ProprietaryProtocol::JC2M => jc2m::query_with_timeout(address, port, timeout_settings).map(Box::new)?,
|
||||
ProprietaryProtocol::Mindustry => mindustry::query(address, port, &timeout_settings).map(Box::new)?,
|
||||
ProprietaryProtocol::Minecraft(version) => {
|
||||
match version {
|
||||
Some(minecraft::Server::Java) => {
|
||||
minecraft::protocol::query_java(
|
||||
&socket_addr,
|
||||
timeout_settings,
|
||||
extra_settings.map(ExtraRequestSettings::into),
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
Some(minecraft::Server::Bedrock) => {
|
||||
minecraft::protocol::query_bedrock(&socket_addr, timeout_settings).map(Box::new)?
|
||||
}
|
||||
Some(minecraft::Server::Legacy(group)) => {
|
||||
minecraft::protocol::query_legacy_specific(*group, &socket_addr, timeout_settings)
|
||||
.map(Box::new)?
|
||||
}
|
||||
None => {
|
||||
minecraft::protocol::query(
|
||||
&socket_addr,
|
||||
timeout_settings,
|
||||
extra_settings.map(ExtraRequestSettings::into),
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
8
crates/lib/src/games/savage2/mod.rs
Normal file
8
crates/lib/src/games/savage2/mod.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/// The implementation.
|
||||
/// Reference: [Node-GameGig](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js)
|
||||
pub mod protocol;
|
||||
/// All types used by the implementation.
|
||||
pub mod types;
|
||||
|
||||
pub use protocol::*;
|
||||
pub use types::*;
|
||||
37
crates/lib/src/games/savage2/protocol.rs
Normal file
37
crates/lib/src/games/savage2/protocol.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::games::savage2::types::Response;
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::socket::{Socket, UdpSocket};
|
||||
use crate::GDResult;
|
||||
use byteorder::LittleEndian;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response> {
|
||||
let addr = &SocketAddr::new(*address, port.unwrap_or(11235));
|
||||
let mut socket = UdpSocket::new(addr, &timeout_settings)?;
|
||||
socket.send(&[0x01])?;
|
||||
let data = socket.receive(None)?;
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
buffer.move_cursor(12)?;
|
||||
|
||||
Ok(Response {
|
||||
name: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
players_online: buffer.read::<u8>()?,
|
||||
players_maximum: buffer.read::<u8>()?,
|
||||
time: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
map: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
next_map: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
location: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
players_minimum: buffer.read::<u8>()?,
|
||||
game_mode: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
protocol_version: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
level_minimum: buffer.read::<u8>()?,
|
||||
})
|
||||
}
|
||||
30
crates/lib/src/games/savage2/types.rs
Normal file
30
crates/lib/src/games/savage2/types.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use crate::protocols::types::CommonResponse;
|
||||
use crate::protocols::GenericResponse;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Response {
|
||||
pub name: String,
|
||||
pub players_online: u8,
|
||||
pub players_maximum: u8,
|
||||
pub players_minimum: u8,
|
||||
pub time: String,
|
||||
pub map: String,
|
||||
pub next_map: String,
|
||||
pub location: String,
|
||||
pub game_mode: String,
|
||||
pub protocol_version: String,
|
||||
pub level_minimum: u8,
|
||||
}
|
||||
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::Savage2(self) }
|
||||
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn players_maximum(&self) -> u32 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u32 { self.players_online.into() }
|
||||
}
|
||||
8
crates/lib/src/games/theship/mod.rs
Normal file
8
crates/lib/src/games/theship/mod.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/// The implementation.
|
||||
/// Reference: [server queries](https://developer.valvesoftware.com/wiki/Server_queries)
|
||||
pub mod protocol;
|
||||
/// All types used by the implementation.
|
||||
pub mod types;
|
||||
|
||||
pub use protocol::*;
|
||||
pub use types::*;
|
||||
23
crates/lib/src/games/theship/protocol.rs
Normal file
23
crates/lib/src/games/theship/protocol.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use crate::games::theship::types::Response;
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::protocols::valve;
|
||||
use crate::protocols::valve::Engine;
|
||||
use crate::GDResult;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response> {
|
||||
let valve_response = valve::query(
|
||||
&SocketAddr::new(*address, port.unwrap_or(27015)),
|
||||
Engine::new(2400),
|
||||
None,
|
||||
timeout_settings,
|
||||
)?;
|
||||
|
||||
Response::new_from_valve_response(valve_response)
|
||||
}
|
||||
|
|
@ -1,17 +1,10 @@
|
|||
use crate::{
|
||||
protocols::{
|
||||
types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings},
|
||||
valve::{self, get_optional_extracted_data, Server, ServerPlayer},
|
||||
GenericResponse,
|
||||
},
|
||||
GDErrorKind::PacketBad,
|
||||
GDResult,
|
||||
};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
||||
use crate::protocols::valve::{get_optional_extracted_data, Server, ServerPlayer};
|
||||
use crate::protocols::{valve, GenericResponse};
|
||||
use crate::GDErrorKind::PacketBad;
|
||||
use crate::GDResult;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::protocols::valve::Engine;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -127,20 +120,3 @@ impl Response {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response> {
|
||||
let valve_response = valve::query(
|
||||
&SocketAddr::new(*address, port.unwrap_or(27015)),
|
||||
Engine::new(2400),
|
||||
None,
|
||||
timeout_settings,
|
||||
)?;
|
||||
|
||||
Response::new_from_valve_response(valve_response)
|
||||
}
|
||||
20
crates/lib/src/games/types.rs
Normal file
20
crates/lib/src/games/types.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//! Game related types
|
||||
|
||||
use crate::protocols::types::{ExtraRequestSettings, Protocol};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Definition of a game
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Game {
|
||||
/// Full name of the game
|
||||
pub name: &'static str,
|
||||
/// Default port used by game
|
||||
pub default_port: u16,
|
||||
/// The protocol the game's query uses
|
||||
pub protocol: Protocol,
|
||||
/// Request settings.
|
||||
pub request_settings: ExtraRequestSettings,
|
||||
}
|
||||
|
|
@ -8,8 +8,20 @@ game_query_mod!(
|
|||
Engine::new(33930),
|
||||
2304
|
||||
);
|
||||
game_query_mod!(basedefense, "Base Defense", Engine::new(632_730), 27015);
|
||||
game_query_mod!(alienswarm, "Alien Swarm", Engine::new(630), 27015);
|
||||
game_query_mod!(aoc, "Age of Chivalry", Engine::new(17510), 27015);
|
||||
game_query_mod!(
|
||||
aapg,
|
||||
"America's Army: Proving Grounds",
|
||||
Engine::new(203_290),
|
||||
27020,
|
||||
GatheringSettings {
|
||||
players: true,
|
||||
rules: false,
|
||||
check_app_id: true,
|
||||
}
|
||||
);
|
||||
game_query_mod!(ase, "ARK: Survival Evolved", Engine::new(346_110), 27015);
|
||||
game_query_mod!(
|
||||
asrd,
|
||||
|
|
@ -17,6 +29,7 @@ game_query_mod!(
|
|||
Engine::new(563_560),
|
||||
2304
|
||||
);
|
||||
game_query_mod!(atlas, "ATLAS", Engine::new(834_910), 57561);
|
||||
game_query_mod!(avorion, "Avorion", Engine::new(445_220), 27020);
|
||||
game_query_mod!(
|
||||
ballisticoverkill,
|
||||
|
|
@ -100,16 +113,19 @@ game_query_mod!(
|
|||
27005
|
||||
);
|
||||
game_query_mod!(onset, "Onset", Engine::new(1_105_810), 7776);
|
||||
game_query_mod!(postscriptum, "Post Scriptum", Engine::new(736_220), 10037);
|
||||
game_query_mod!(
|
||||
projectzomboid,
|
||||
"Project Zomboid",
|
||||
Engine::new(108_600),
|
||||
16261
|
||||
);
|
||||
game_query_mod!(risingworld, "Rising World", Engine::new(324_080), 4254);
|
||||
game_query_mod!(ror2, "Risk of Rain 2", Engine::new(632_360), 27016);
|
||||
game_query_mod!(rust, "Rust", Engine::new(252_490), 27015);
|
||||
game_query_mod!(sco, "Sven Co-op", Engine::new_gold_src(false), 27015);
|
||||
game_query_mod!(sdtd, "7 Days to Die", Engine::new(251_570), 26900);
|
||||
game_query_mod!(squad, "Squad", Engine::new(393_380), 27165);
|
||||
game_query_mod!(teamfortress2, "Team Fortress 2", Engine::new(440), 27015);
|
||||
game_query_mod!(
|
||||
tfc,
|
||||
|
|
@ -132,3 +148,4 @@ game_query_mod!(
|
|||
}
|
||||
);
|
||||
game_query_mod!(vrising, "V Rising", Engine::new(1_604_030), 27016);
|
||||
game_query_mod!(zps, "Zombie Panic: Source", Engine::new(17_500), 27015);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
//!
|
||||
//! ## Using a game definition
|
||||
//! ```
|
||||
//! use gamedig::games::{GAMES, query};
|
||||
//! use gamedig::{GAMES, query};
|
||||
//!
|
||||
//! let game = GAMES.get("teamfortress2").unwrap(); // Get a game definition, the full list can be found in src/games/mod.rs
|
||||
//! let response = query(game, &"127.0.0.1".parse().unwrap(), None); // None will use the default port
|
||||
|
|
@ -51,5 +51,10 @@ pub mod capture;
|
|||
pub use errors::*;
|
||||
#[cfg(feature = "games")]
|
||||
pub use games::*;
|
||||
#[cfg(feature = "games")]
|
||||
pub use query::*;
|
||||
#[cfg(feature = "services")]
|
||||
pub use services::*;
|
||||
|
||||
// Re-export types needed to call games::query::query in the root
|
||||
pub use protocols::types::{ExtraRequestSettings, TimeoutSettings};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::collections::HashMap;
|
|||
pub fn has_password(server_vars: &mut HashMap<String, String>) -> GDResult<bool> {
|
||||
let password_value = server_vars
|
||||
.remove("password")
|
||||
.ok_or(GDErrorKind::PacketBad.context("Missing password (exists) field"))?
|
||||
.ok_or_else(|| GDErrorKind::PacketBad.context("Missing password (exists) field"))?
|
||||
.to_lowercase();
|
||||
|
||||
if let Ok(has) = password_value.parse::<bool>() {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ fn get_server_values_impl(socket: &mut UdpSocket) -> GDResult<HashMap<String, St
|
|||
let key = splited[position].clone();
|
||||
let value = splited
|
||||
.get(position + 1)
|
||||
.map_or_else(String::new, |v| v.clone());
|
||||
.map_or_else(String::new, Clone::clone);
|
||||
|
||||
server_values.insert(key, value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ fn parse_players_and_teams(packets: Vec<Vec<u8>>) -> GDResult<(Vec<Player>, Vec<
|
|||
}
|
||||
|
||||
let entry_data = data.get_mut(offset).ok_or(PacketBad)?;
|
||||
entry_data.insert(field_name.to_string(), item);
|
||||
entry_data.insert((*field_name).to_string(), item);
|
||||
|
||||
offset += 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ fn get_server_values(bufferer: &mut Buffer<LittleEndian>) -> GDResult<HashMap<St
|
|||
|
||||
if let Some(k) = key {
|
||||
if let Some(v) = value {
|
||||
vars.insert(k.to_string(), v.to_string());
|
||||
vars.insert((*k).to_string(), (*v).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,23 +116,23 @@ pub fn client_query<Client: QuakeClient>(
|
|||
Ok(Response {
|
||||
name: server_vars
|
||||
.remove("hostname")
|
||||
.or(server_vars.remove("sv_hostname"))
|
||||
.or_else(|| server_vars.remove("sv_hostname"))
|
||||
.ok_or(GDErrorKind::PacketBad)?,
|
||||
map: server_vars
|
||||
.remove("mapname")
|
||||
.or(server_vars.remove("map"))
|
||||
.or_else(|| server_vars.remove("map"))
|
||||
.ok_or(GDErrorKind::PacketBad)?,
|
||||
players_online: players.len() as u8,
|
||||
players_maximum: server_vars
|
||||
.remove("maxclients")
|
||||
.or(server_vars.remove("sv_maxclients"))
|
||||
.or_else(|| server_vars.remove("sv_maxclients"))
|
||||
.ok_or(GDErrorKind::PacketBad)?
|
||||
.parse()
|
||||
.map_err(|e| TypeParse.context(e))?,
|
||||
players,
|
||||
game_version: server_vars
|
||||
.remove("version")
|
||||
.or(server_vars.remove("*version")),
|
||||
.or_else(|| server_vars.remove("*version")),
|
||||
unused_entries: server_vars,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub enum ProprietaryProtocol {
|
|||
Minecraft(Option<minecraft::types::Server>),
|
||||
FFOW,
|
||||
JC2M,
|
||||
Savage2,
|
||||
Mindustry,
|
||||
}
|
||||
|
||||
/// Enumeration of all valid protocol types
|
||||
|
|
@ -41,6 +43,8 @@ pub enum GenericResponse<'a> {
|
|||
Valve(&'a valve::Response),
|
||||
Unreal2(&'a unreal2::Response),
|
||||
#[cfg(feature = "games")]
|
||||
Mindustry(&'a crate::games::mindustry::types::ServerData),
|
||||
#[cfg(feature = "games")]
|
||||
Minecraft(minecraft::VersionedResponse<'a>),
|
||||
#[cfg(feature = "games")]
|
||||
TheShip(&'a crate::games::theship::Response),
|
||||
|
|
@ -48,6 +52,8 @@ pub enum GenericResponse<'a> {
|
|||
FFOW(&'a crate::games::ffow::Response),
|
||||
#[cfg(feature = "games")]
|
||||
JC2M(&'a crate::games::jc2m::Response),
|
||||
#[cfg(feature = "games")]
|
||||
Savage2(&'a crate::games::savage2::Response),
|
||||
}
|
||||
|
||||
/// All player types
|
||||
|
|
@ -228,33 +234,33 @@ impl TimeoutSettings {
|
|||
|
||||
/// Get the number of retries if there are timeout settings else fall back
|
||||
/// to the default
|
||||
pub const fn get_retries_or_default(timeout_settings: &Option<TimeoutSettings>) -> usize {
|
||||
pub const fn get_retries_or_default(timeout_settings: &Option<Self>) -> usize {
|
||||
if let Some(timeout_settings) = timeout_settings {
|
||||
timeout_settings.get_retries()
|
||||
} else {
|
||||
TimeoutSettings::const_default().get_retries()
|
||||
Self::const_default().get_retries()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the read and write durations if there are timeout settings else fall
|
||||
/// back to the defaults
|
||||
pub const fn get_read_and_write_or_defaults(
|
||||
timeout_settings: &Option<TimeoutSettings>,
|
||||
timeout_settings: &Option<Self>,
|
||||
) -> (Option<Duration>, Option<Duration>) {
|
||||
if let Some(timeout_settings) = timeout_settings {
|
||||
(timeout_settings.get_read(), timeout_settings.get_write())
|
||||
} else {
|
||||
let default = TimeoutSettings::const_default();
|
||||
let default = Self::const_default();
|
||||
(default.get_read(), default.get_write())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the connect duration given timeout settings or get the default.
|
||||
pub const fn get_connect_or_default(timeout_settings: &Option<TimeoutSettings>) -> Option<Duration> {
|
||||
pub const fn get_connect_or_default(timeout_settings: &Option<Self>) -> Option<Duration> {
|
||||
if let Some(timeout_settings) = timeout_settings {
|
||||
timeout_settings.get_connect()
|
||||
} else {
|
||||
TimeoutSettings::const_default().get_connect()
|
||||
Self::const_default().get_connect()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -337,22 +343,22 @@ impl ExtraRequestSettings {
|
|||
}
|
||||
/// [Sets protocol
|
||||
/// version](ExtraRequestSettings#structfield.protocol_version)
|
||||
pub fn set_protocol_version(mut self, protocol_version: i32) -> Self {
|
||||
pub const fn set_protocol_version(mut self, protocol_version: i32) -> Self {
|
||||
self.protocol_version = Some(protocol_version);
|
||||
self
|
||||
}
|
||||
/// [Sets gather players](ExtraRequestSettings#structfield.gather_players)
|
||||
pub fn set_gather_players(mut self, gather_players: bool) -> Self {
|
||||
pub const fn set_gather_players(mut self, gather_players: bool) -> Self {
|
||||
self.gather_players = Some(gather_players);
|
||||
self
|
||||
}
|
||||
/// [Sets gather rules](ExtraRequestSettings#structfield.gather_rules)
|
||||
pub fn set_gather_rules(mut self, gather_rules: bool) -> Self {
|
||||
pub const fn set_gather_rules(mut self, gather_rules: bool) -> Self {
|
||||
self.gather_rules = Some(gather_rules);
|
||||
self
|
||||
}
|
||||
/// [Sets check app ID](ExtraRequestSettings#structfield.check_app_id)
|
||||
pub fn set_check_app_id(mut self, check_app_id: bool) -> Self {
|
||||
pub const fn set_check_app_id(mut self, check_app_id: bool) -> Self {
|
||||
self.check_app_id = Some(check_app_id);
|
||||
self
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ pub(crate) struct Unreal2Protocol {
|
|||
impl Unreal2Protocol {
|
||||
pub fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
|
||||
let socket = UdpSocket::new(address, &timeout_settings)?;
|
||||
let retry_count = timeout_settings
|
||||
.as_ref()
|
||||
.map(|t| t.get_retries())
|
||||
.unwrap_or_else(|| TimeoutSettings::default().get_retries());
|
||||
let retry_count = timeout_settings.as_ref().map_or_else(
|
||||
|| TimeoutSettings::default().get_retries(),
|
||||
TimeoutSettings::get_retries,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
socket,
|
||||
|
|
@ -209,7 +209,7 @@ impl StringDecoder for Unreal2StringDecoder {
|
|||
let mut ucs2 = false;
|
||||
let mut length: usize = (*data
|
||||
.first()
|
||||
.ok_or(PacketBad.context("Tried to decode string without length"))?)
|
||||
.ok_or_else(|| PacketBad.context("Tried to decode string without length"))?)
|
||||
.into();
|
||||
|
||||
let mut start = 0;
|
||||
|
|
@ -225,7 +225,7 @@ impl StringDecoder for Unreal2StringDecoder {
|
|||
// For UCS-2 strings, some unreal 2 games randomly insert an extra 0x01 here,
|
||||
// not included in the length. Skip it if present (hopefully this never happens
|
||||
// legitimately)
|
||||
if let Some(1) = data[start ..].first() {
|
||||
if data[start ..].first() == Some(&1) {
|
||||
start += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ pub struct ServerInfo {
|
|||
|
||||
impl ServerInfo {
|
||||
pub fn parse<B: ByteOrder>(buffer: &mut Buffer<B>) -> GDResult<Self> {
|
||||
Ok(ServerInfo {
|
||||
Ok(Self {
|
||||
server_id: buffer.read()?,
|
||||
ip: buffer.read_string::<Unreal2StringDecoder>(None)?,
|
||||
game_port: buffer.read()?,
|
||||
|
|
@ -118,7 +118,7 @@ impl Players {
|
|||
/// Pre-allocate the vectors inside the players struct based on the provided
|
||||
/// capacity.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Players {
|
||||
Self {
|
||||
players: Vec::with_capacity(capacity),
|
||||
// Allocate half as many bots as we don't expect there to be as many
|
||||
bots: Vec::with_capacity(capacity / 2),
|
||||
|
|
@ -234,7 +234,7 @@ impl GatheringSettings {
|
|||
}
|
||||
|
||||
impl Default for GatheringSettings {
|
||||
fn default() -> Self { GatheringSettings::default() }
|
||||
fn default() -> Self { Self::default() }
|
||||
}
|
||||
|
||||
impl From<ExtraRequestSettings> for GatheringSettings {
|
||||
|
|
|
|||
|
|
@ -127,10 +127,10 @@ static PACKET_SIZE: usize = 6144;
|
|||
impl ValveProtocol {
|
||||
pub fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
|
||||
let socket = UdpSocket::new(address, &timeout_settings)?;
|
||||
let retry_count = timeout_settings
|
||||
.as_ref()
|
||||
.map(|t| t.get_retries())
|
||||
.unwrap_or_else(|| TimeoutSettings::default().get_retries());
|
||||
let retry_count = timeout_settings.as_ref().map_or_else(
|
||||
|| TimeoutSettings::default().get_retries(),
|
||||
|t| t.get_retries(),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
socket,
|
||||
|
|
@ -271,7 +271,7 @@ impl ValveProtocol {
|
|||
has_password,
|
||||
vac_secured,
|
||||
the_ship: None,
|
||||
game_version: "".to_string(), // a version field only for the mod
|
||||
game_version: String::new(), // a version field only for the mod
|
||||
extra_data: None,
|
||||
is_mod,
|
||||
mod_data,
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ impl GatheringSettings {
|
|||
}
|
||||
|
||||
impl Default for GatheringSettings {
|
||||
fn default() -> Self { GatheringSettings::default() }
|
||||
fn default() -> Self { Self::default() }
|
||||
}
|
||||
|
||||
impl From<ExtraRequestSettings> for GatheringSettings {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ pub fn default_master_address() -> SocketAddr {
|
|||
fn construct_payload(region: Region, filters: &Option<SearchFilters>, last_ip: &str, last_port: u16) -> Vec<u8> {
|
||||
let filters_bytes: Vec<u8> = filters
|
||||
.as_ref()
|
||||
.map_or_else(|| vec![0x00], |f| f.to_bytes());
|
||||
.map_or_else(|| vec![0x00], SearchFilters::to_bytes);
|
||||
|
||||
let region_byte = &[region as u8];
|
||||
|
||||
|
|
|
|||
|
|
@ -75,11 +75,10 @@ pub struct TcpSocketImpl {
|
|||
|
||||
impl Socket for TcpSocketImpl {
|
||||
fn new(address: &SocketAddr, timeout_settings: &Option<TimeoutSettings>) -> GDResult<Self> {
|
||||
let socket = if let Some(timeout) = TimeoutSettings::get_connect_or_default(timeout_settings) {
|
||||
net::TcpStream::connect_timeout(address, timeout)
|
||||
} else {
|
||||
net::TcpStream::connect(address)
|
||||
};
|
||||
let socket = TimeoutSettings::get_connect_or_default(timeout_settings).map_or_else(
|
||||
|| net::TcpStream::connect(address),
|
||||
|timeout| net::TcpStream::connect_timeout(address, timeout),
|
||||
);
|
||||
|
||||
let socket = Self {
|
||||
socket: socket.map_err(|e| SocketConnect.context(e))?,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue