From 8098136d0983c72b4d0c0545b59d28fd3828b4be Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 15 Oct 2022 22:20:01 +0300 Subject: [PATCH 001/597] Initial valve setup and tf2 game setup --- examples/tf2.rs | 14 +++++ src/errors.rs | 15 +++++ src/games/mod.rs | 4 ++ src/games/tf2.rs | 14 +++++ src/lib.rs | 18 ++---- src/protocol.rs | 7 +++ src/protocols/mod.rs | 2 + src/protocols/valve.rs | 137 +++++++++++++++++++++++++++++++++++++++++ src/utils.rs | 20 ++++++ tests/test.rs | 15 +++++ 10 files changed, 234 insertions(+), 12 deletions(-) create mode 100644 examples/tf2.rs create mode 100644 src/errors.rs create mode 100644 src/games/mod.rs create mode 100644 src/games/tf2.rs create mode 100644 src/protocol.rs create mode 100644 src/protocols/mod.rs create mode 100644 src/protocols/valve.rs create mode 100644 src/utils.rs create mode 100644 tests/test.rs diff --git a/examples/tf2.rs b/examples/tf2.rs new file mode 100644 index 0000000..99c3741 --- /dev/null +++ b/examples/tf2.rs @@ -0,0 +1,14 @@ + +use gamedig::TF2; + +fn main() { + let response = TF2::query("5.15.202.107", None); + match response { + Err(_) => println!("fuck"), + Ok(r) => { + println!("{:?}", r); + + () + } + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..1b0623e --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,15 @@ +use core::fmt; +use std::fmt::Formatter; + +#[derive(Debug, Clone)] +pub enum GDError { + IDK(String) +} + +impl fmt::Display for GDError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + GDError::IDK(details) => write!(f, "IDK: {details}") + } + } +} diff --git a/src/games/mod.rs b/src/games/mod.rs new file mode 100644 index 0000000..eee99b5 --- /dev/null +++ b/src/games/mod.rs @@ -0,0 +1,4 @@ + +pub mod tf2; + +pub use tf2::*; diff --git a/src/games/tf2.rs b/src/games/tf2.rs new file mode 100644 index 0000000..83c1452 --- /dev/null +++ b/src/games/tf2.rs @@ -0,0 +1,14 @@ +use crate::errors::GDError; +use crate::protocol::Protocol; +use crate::protocols::valve::{Response, ValveProtocol}; + +pub struct TF2; + +impl TF2 { + pub fn query(address: &str, port: Option) -> Result { + ValveProtocol::query(address, match port { + None => 27015, + Some(port) => port + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7d12d9a..775b4e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,8 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} -#[cfg(test)] -mod tests { - use super::*; +mod errors; +mod protocol; +mod protocols; +mod utils; +pub mod games; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub use games::*; diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..cadc7cc --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,7 @@ +use crate::errors::GDError; + +pub trait Protocol { + type Response; + + fn query(address: &str, port: u16) -> Result; +} diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs new file mode 100644 index 0000000..3deed53 --- /dev/null +++ b/src/protocols/mod.rs @@ -0,0 +1,2 @@ + +pub mod valve; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs new file mode 100644 index 0000000..5fa00cd --- /dev/null +++ b/src/protocols/valve.rs @@ -0,0 +1,137 @@ +use std::net::UdpSocket; +use crate::errors::GDError; +use crate::protocol::Protocol; +use crate::utils::{combine_two_u8, complete_address, concat_u8, find_null_in_array}; + +#[derive(Debug)] +pub enum Server { + Dedicated, + NonDedicated, + SourceTV +} + +#[derive(Debug)] +pub enum Environment { + Linux, + Windows, + Mac +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub map: String, + pub name: String, + pub folder: String, + pub game: String, + pub id: u16, + pub players: u8, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub environment_type: Environment, + pub has_password: bool, + pub vac_secured: bool +} + +pub enum Request { + A2sInfo(Option<[u8; 4]>) +} + +pub struct ValveProtocol { + socket: UdpSocket, + complete_address: String +} + +impl ValveProtocol { + fn new(address: &str, port: u16) -> Self { + Self { + socket: UdpSocket::bind("0.0.0.0:0").unwrap(), + complete_address: complete_address(address, port) + } + } + + pub fn do_request(&self, kind: Request, data_packet: Option<&[u8]>) -> bool { + let default: Vec = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, + 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; + + let request_kind_packet = match kind { + Request::A2sInfo(challenge) => match challenge { + None => default, + Some(value) => concat_u8(&default, &value) + } + }; + + let mut packet = request_kind_packet; + match data_packet { + None => (), + Some(data) => packet.extend_from_slice(data) + } + + match self.socket.send_to(&packet, &self.complete_address) { + Err(_) => false, + Ok(_) => true + } + } + + pub fn receive(&self) -> Vec { + self.receive_with_size(64) + } + + pub fn receive_with_size(&self, buffer_size: usize) -> Vec { + let mut buffer: Vec = vec![0; buffer_size]; + let (amt, _) = self.socket.recv_from(&mut buffer.as_mut_slice()).unwrap(); + buffer[..amt].to_vec() + } +} + +impl Protocol for ValveProtocol { + type Response = Response; + + fn query(address: &str, port: u16) -> Result { + let client = ValveProtocol::new(address, port); + + client.do_request(Request::A2sInfo(None), None); + let mut buf = client.receive(); + + if buf[4] == 0x41 { + client.do_request(Request::A2sInfo(Some([buf[5], buf[6], buf[7], buf[8]])), None); + } + + buf = client.receive_with_size(256); + println!("{:x?}", &buf); + + let name_nul_pos = find_null_in_array(&mut buf); + let map_nul_pos = name_nul_pos + 1 + find_null_in_array(&mut buf[name_nul_pos + 1..]); + let folder_nul_pos = map_nul_pos + 1 + find_null_in_array(&mut buf[map_nul_pos + 1..]); + let game_nul_pos = folder_nul_pos + 1 + find_null_in_array(&mut buf[folder_nul_pos + 1..]); + + let server_type = match buf[game_nul_pos + 6] as char { + 'd' => Server::Dedicated, + 'l' => Server::NonDedicated, + _ => Server::SourceTV + }; + + let environment_type = match buf[game_nul_pos + 7] as char { + 'l' => Environment::Linux, + 'w' => Environment::Windows, + _ => Environment::Mac + }; + + Ok(Response { + protocol: buf[5], + name: String::from_utf8(Vec::from(&mut buf[6..name_nul_pos])).expect("cacat"), + map: String::from_utf8(Vec::from(&mut buf[name_nul_pos + 1..map_nul_pos])).expect("cacat"), + folder: String::from_utf8(Vec::from(&mut buf[map_nul_pos + 1..folder_nul_pos])).expect("cacat"), + game: String::from_utf8(Vec::from(&mut buf[folder_nul_pos + 1..game_nul_pos])).expect("cacat"), + id: combine_two_u8(buf[game_nul_pos + 2], buf[game_nul_pos + 1]), + players: buf[game_nul_pos + 3], + max_players: buf[game_nul_pos + 4], + bots: buf[game_nul_pos + 5], + server_type, + environment_type, + has_password: buf[game_nul_pos + 8] == 1, + vac_secured: buf[game_nul_pos + 9] != 0 + }) + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..c32b35c --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,20 @@ +use std::ops::Add; + +pub fn concat_u8(first: &[u8], second: &[u8]) -> Vec { + [first, second].concat() +} + +pub fn find_null_in_array(arr: &[u8]) -> usize { + match arr.iter().position(|&x| x == 0) { + None => arr.len(), + Some(position) => position + } +} + +pub fn complete_address(address: &str, port: u16) -> String { + String::from(address.to_owned() + ":").add(&*port.to_string()) +} + +pub fn combine_two_u8(high: u8, low: u8) -> u16 { + ((high as u16) << 8) | low as u16 +} diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..776744b --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,15 @@ + +#[cfg(test)] +mod tests { + use gamedig::protocols::valve::ValveProtocol; + + #[test] + fn tf2() { + let response = ValveProtocol::query("5.15.202.107", 27015); + match response { + Err(_) => println!("fuck"), + Ok(r) => println!("{}", r.name) + } + assert_eq!(4, 4); + } +} From c9eb725a51d73c4de4e2f6fe4e505b2838220d3f Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 02:42:17 +0300 Subject: [PATCH 002/597] Almost completed the valve protocol --- examples/tf2.rs | 10 +-- src/games/tf2.rs | 3 +- src/lib.rs | 1 - src/protocol.rs | 7 -- src/protocols/valve.rs | 147 ++++++++++++++++++++++++++++++++++------- src/utils.rs | 10 ++- 6 files changed, 135 insertions(+), 43 deletions(-) delete mode 100644 src/protocol.rs diff --git a/examples/tf2.rs b/examples/tf2.rs index 99c3741..1f8b659 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -2,13 +2,9 @@ use gamedig::TF2; fn main() { - let response = TF2::query("5.15.202.107", None); + let response = TF2::query("91.216.250.10", None); match response { - Err(_) => println!("fuck"), - Ok(r) => { - println!("{:?}", r); - - () - } + Err(error) => println!("Couldn't query, error: {}", error), + Ok(r) => println!("{:?}", r) } } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 83c1452..d349f0f 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,5 +1,4 @@ use crate::errors::GDError; -use crate::protocol::Protocol; use crate::protocols::valve::{Response, ValveProtocol}; pub struct TF2; @@ -9,6 +8,6 @@ impl TF2 { ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }) + }, false) } } diff --git a/src/lib.rs b/src/lib.rs index 775b4e0..482d0df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ mod errors; -mod protocol; mod protocols; mod utils; pub mod games; diff --git a/src/protocol.rs b/src/protocol.rs deleted file mode 100644 index cadc7cc..0000000 --- a/src/protocol.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::errors::GDError; - -pub trait Protocol { - type Response; - - fn query(address: &str, port: u16) -> Result; -} diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 5fa00cd..64f9fcc 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,7 +1,6 @@ use std::net::UdpSocket; use crate::errors::GDError; -use crate::protocol::Protocol; -use crate::utils::{combine_two_u8, complete_address, concat_u8, find_null_in_array}; +use crate::utils::{combine_two_u8, complete_address, concat_u8, find_null_in_array, get_u64_from_buf}; #[derive(Debug)] pub enum Server { @@ -31,7 +30,27 @@ pub struct Response { pub server_type: Server, pub environment_type: Environment, pub has_password: bool, - pub vac_secured: bool + pub vac_secured: bool, + pub the_ship: Option, + pub version: String, + pub extra_data: Option +} + +#[derive(Debug)] +pub struct TheShip { + pub mode: u8, + pub witnesses: u8, + pub duration: u8 +} + +#[derive(Debug)] +pub struct ExtraData { + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub game_id: Option } pub enum Request { @@ -43,6 +62,8 @@ pub struct ValveProtocol { complete_address: String } +static default_packet_size: usize = 256; + impl ValveProtocol { fn new(address: &str, port: u16) -> Self { Self { @@ -85,53 +106,129 @@ impl ValveProtocol { } } -impl Protocol for ValveProtocol { - type Response = Response; - - fn query(address: &str, port: u16) -> Result { +impl ValveProtocol { + pub(crate) fn query(address: &str, port: u16, has_the_ship: bool) -> Result { let client = ValveProtocol::new(address, port); client.do_request(Request::A2sInfo(None), None); - let mut buf = client.receive(); + let mut buf = client.receive_with_size(default_packet_size); if buf[4] == 0x41 { client.do_request(Request::A2sInfo(Some([buf[5], buf[6], buf[7], buf[8]])), None); + buf = client.receive_with_size(default_packet_size); } - buf = client.receive_with_size(256); println!("{:x?}", &buf); - let name_nul_pos = find_null_in_array(&mut buf); - let map_nul_pos = name_nul_pos + 1 + find_null_in_array(&mut buf[name_nul_pos + 1..]); - let folder_nul_pos = map_nul_pos + 1 + find_null_in_array(&mut buf[map_nul_pos + 1..]); - let game_nul_pos = folder_nul_pos + 1 + find_null_in_array(&mut buf[folder_nul_pos + 1..]); + let name_null_pos = find_null_in_array(&mut buf); + let map_null_pos = name_null_pos + 1 + find_null_in_array(&mut buf[name_null_pos + 1..]); + let folder_null_pos = map_null_pos + 1 + find_null_in_array(&mut buf[map_null_pos + 1..]); + let game_null_pos = folder_null_pos + 1 + find_null_in_array(&mut buf[folder_null_pos + 1..]); - let server_type = match buf[game_nul_pos + 6] as char { + let server_type = match buf[game_null_pos + 6] as char { 'd' => Server::Dedicated, 'l' => Server::NonDedicated, _ => Server::SourceTV }; - let environment_type = match buf[game_nul_pos + 7] as char { + let environment_type = match buf[game_null_pos + 7] as char { 'l' => Environment::Linux, 'w' => Environment::Windows, _ => Environment::Mac }; + let mut the_ship_index = game_null_pos + 10; + let the_ship = match has_the_ship { + false => None, + true => { + let ship = TheShip { + mode: buf[the_ship_index], + witnesses: buf[the_ship_index + 1], + duration: buf[the_ship_index + 2] + }; + the_ship_index = the_ship_index + 3; + Some(ship) + } + }; + + let version_null_pos = the_ship_index + find_null_in_array(&mut buf[the_ship_index..]); + let extra_data = match buf.get(version_null_pos + 1) { + None => None, + Some(value) => { + let mut last_edf_position = version_null_pos + 2; + let edf_port = match (value & 0x80) > 0 { + false => None, + true => { + let p = combine_two_u8(buf[last_edf_position + 1], buf[last_edf_position]); + last_edf_position = last_edf_position + 2; + Some(p) + } + }; + + let steam_id = match (value & 0x10) > 0 { //doesnt work? + false => None, + true => { + let p = get_u64_from_buf(&buf[last_edf_position..]); + last_edf_position = last_edf_position + 8; + Some(p) + } + }; + + let (tv_port, tv_name) = match (value & 0x40) > 0 { + false => (None, None), + true => { + let port = combine_two_u8(buf[last_edf_position + 1], buf[last_edf_position]); + last_edf_position = last_edf_position + 2; + let tv_name_null_pos = last_edf_position + find_null_in_array(&buf[last_edf_position..]); + let tv_name = String::from_utf8(Vec::from(&buf[last_edf_position..tv_name_null_pos])).expect("cacat"); + last_edf_position = tv_name_null_pos + 1; + (Some(port), Some(tv_name)) + } + }; + + let keywords = match (value & 0x20) > 0 { + false => None, + true => { + let kws_null_pos = last_edf_position + find_null_in_array(&buf[last_edf_position..]); + let kws = String::from_utf8(Vec::from(&buf[last_edf_position..kws_null_pos])).expect("cacat"); + last_edf_position = kws_null_pos + 1; + Some(kws) + } + }; + + let game_id = match (value & 0x01) > 0 { + false => None, + true => Some(get_u64_from_buf(&buf[last_edf_position..])) + }; + + Some(ExtraData { + port: edf_port, + steam_id, + tv_port, + tv_name, + keywords, + game_id + }) + } + }; + Ok(Response { protocol: buf[5], - name: String::from_utf8(Vec::from(&mut buf[6..name_nul_pos])).expect("cacat"), - map: String::from_utf8(Vec::from(&mut buf[name_nul_pos + 1..map_nul_pos])).expect("cacat"), - folder: String::from_utf8(Vec::from(&mut buf[map_nul_pos + 1..folder_nul_pos])).expect("cacat"), - game: String::from_utf8(Vec::from(&mut buf[folder_nul_pos + 1..game_nul_pos])).expect("cacat"), - id: combine_two_u8(buf[game_nul_pos + 2], buf[game_nul_pos + 1]), - players: buf[game_nul_pos + 3], - max_players: buf[game_nul_pos + 4], - bots: buf[game_nul_pos + 5], + name: String::from_utf8(Vec::from(&mut buf[6..name_null_pos])).expect("cacat"), + map: String::from_utf8(Vec::from(&mut buf[name_null_pos + 1..map_null_pos])).expect("cacat"), + folder: String::from_utf8(Vec::from(&mut buf[map_null_pos + 1..folder_null_pos])).expect("cacat"), + game: String::from_utf8(Vec::from(&mut buf[folder_null_pos + 1..game_null_pos])).expect("cacat"), + id: combine_two_u8(buf[game_null_pos + 2], buf[game_null_pos + 1]), + players: buf[game_null_pos + 3], + max_players: buf[game_null_pos + 4], + bots: buf[game_null_pos + 5], server_type, environment_type, - has_password: buf[game_nul_pos + 8] == 1, - vac_secured: buf[game_nul_pos + 9] != 0 + has_password: buf[game_null_pos + 8] == 1, + vac_secured: buf[game_null_pos + 9] != 0, + the_ship, + version: String::from_utf8(Vec::from(&mut buf[the_ship_index..version_null_pos])).expect("cacat"), + extra_data }) } } diff --git a/src/utils.rs b/src/utils.rs index c32b35c..2428578 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,5 +16,13 @@ pub fn complete_address(address: &str, port: u16) -> String { } pub fn combine_two_u8(high: u8, low: u8) -> u16 { - ((high as u16) << 8) | low as u16 + u16::from_be_bytes([high, low]) +} + +pub fn combine_eight_u8(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) -> u64 { + u64::from_be_bytes([a, b, c, d, e, f, g, h]) +} + +pub fn get_u64_from_buf(buf: &[u8]) -> u64 { + combine_eight_u8(buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]) } From 73c8ade3a2648cf9a640be5655d5921655266294 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 03:23:18 +0300 Subject: [PATCH 003/597] Modified public presentation files --- Cargo.toml | 7 +++++-- GAMES.md | 7 +++++++ README.md | 32 +++++++++++++++++++++++++++++++- examples/tf2.rs | 2 +- src/protocols/valve.rs | 12 ++++-------- src/utils.rs | 29 +++++++++++++++++++++++++++++ tests/test.rs | 15 --------------- 7 files changed, 77 insertions(+), 27 deletions(-) create mode 100644 GAMES.md delete mode 100644 tests/test.rs diff --git a/Cargo.toml b/Cargo.toml index 4c4f940..fc2e43a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,16 @@ name = "gamedig" version = "0.0.0" edition = "2021" -authors = ["CosminPerRam [cosmin.p@live.com]", "mmorrisontx [https://github.com/mmorrisontx]"] +authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license-file = "LICENSE.md" description = "Check out servers with this." homepage = "https://github.com/CosminPerRam/rust-gamedig" -documentation = "https://github.com/CosminPerRam/rust-gamedig" +documentation = "https://docs.rs/gamedig/latest/gamedig/" repository = "https://github.com/CosminPerRam/rust-gamedig" readme = "README.md" keywords = ["server", "valve", "games", "checker", "status"] +[package.metadata] +msrv = "1.58.1" + [dependencies] diff --git a/GAMES.md b/GAMES.md new file mode 100644 index 0000000..13fd16b --- /dev/null +++ b/GAMES.md @@ -0,0 +1,7 @@ +# Supported games: +| Type ID | Name | Notes | +|---------|-----------------|-------| +| TF2 | Team Fortress 2 | | + +# Planned to add support: +All Valve titles. diff --git a/README.md b/README.md index c4c8038..4be53e8 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -# rust-gamedig \ No newline at end of file +# rust-gamedig +rust-GameDig is a game server/services query library, capable of querying the status of many games/services, this library brings what [node-GameDig](https://github.com/gamedig/node-gamedig) does, to pure Rust! + +MSRV is `1.58.1` and the code is cross-platform. + +# Example +Basic usage of the library is: +```rust +use gamedig::TF2; + +fn main() { + let response = TF2::query("91.216.250.10", None); + //query your favorite game/protocol/service, some might come with different parameters + //here its just the IP and the port (if None, its gonna be the default from the protocol) + + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} +``` +To see more examples, see the [examples](examples) folder. + +# Documentation +The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). + +# Games List +To see the supported games, see [GAMES](GAMES.md). + +# Contributing +If you want see your favorite game/service being supported here, open an issue (or do a pull request if you want to implement it yourself)! diff --git a/examples/tf2.rs b/examples/tf2.rs index 1f8b659..e77a699 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -4,7 +4,7 @@ use gamedig::TF2; fn main() { let response = TF2::query("91.216.250.10", None); match response { - Err(error) => println!("Couldn't query, error: {}", error), + Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) } } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 64f9fcc..42e8151 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -62,7 +62,7 @@ pub struct ValveProtocol { complete_address: String } -static default_packet_size: usize = 256; +static DEFAULT_PACKET_SIZE: usize = 256; impl ValveProtocol { fn new(address: &str, port: u16) -> Self { @@ -95,11 +95,7 @@ impl ValveProtocol { } } - pub fn receive(&self) -> Vec { - self.receive_with_size(64) - } - - pub fn receive_with_size(&self, buffer_size: usize) -> Vec { + pub fn receive(&self, buffer_size: usize) -> Vec { let mut buffer: Vec = vec![0; buffer_size]; let (amt, _) = self.socket.recv_from(&mut buffer.as_mut_slice()).unwrap(); buffer[..amt].to_vec() @@ -111,11 +107,11 @@ impl ValveProtocol { let client = ValveProtocol::new(address, port); client.do_request(Request::A2sInfo(None), None); - let mut buf = client.receive_with_size(default_packet_size); + let mut buf = client.receive(DEFAULT_PACKET_SIZE); if buf[4] == 0x41 { client.do_request(Request::A2sInfo(Some([buf[5], buf[6], buf[7], buf[8]])), None); - buf = client.receive_with_size(default_packet_size); + buf = client.receive(DEFAULT_PACKET_SIZE); } println!("{:x?}", &buf); diff --git a/src/utils.rs b/src/utils.rs index 2428578..496a23d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -26,3 +26,32 @@ pub fn combine_eight_u8(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) pub fn get_u64_from_buf(buf: &[u8]) -> u64 { combine_eight_u8(buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]) } + +#[cfg(test)] +mod utils { + use super::*; + + #[test] + fn concat_u8_test() { + let a: [u8; 2] = [1, 2]; + let b: [u8; 2] = [3, 4]; + let combined = concat_u8(&a, &b); + assert_eq!(a[0], combined[0]); + assert_eq!(a[1], combined[1]); + assert_eq!(b[0], combined[2]); + assert_eq!(b[1], combined[3]); + } + + #[test] + fn find_null_in_array_test() { + let arr: [u8; 4] = [0x64, 0x32, 0x00, 0x20]; + assert_eq!(2, find_null_in_array(&arr)); + } + + #[test] + fn complete_address_test() { + let address = "192.168.0.1"; + let port = 27015; + assert_eq!(complete_address(address, port), "192.168.0.1:27015"); + } +} diff --git a/tests/test.rs b/tests/test.rs deleted file mode 100644 index 776744b..0000000 --- a/tests/test.rs +++ /dev/null @@ -1,15 +0,0 @@ - -#[cfg(test)] -mod tests { - use gamedig::protocols::valve::ValveProtocol; - - #[test] - fn tf2() { - let response = ValveProtocol::query("5.15.202.107", 27015); - match response { - Err(_) => println!("fuck"), - Ok(r) => println!("{}", r.name) - } - assert_eq!(4, 4); - } -} From 5870a52ce23d6194ca5ccac646b8d291f8ca67a8 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 03:25:41 +0300 Subject: [PATCH 004/597] Modified the README.md file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4be53e8..f9673f7 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ To see more examples, see the [examples](examples) folder. The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). # Games List -To see the supported games, see [GAMES](GAMES.md). +To see the supported (or the planned to support) games, see [GAMES](GAMES.md). # Contributing If you want see your favorite game/service being supported here, open an issue (or do a pull request if you want to implement it yourself)! From fa4e72f80aa408eb152dd44010198b69741d64a5 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 03:28:07 +0300 Subject: [PATCH 005/597] Bumped the Cargo.toml package version to mark the first usable version! --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fc2e43a..7447f84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.0.0" +version = "0.0.1" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license-file = "LICENSE.md" From 1fb399803341f4c51a55980d21c61a1df45c834b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 03:33:25 +0300 Subject: [PATCH 006/597] Let access of protocols and errors stuff --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 482d0df..59232a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,9 @@ -mod errors; -mod protocols; +pub mod errors; +pub mod protocols; mod utils; pub mod games; +pub use errors::*; +pub use protocols::*; pub use games::*; From 3ef9289dfbbbea1201819b9954bc29f0b9c432bb Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 03:45:08 +0300 Subject: [PATCH 007/597] Added the CHANGELOG.md file. --- CHANGELOG.md | 11 +++++++++++ README.md | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..82a9321 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ + +Who knows what the future holds... + +# 0.0.1 - 16/09/2022 +The first usable version of the crate, yay! +It brings: +- Initial implementation of the [valve server query protocol](https://developer.valvesoftware.com/wiki/Server_queries). +- Initial [Team Fortress 2](https://en.wikipedia.org/wiki/Team_Fortress_2) support. + +# 0.0.0 - 15/09/2022 +The first *markdown*, the crate is unusable as it doesn't contain anything helpful. diff --git a/README.md b/README.md index f9673f7..1b2a48b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ fn main() { To see more examples, see the [examples](examples) folder. # Documentation -The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). +The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). +Curious about the history and what changed between versions? you can see just that in the [CHANGELOG](CHANGELOG.md) file. # Games List To see the supported (or the planned to support) games, see [GAMES](GAMES.md). From 11964d530f0ab8409b99afbdd7ec17810c005038 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 13:10:48 +0300 Subject: [PATCH 008/597] Removed a comment that suggested that a feature doesnt work it turns out it was working, i just thought it wasnt. --- src/protocols/valve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 42e8151..0d684eb 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -161,7 +161,7 @@ impl ValveProtocol { } }; - let steam_id = match (value & 0x10) > 0 { //doesnt work? + let steam_id = match (value & 0x10) > 0 { false => None, true => { let p = get_u64_from_buf(&buf[last_edf_position..]); From c2742fbcf0d7c842db2d620f318f588b00428960 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 16:14:10 +0300 Subject: [PATCH 009/597] More readable code --- src/protocols/valve.rs | 98 ++++++++++++++++++++++-------------------- src/utils.rs | 30 ++++++++++++- 2 files changed, 79 insertions(+), 49 deletions(-) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 0d684eb..028f2d9 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,6 +1,6 @@ use std::net::UdpSocket; use crate::errors::GDError; -use crate::utils::{combine_two_u8, complete_address, concat_u8, find_null_in_array, get_u64_from_buf}; +use crate::utils::{combine_two_u8, complete_address, concat_u8, find_first_string, get_u64_from_buf}; #[derive(Debug)] pub enum Server { @@ -108,55 +108,62 @@ impl ValveProtocol { client.do_request(Request::A2sInfo(None), None); let mut buf = client.receive(DEFAULT_PACKET_SIZE); + let mut pos = 5; if buf[4] == 0x41 { - client.do_request(Request::A2sInfo(Some([buf[5], buf[6], buf[7], buf[8]])), None); + client.do_request(Request::A2sInfo(Some([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])), None); buf = client.receive(DEFAULT_PACKET_SIZE); } - println!("{:x?}", &buf); + let protocol = buf[5]; pos = pos + 1; - let name_null_pos = find_null_in_array(&mut buf); - let map_null_pos = name_null_pos + 1 + find_null_in_array(&mut buf[name_null_pos + 1..]); - let folder_null_pos = map_null_pos + 1 + find_null_in_array(&mut buf[map_null_pos + 1..]); - let game_null_pos = folder_null_pos + 1 + find_null_in_array(&mut buf[folder_null_pos + 1..]); + let name = find_first_string(&buf[pos..]); pos = pos + name.len() + 1; + let map = find_first_string(&buf[pos..]); pos = pos + map.len() + 1; + let folder = find_first_string(&buf[pos..]); pos = pos + folder.len() + 1; + let game = find_first_string(&buf[pos..]); pos = pos + game.len() + 1; - let server_type = match buf[game_null_pos + 6] as char { + let id = combine_two_u8(buf[pos + 1], buf[pos]); pos = pos + 2; + let players = buf[pos]; pos = pos + 1; + let max_players = buf[pos]; pos = pos + 1; + let bots = buf[pos]; pos = pos + 1; + + let server_type = match buf[pos] as char { 'd' => Server::Dedicated, 'l' => Server::NonDedicated, _ => Server::SourceTV - }; + }; pos = pos + 1; - let environment_type = match buf[game_null_pos + 7] as char { + let environment_type = match buf[pos] as char { 'l' => Environment::Linux, 'w' => Environment::Windows, _ => Environment::Mac - }; + }; pos = pos + 1; + + let has_password = buf[pos] == 1; pos = pos + 1; + let vac_secured = buf[pos] == 1; pos = pos + 1; - let mut the_ship_index = game_null_pos + 10; let the_ship = match has_the_ship { false => None, true => { let ship = TheShip { - mode: buf[the_ship_index], - witnesses: buf[the_ship_index + 1], - duration: buf[the_ship_index + 2] - }; - the_ship_index = the_ship_index + 3; + mode: buf[pos], + witnesses: buf[pos + 1], + duration: buf[pos + 2] + }; pos = pos + 3; Some(ship) } }; - let version_null_pos = the_ship_index + find_null_in_array(&mut buf[the_ship_index..]); - let extra_data = match buf.get(version_null_pos + 1) { + let version = find_first_string(&buf[pos..]); pos = pos + version.len() + 1; + + pos = pos + 1; //look ahead + let extra_data = match buf.get(pos - 1) { None => None, Some(value) => { - let mut last_edf_position = version_null_pos + 2; let edf_port = match (value & 0x80) > 0 { false => None, true => { - let p = combine_two_u8(buf[last_edf_position + 1], buf[last_edf_position]); - last_edf_position = last_edf_position + 2; + let p = combine_two_u8(buf[pos + 1], buf[pos]); pos = pos + 2; Some(p) } }; @@ -164,8 +171,7 @@ impl ValveProtocol { let steam_id = match (value & 0x10) > 0 { false => None, true => { - let p = get_u64_from_buf(&buf[last_edf_position..]); - last_edf_position = last_edf_position + 8; + let p = get_u64_from_buf(&buf[pos..]); pos = pos + 8; Some(p) } }; @@ -173,28 +179,26 @@ impl ValveProtocol { let (tv_port, tv_name) = match (value & 0x40) > 0 { false => (None, None), true => { - let port = combine_two_u8(buf[last_edf_position + 1], buf[last_edf_position]); - last_edf_position = last_edf_position + 2; - let tv_name_null_pos = last_edf_position + find_null_in_array(&buf[last_edf_position..]); - let tv_name = String::from_utf8(Vec::from(&buf[last_edf_position..tv_name_null_pos])).expect("cacat"); - last_edf_position = tv_name_null_pos + 1; - (Some(port), Some(tv_name)) + let tv_port = combine_two_u8(buf[pos + 1], buf[pos]); pos = pos + 2; + let tv_name = find_first_string(&buf[pos..]); pos = pos + tv_name.len() + 1; + (Some(tv_port), Some(tv_name)) } }; let keywords = match (value & 0x20) > 0 { false => None, true => { - let kws_null_pos = last_edf_position + find_null_in_array(&buf[last_edf_position..]); - let kws = String::from_utf8(Vec::from(&buf[last_edf_position..kws_null_pos])).expect("cacat"); - last_edf_position = kws_null_pos + 1; - Some(kws) + let keywords = find_first_string(&buf[pos..]); pos = pos + keywords.len() + 1; + Some(keywords) } }; let game_id = match (value & 0x01) > 0 { false => None, - true => Some(get_u64_from_buf(&buf[last_edf_position..])) + true => { + let game_id = get_u64_from_buf(&buf[pos..]); pos = pos + 8; + Some(game_id) + } }; Some(ExtraData { @@ -209,21 +213,21 @@ impl ValveProtocol { }; Ok(Response { - protocol: buf[5], - name: String::from_utf8(Vec::from(&mut buf[6..name_null_pos])).expect("cacat"), - map: String::from_utf8(Vec::from(&mut buf[name_null_pos + 1..map_null_pos])).expect("cacat"), - folder: String::from_utf8(Vec::from(&mut buf[map_null_pos + 1..folder_null_pos])).expect("cacat"), - game: String::from_utf8(Vec::from(&mut buf[folder_null_pos + 1..game_null_pos])).expect("cacat"), - id: combine_two_u8(buf[game_null_pos + 2], buf[game_null_pos + 1]), - players: buf[game_null_pos + 3], - max_players: buf[game_null_pos + 4], - bots: buf[game_null_pos + 5], + protocol, + name, + map, + folder, + game, + id, + players, + max_players, + bots, server_type, environment_type, - has_password: buf[game_null_pos + 8] == 1, - vac_secured: buf[game_null_pos + 9] != 0, + has_password, + vac_secured, the_ship, - version: String::from_utf8(Vec::from(&mut buf[the_ship_index..version_null_pos])).expect("cacat"), + version, extra_data }) } diff --git a/src/utils.rs b/src/utils.rs index 496a23d..ca4a0d6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,13 +4,17 @@ pub fn concat_u8(first: &[u8], second: &[u8]) -> Vec { [first, second].concat() } -pub fn find_null_in_array(arr: &[u8]) -> usize { +pub fn find_first_null(arr: &[u8]) -> usize { match arr.iter().position(|&x| x == 0) { None => arr.len(), Some(position) => position } } +pub fn find_first_string(arr: &[u8]) -> String { + arr.iter().take_while(|&&b| b != 0).map(|&e| e as char).collect::() +} + pub fn complete_address(address: &str, port: u16) -> String { String::from(address.to_owned() + ":").add(&*port.to_string()) } @@ -23,6 +27,28 @@ pub fn combine_eight_u8(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) u64::from_be_bytes([a, b, c, d, e, f, g, h]) } +pub mod buffer { + use super::*; + + pub fn get_u8(buf: &[u8], pos: usize) -> (u8, usize) { + (buf[pos], pos + 1) + } + + pub fn get_u16(buf: &[u8], pos: usize) -> (u16, usize) { + (combine_two_u8(buf[pos + 1], buf[pos]), pos + 2) + } + + pub fn get_u64(buf: &[u8], pos: usize) -> (u64, usize) { + (combine_eight_u8(buf[pos + 7], buf[pos + 6], buf[pos + 5], buf[pos + 4], buf[pos + 3], buf[pos + 2], buf[pos + 1], buf[pos]), pos + 8) + } + + pub fn get_string(buf: &[u8], pos: usize) -> (String, usize) { + let string = find_first_string(&buf[pos..]); + let string_size = string.len(); + (string, pos + string_size + 1) + } +} + pub fn get_u64_from_buf(buf: &[u8]) -> u64 { combine_eight_u8(buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]) } @@ -45,7 +71,7 @@ mod utils { #[test] fn find_null_in_array_test() { let arr: [u8; 4] = [0x64, 0x32, 0x00, 0x20]; - assert_eq!(2, find_null_in_array(&arr)); + assert_eq!(2, find_first_null(&arr)); } #[test] From 3a83588802224c2495db04d81dd0e20cb9c07d5c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 16 Oct 2022 17:55:48 +0300 Subject: [PATCH 010/597] Much more readable code! --- src/protocols/valve.rs | 174 ++++++++++++++--------------------------- src/utils.rs | 46 +++++------ 2 files changed, 78 insertions(+), 142 deletions(-) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 028f2d9..e1e2486 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,6 +1,6 @@ use std::net::UdpSocket; use crate::errors::GDError; -use crate::utils::{combine_two_u8, complete_address, concat_u8, find_first_string, get_u64_from_buf}; +use crate::utils::{buffer, complete_address, concat_u8}; #[derive(Debug)] pub enum Server { @@ -108,127 +108,73 @@ impl ValveProtocol { client.do_request(Request::A2sInfo(None), None); let mut buf = client.receive(DEFAULT_PACKET_SIZE); - let mut pos = 5; + let mut pos = 4; - if buf[4] == 0x41 { + if buffer::get_u8(&buf, &mut pos)? == 0x41 { client.do_request(Request::A2sInfo(Some([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])), None); buf = client.receive(DEFAULT_PACKET_SIZE); } - let protocol = buf[5]; pos = pos + 1; - - let name = find_first_string(&buf[pos..]); pos = pos + name.len() + 1; - let map = find_first_string(&buf[pos..]); pos = pos + map.len() + 1; - let folder = find_first_string(&buf[pos..]); pos = pos + folder.len() + 1; - let game = find_first_string(&buf[pos..]); pos = pos + game.len() + 1; - - let id = combine_two_u8(buf[pos + 1], buf[pos]); pos = pos + 2; - let players = buf[pos]; pos = pos + 1; - let max_players = buf[pos]; pos = pos + 1; - let bots = buf[pos]; pos = pos + 1; - - let server_type = match buf[pos] as char { - 'd' => Server::Dedicated, - 'l' => Server::NonDedicated, - _ => Server::SourceTV - }; pos = pos + 1; - - let environment_type = match buf[pos] as char { - 'l' => Environment::Linux, - 'w' => Environment::Windows, - _ => Environment::Mac - }; pos = pos + 1; - - let has_password = buf[pos] == 1; pos = pos + 1; - let vac_secured = buf[pos] == 1; pos = pos + 1; - - let the_ship = match has_the_ship { - false => None, - true => { - let ship = TheShip { - mode: buf[pos], - witnesses: buf[pos + 1], - duration: buf[pos + 2] - }; pos = pos + 3; - Some(ship) - } - }; - - let version = find_first_string(&buf[pos..]); pos = pos + version.len() + 1; - - pos = pos + 1; //look ahead - let extra_data = match buf.get(pos - 1) { - None => None, - Some(value) => { - let edf_port = match (value & 0x80) > 0 { - false => None, - true => { - let p = combine_two_u8(buf[pos + 1], buf[pos]); pos = pos + 2; - Some(p) + Ok(Response { + protocol: buffer::get_u8(&buf, &mut pos)?, + name: buffer::get_string(&buf, &mut pos), + map: buffer::get_string(&buf, &mut pos), + folder: buffer::get_string(&buf, &mut pos), + game: buffer::get_string(&buf, &mut pos), + id: buffer::get_u16(&buf, &mut pos), + players: buffer::get_u8(&buf, &mut pos)?, + max_players: buffer::get_u8(&buf, &mut pos)?, + bots: buffer::get_u8(&buf, &mut pos)?, + server_type: match buffer::get_u8(&buf, &mut pos)? as char { + 'd' => Server::Dedicated, + 'l' => Server::NonDedicated, + _ => Server::SourceTV + }, + environment_type: match buffer::get_u8(&buf, &mut pos)? as char { + 'l' => Environment::Linux, + 'w' => Environment::Windows, + _ => Environment::Mac + }, + has_password: buffer::get_u8(&buf, &mut pos)? == 1, + vac_secured: buffer::get_u8(&buf, &mut pos)? == 1, + the_ship: match has_the_ship { + false => None, + true => Some(TheShip { + mode: buffer::get_u8(&buf, &mut pos)?, + witnesses: buffer::get_u8(&buf, &mut pos)?, + duration: buffer::get_u8(&buf, &mut pos)? + }) + }, + version: buffer::get_string(&buf, &mut pos), + extra_data: match buffer::get_u8(&buf, &mut pos) { + Err(_) => None, + Ok(value) => Some(ExtraData { + port: match (value & 0x80) > 0 { + false => None, + true => Some(buffer::get_u16(&buf, &mut pos)) + }, + steam_id: match (value & 0x10) > 0 { + false => None, + true => Some(buffer::get_u64(&buf, &mut pos)) + }, + tv_port: match (value & 0x40) > 0 { + false => None, + true => Some(buffer::get_u16(&buf, &mut pos)) + }, + tv_name: match (value & 0x40) > 0 { + false => None, + true => Some(buffer::get_string(&buf, &mut pos)) + }, + keywords: match (value & 0x20) > 0 { + false => None, + true => Some(buffer::get_string(&buf, &mut pos)) + }, + game_id: match (value & 0x01) > 0 { + false => None, + true => Some(buffer::get_u64(&buf, &mut pos)) } - }; - - let steam_id = match (value & 0x10) > 0 { - false => None, - true => { - let p = get_u64_from_buf(&buf[pos..]); pos = pos + 8; - Some(p) - } - }; - - let (tv_port, tv_name) = match (value & 0x40) > 0 { - false => (None, None), - true => { - let tv_port = combine_two_u8(buf[pos + 1], buf[pos]); pos = pos + 2; - let tv_name = find_first_string(&buf[pos..]); pos = pos + tv_name.len() + 1; - (Some(tv_port), Some(tv_name)) - } - }; - - let keywords = match (value & 0x20) > 0 { - false => None, - true => { - let keywords = find_first_string(&buf[pos..]); pos = pos + keywords.len() + 1; - Some(keywords) - } - }; - - let game_id = match (value & 0x01) > 0 { - false => None, - true => { - let game_id = get_u64_from_buf(&buf[pos..]); pos = pos + 8; - Some(game_id) - } - }; - - Some(ExtraData { - port: edf_port, - steam_id, - tv_port, - tv_name, - keywords, - game_id }) } - }; - - Ok(Response { - protocol, - name, - map, - folder, - game, - id, - players, - max_players, - bots, - server_type, - environment_type, - has_password, - vac_secured, - the_ship, - version, - extra_data }) } } diff --git a/src/utils.rs b/src/utils.rs index ca4a0d6..6e5ee2c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,18 +1,12 @@ use std::ops::Add; +use crate::GDError; pub fn concat_u8(first: &[u8], second: &[u8]) -> Vec { [first, second].concat() } -pub fn find_first_null(arr: &[u8]) -> usize { - match arr.iter().position(|&x| x == 0) { - None => arr.len(), - Some(position) => position - } -} - pub fn find_first_string(arr: &[u8]) -> String { - arr.iter().take_while(|&&b| b != 0).map(|&e| e as char).collect::() + std::str::from_utf8(&arr[..arr.iter().position(|&x| x == 0).unwrap()]).unwrap().to_string() } pub fn complete_address(address: &str, port: u16) -> String { @@ -30,29 +24,31 @@ pub fn combine_eight_u8(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) pub mod buffer { use super::*; - pub fn get_u8(buf: &[u8], pos: usize) -> (u8, usize) { - (buf[pos], pos + 1) + pub fn get_u8(buf: &[u8], pos: &mut usize) -> Result { + let value = buf[*pos]; + *pos += 1; + Ok(value) } - pub fn get_u16(buf: &[u8], pos: usize) -> (u16, usize) { - (combine_two_u8(buf[pos + 1], buf[pos]), pos + 2) + pub fn get_u16(buf: &[u8], pos: &mut usize) -> u16 { + let value = combine_two_u8(buf[*pos + 1], buf[*pos]); + *pos += 2; + value } - pub fn get_u64(buf: &[u8], pos: usize) -> (u64, usize) { - (combine_eight_u8(buf[pos + 7], buf[pos + 6], buf[pos + 5], buf[pos + 4], buf[pos + 3], buf[pos + 2], buf[pos + 1], buf[pos]), pos + 8) + pub fn get_u64(buf: &[u8], pos: &mut usize) -> u64 { + let value = combine_eight_u8(buf[*pos + 7], buf[*pos + 6], buf[*pos + 5], buf[*pos + 4], buf[*pos + 3], buf[*pos + 2], buf[*pos + 1], buf[*pos]); + *pos += 8; + value } - pub fn get_string(buf: &[u8], pos: usize) -> (String, usize) { - let string = find_first_string(&buf[pos..]); - let string_size = string.len(); - (string, pos + string_size + 1) + pub fn get_string(buf: &[u8], pos: &mut usize) -> String { + let value = find_first_string(&buf[*pos..]); + *pos += value.len() + 1; + value } } -pub fn get_u64_from_buf(buf: &[u8]) -> u64 { - combine_eight_u8(buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]) -} - #[cfg(test)] mod utils { use super::*; @@ -68,12 +64,6 @@ mod utils { assert_eq!(b[1], combined[3]); } - #[test] - fn find_null_in_array_test() { - let arr: [u8; 4] = [0x64, 0x32, 0x00, 0x20]; - assert_eq!(2, find_first_null(&arr)); - } - #[test] fn complete_address_test() { let address = "192.168.0.1"; From 544ce897c545e29bd994c2d443d2f163baeca5bb Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 17 Oct 2022 11:11:40 +0300 Subject: [PATCH 011/597] Better protocol parameters and utils tests --- src/errors.rs | 4 +- src/games/tf2.rs | 6 +-- src/protocols/valve.rs | 38 ++++++++------- src/utils.rs | 106 ++++++++++++++++++++++++++++++++--------- 4 files changed, 110 insertions(+), 44 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 1b0623e..85784b2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,13 +3,13 @@ use std::fmt::Formatter; #[derive(Debug, Clone)] pub enum GDError { - IDK(String) + PacketOverflow } impl fmt::Display for GDError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - GDError::IDK(details) => write!(f, "IDK: {details}") + GDError::PacketOverflow => write!(f, "Packet overflow!") } } } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index d349f0f..bf2461a 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,13 +1,13 @@ use crate::errors::GDError; -use crate::protocols::valve::{Response, ValveProtocol}; +use crate::valve::{Response, ValveProtocol, App}; pub struct TF2; impl TF2 { pub fn query(address: &str, port: Option) -> Result { - ValveProtocol::query(address, match port { + ValveProtocol::query(App::TF2, address, match port { None => 27015, Some(port) => port - }, false) + }) } } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index e1e2486..cee509a 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,6 +1,6 @@ use std::net::UdpSocket; use crate::errors::GDError; -use crate::utils::{buffer, complete_address, concat_u8}; +use crate::utils::{buffer, complete_address, concat_u8_arrays}; #[derive(Debug)] pub enum Server { @@ -57,6 +57,12 @@ pub enum Request { A2sInfo(Option<[u8; 4]>) } +#[derive(PartialEq)] +pub enum App { + TF2 = 440, + TheShip = 2400 +} + pub struct ValveProtocol { socket: UdpSocket, complete_address: String @@ -79,7 +85,7 @@ impl ValveProtocol { let request_kind_packet = match kind { Request::A2sInfo(challenge) => match challenge { None => default, - Some(value) => concat_u8(&default, &value) + Some(value) => concat_u8_arrays(&default, &value) } }; @@ -103,7 +109,7 @@ impl ValveProtocol { } impl ValveProtocol { - pub(crate) fn query(address: &str, port: u16, has_the_ship: bool) -> Result { + pub(crate) fn query(app: App, address: &str, port: u16) -> Result { let client = ValveProtocol::new(address, port); client.do_request(Request::A2sInfo(None), None); @@ -117,11 +123,11 @@ impl ValveProtocol { Ok(Response { protocol: buffer::get_u8(&buf, &mut pos)?, - name: buffer::get_string(&buf, &mut pos), - map: buffer::get_string(&buf, &mut pos), - folder: buffer::get_string(&buf, &mut pos), - game: buffer::get_string(&buf, &mut pos), - id: buffer::get_u16(&buf, &mut pos), + name: buffer::get_string(&buf, &mut pos)?, + map: buffer::get_string(&buf, &mut pos)?, + folder: buffer::get_string(&buf, &mut pos)?, + game: buffer::get_string(&buf, &mut pos)?, + id: buffer::get_u16_le(&buf, &mut pos)?, players: buffer::get_u8(&buf, &mut pos)?, max_players: buffer::get_u8(&buf, &mut pos)?, bots: buffer::get_u8(&buf, &mut pos)?, @@ -137,7 +143,7 @@ impl ValveProtocol { }, has_password: buffer::get_u8(&buf, &mut pos)? == 1, vac_secured: buffer::get_u8(&buf, &mut pos)? == 1, - the_ship: match has_the_ship { + the_ship: match app == App::TheShip { false => None, true => Some(TheShip { mode: buffer::get_u8(&buf, &mut pos)?, @@ -145,33 +151,33 @@ impl ValveProtocol { duration: buffer::get_u8(&buf, &mut pos)? }) }, - version: buffer::get_string(&buf, &mut pos), + version: buffer::get_string(&buf, &mut pos)?, extra_data: match buffer::get_u8(&buf, &mut pos) { Err(_) => None, Ok(value) => Some(ExtraData { port: match (value & 0x80) > 0 { false => None, - true => Some(buffer::get_u16(&buf, &mut pos)) + true => Some(buffer::get_u16_le(&buf, &mut pos)?) }, steam_id: match (value & 0x10) > 0 { false => None, - true => Some(buffer::get_u64(&buf, &mut pos)) + true => Some(buffer::get_u64_le(&buf, &mut pos)?) }, tv_port: match (value & 0x40) > 0 { false => None, - true => Some(buffer::get_u16(&buf, &mut pos)) + true => Some(buffer::get_u16_le(&buf, &mut pos)?) }, tv_name: match (value & 0x40) > 0 { false => None, - true => Some(buffer::get_string(&buf, &mut pos)) + true => Some(buffer::get_string(&buf, &mut pos)?) }, keywords: match (value & 0x20) > 0 { false => None, - true => Some(buffer::get_string(&buf, &mut pos)) + true => Some(buffer::get_string(&buf, &mut pos)?) }, game_id: match (value & 0x01) > 0 { false => None, - true => Some(buffer::get_u64(&buf, &mut pos)) + true => Some(buffer::get_u64_le(&buf, &mut pos)?) } }) } diff --git a/src/utils.rs b/src/utils.rs index 6e5ee2c..2ce307f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,23 +1,19 @@ use std::ops::Add; use crate::GDError; -pub fn concat_u8(first: &[u8], second: &[u8]) -> Vec { +pub fn concat_u8_arrays(first: &[u8], second: &[u8]) -> Vec { [first, second].concat() } -pub fn find_first_string(arr: &[u8]) -> String { - std::str::from_utf8(&arr[..arr.iter().position(|&x| x == 0).unwrap()]).unwrap().to_string() -} - pub fn complete_address(address: &str, port: u16) -> String { String::from(address.to_owned() + ":").add(&*port.to_string()) } -pub fn combine_two_u8(high: u8, low: u8) -> u16 { +pub fn combine_two_u8_le(high: u8, low: u8) -> u16 { u16::from_be_bytes([high, low]) } -pub fn combine_eight_u8(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) -> u64 { +pub fn combine_eight_u8_le(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) -> u64 { u64::from_be_bytes([a, b, c, d, e, f, g, h]) } @@ -25,43 +21,57 @@ pub mod buffer { use super::*; pub fn get_u8(buf: &[u8], pos: &mut usize) -> Result { + if buf.len() <= *pos { + return Err(GDError::PacketOverflow); + } + let value = buf[*pos]; *pos += 1; Ok(value) } - pub fn get_u16(buf: &[u8], pos: &mut usize) -> u16 { - let value = combine_two_u8(buf[*pos + 1], buf[*pos]); + pub fn get_u16_le(buf: &[u8], pos: &mut usize) -> Result { + if buf.len() <= *pos + 1 { + return Err(GDError::PacketOverflow); + } + + let value = combine_two_u8_le(buf[*pos + 1], buf[*pos]); *pos += 2; - value + Ok(value) } - pub fn get_u64(buf: &[u8], pos: &mut usize) -> u64 { - let value = combine_eight_u8(buf[*pos + 7], buf[*pos + 6], buf[*pos + 5], buf[*pos + 4], buf[*pos + 3], buf[*pos + 2], buf[*pos + 1], buf[*pos]); + pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> Result { + if buf.len() <= *pos + 7 { + return Err(GDError::PacketOverflow); + } + + let value = combine_eight_u8_le(buf[*pos + 7], buf[*pos + 6], buf[*pos + 5], buf[*pos + 4], buf[*pos + 3], buf[*pos + 2], buf[*pos + 1], buf[*pos]); *pos += 8; - value + Ok(value) } - pub fn get_string(buf: &[u8], pos: &mut usize) -> String { - let value = find_first_string(&buf[*pos..]); + pub fn get_string(buf: &[u8], pos: &mut usize) -> Result { + let sub_buf = &buf[*pos..]; + let first_null_position = sub_buf.iter().position(|&x| x == 0).ok_or(GDError::PacketOverflow)?; + let value = std::str::from_utf8(&sub_buf[..first_null_position]).unwrap().to_string(); *pos += value.len() + 1; - value + Ok(value) } } #[cfg(test)] -mod utils { +mod tests { use super::*; #[test] - fn concat_u8_test() { + fn concat_u8_arrays_test() { let a: [u8; 2] = [1, 2]; let b: [u8; 2] = [3, 4]; - let combined = concat_u8(&a, &b); - assert_eq!(a[0], combined[0]); - assert_eq!(a[1], combined[1]); - assert_eq!(b[0], combined[2]); - assert_eq!(b[1], combined[3]); + let combined = concat_u8_arrays(&a, &b); + assert_eq!(combined[0], a[0]); + assert_eq!(combined[1], a[1]); + assert_eq!(combined[2], b[0]); + assert_eq!(combined[3], b[1]); } #[test] @@ -70,4 +80,54 @@ mod utils { let port = 27015; assert_eq!(complete_address(address, port), "192.168.0.1:27015"); } + + #[test] + fn combine_two_u8_le_test() { + assert_eq!(combine_two_u8_le(49, 123), 12667); + } + + #[test] + fn combine_eight_u8_le_test() { + assert_eq!(combine_eight_u8_le(49, 123, 44, 52, 3, 250, 22, 0), 3565492131910522368); + } + + #[test] + fn get_u8_test() { + let data = [72]; + let mut pos = 0; + assert_eq!(buffer::get_u8(&data, &mut pos).unwrap(), 72); + assert_eq!(pos, 1); + assert!(buffer::get_u8(&data, &mut pos).is_err()); + assert_eq!(pos, 1); + } + + #[test] + fn get_u16_le_test() { + let data = [72, 29]; + let mut pos = 0; + assert_eq!(buffer::get_u16_le(&data, &mut pos).unwrap(), 7496); + assert_eq!(pos, 2); + assert!(buffer::get_u16_le(&data, &mut pos).is_err()); + assert_eq!(pos, 2); + } + + #[test] + fn get_u64_le_test() { + let data = [72, 29, 128, 99, 69, 4, 2, 0]; + let mut pos = 0; + assert_eq!(buffer::get_u64_le(&data, &mut pos).unwrap(), 567646022016328); + assert_eq!(pos, 8); + assert!(buffer::get_u64_le(&data, &mut pos).is_err()); + assert_eq!(pos, 8); + } + + #[test] + fn get_string_test() { + let data = [72, 101, 108, 108, 111, 0, 72]; + let mut pos = 0; + assert_eq!(buffer::get_string(&data, &mut pos).unwrap(), "Hello"); + assert_eq!(pos, 6); + assert!(buffer::get_string(&data, &mut pos).is_err()); + assert_eq!(pos, 6); + } } From 9ab4b8a7fd5ac2c49f173ef56fc30f77107cf597 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 17 Oct 2022 23:22:46 +0300 Subject: [PATCH 012/597] Error handling and better structure --- src/errors.rs | 12 +++++- src/games/tf2.rs | 2 +- src/protocols/valve.rs | 90 +++++++++++++++++++++++------------------- src/utils.rs | 8 ++-- 4 files changed, 64 insertions(+), 48 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 85784b2..782886e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,13 +3,21 @@ use std::fmt::Formatter; #[derive(Debug, Clone)] pub enum GDError { - PacketOverflow + PacketOverflow(String), + PacketUnderflow(String), + PacketBad(String), + PacketSend(String), + PacketReceive(String) } impl fmt::Display for GDError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - GDError::PacketOverflow => write!(f, "Packet overflow!") + GDError::PacketOverflow(details) => write!(f, "Packet overflow: {details}"), + GDError::PacketUnderflow(details) => write!(f, "Packet underflow: {details}"), + GDError::PacketBad(details) => write!(f, "Packet bad: {details}"), + GDError::PacketSend(details) => write!(f, "Couldn't send a packet: {details}"), + GDError::PacketReceive(details) => write!(f, "Couldn't receive a packet: {details}") } } } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index bf2461a..06d89cc 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -8,6 +8,6 @@ impl TF2 { ValveProtocol::query(App::TF2, address, match port { None => 27015, Some(port) => port - }) + }, true, true) } } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index cee509a..eeaf276 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -54,7 +54,9 @@ pub struct ExtraData { } pub enum Request { - A2sInfo(Option<[u8; 4]>) + INFO, + PLAYER, + RULES } #[derive(PartialEq)] @@ -78,49 +80,55 @@ impl ValveProtocol { } } - pub fn do_request(&self, kind: Request, data_packet: Option<&[u8]>) -> bool { - let default: Vec = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, - 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; - - let request_kind_packet = match kind { - Request::A2sInfo(challenge) => match challenge { - None => default, - Some(value) => concat_u8_arrays(&default, &value) - } - }; - - let mut packet = request_kind_packet; - match data_packet { - None => (), - Some(data) => packet.extend_from_slice(data) - } - - match self.socket.send_to(&packet, &self.complete_address) { - Err(_) => false, - Ok(_) => true - } + fn send(&self, data: &[u8]) -> Result<(), GDError> { + self.socket.send_to(&data, &self.complete_address).map_err(|e| GDError::PacketSend(e.to_string()))?; + Ok(()) } - pub fn receive(&self, buffer_size: usize) -> Vec { + fn receive(&self, buffer_size: usize) -> Result, GDError> { let mut buffer: Vec = vec![0; buffer_size]; - let (amt, _) = self.socket.recv_from(&mut buffer.as_mut_slice()).unwrap(); - buffer[..amt].to_vec() + let (amt, _) = self.socket.recv_from(&mut buffer.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?; + Ok(buffer[..amt].to_vec()) + } + + pub fn do_request(&self, kind: Request) -> Result, GDError> { + let info_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; + let player_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55]; + let rules_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56]; + + let no_challenge: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF]; + + let request_initial_packet = match kind { + Request::INFO => info_initial_packet, + Request::PLAYER => concat_u8_arrays(&player_initial_packet, &no_challenge), + Request::RULES => concat_u8_arrays(&rules_initial_packet, &no_challenge) + }; + + self.send(&request_initial_packet)?; + let buffer = self.receive(DEFAULT_PACKET_SIZE)?; + + if buffer.len() < 9 { + return Err(GDError::PacketOverflow("Any Valve Protocol response can't be under 9 bytes long.".to_string())); + } + + if buffer[4] != 41 { //'A' + return Ok(buffer); + } + + let challenge: [u8; 4] = [buffer[5], buffer[6], buffer[7], buffer[8]]; + self.send(&concat_u8_arrays(&request_initial_packet, &challenge))?; + + Ok(self.receive(DEFAULT_PACKET_SIZE)?) } } impl ValveProtocol { - pub(crate) fn query(app: App, address: &str, port: u16) -> Result { + pub(crate) fn query(app: App, address: &str, port: u16, gather_players: bool, gather_rules: bool) -> Result { let client = ValveProtocol::new(address, port); - client.do_request(Request::A2sInfo(None), None); - let mut buf = client.receive(DEFAULT_PACKET_SIZE); + let buf = client.do_request(Request::INFO)?; let mut pos = 4; - if buffer::get_u8(&buf, &mut pos)? == 0x41 { - client.do_request(Request::A2sInfo(Some([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])), None); - buf = client.receive(DEFAULT_PACKET_SIZE); - } - Ok(Response { protocol: buffer::get_u8(&buf, &mut pos)?, name: buffer::get_string(&buf, &mut pos)?, @@ -131,15 +139,15 @@ impl ValveProtocol { players: buffer::get_u8(&buf, &mut pos)?, max_players: buffer::get_u8(&buf, &mut pos)?, bots: buffer::get_u8(&buf, &mut pos)?, - server_type: match buffer::get_u8(&buf, &mut pos)? as char { - 'd' => Server::Dedicated, - 'l' => Server::NonDedicated, - _ => Server::SourceTV + server_type: match buffer::get_u8(&buf, &mut pos)? { + 100 => Server::Dedicated, //'d' + 108 => Server::NonDedicated, //'l' + _ => Server::SourceTV //'p' }, - environment_type: match buffer::get_u8(&buf, &mut pos)? as char { - 'l' => Environment::Linux, - 'w' => Environment::Windows, - _ => Environment::Mac + environment_type: match buffer::get_u8(&buf, &mut pos)? { + 100 => Environment::Linux, //'l' + 119 => Environment::Windows, //'w' + _ => Environment::Mac //'m' or 'o' }, has_password: buffer::get_u8(&buf, &mut pos)? == 1, vac_secured: buffer::get_u8(&buf, &mut pos)? == 1, diff --git a/src/utils.rs b/src/utils.rs index 2ce307f..e0c5e55 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -22,7 +22,7 @@ pub mod buffer { pub fn get_u8(buf: &[u8], pos: &mut usize) -> Result { if buf.len() <= *pos { - return Err(GDError::PacketOverflow); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } let value = buf[*pos]; @@ -32,7 +32,7 @@ pub mod buffer { pub fn get_u16_le(buf: &[u8], pos: &mut usize) -> Result { if buf.len() <= *pos + 1 { - return Err(GDError::PacketOverflow); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } let value = combine_two_u8_le(buf[*pos + 1], buf[*pos]); @@ -42,7 +42,7 @@ pub mod buffer { pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> Result { if buf.len() <= *pos + 7 { - return Err(GDError::PacketOverflow); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } let value = combine_eight_u8_le(buf[*pos + 7], buf[*pos + 6], buf[*pos + 5], buf[*pos + 4], buf[*pos + 3], buf[*pos + 2], buf[*pos + 1], buf[*pos]); @@ -52,7 +52,7 @@ pub mod buffer { pub fn get_string(buf: &[u8], pos: &mut usize) -> Result { let sub_buf = &buf[*pos..]; - let first_null_position = sub_buf.iter().position(|&x| x == 0).ok_or(GDError::PacketOverflow)?; + let first_null_position = sub_buf.iter().position(|&x| x == 0).ok_or(GDError::PacketBad("Unexpectedly formatted packet.".to_string()))?; let value = std::str::from_utf8(&sub_buf[..first_null_position]).unwrap().to_string(); *pos += value.len() + 1; Ok(value) From 92d964900849ac2bc5a79ec84ade5145ed03da7e Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 18 Oct 2022 01:40:37 +0300 Subject: [PATCH 013/597] Valve PLAYERS and RULES request first implementations --- src/games/tf2.rs | 8 +- src/protocols/valve.rs | 175 +++++++++++++++++++++++++++++++++++------ src/utils.rs | 62 ++++++++++----- 3 files changed, 198 insertions(+), 47 deletions(-) diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 06d89cc..3b6ea8c 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,5 +1,5 @@ use crate::errors::GDError; -use crate::valve::{Response, ValveProtocol, App}; +use crate::valve::{Response, ValveProtocol, App, GatheringSettings}; pub struct TF2; @@ -8,6 +8,10 @@ impl TF2 { ValveProtocol::query(App::TF2, address, match port { None => 27015, Some(port) => port - }, true, true) + }, GatheringSettings { + info: false, + players: false, + rules: true + }) } } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index eeaf276..984ea92 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -18,6 +18,13 @@ pub enum Environment { #[derive(Debug)] pub struct Response { + pub info: Option, + pub players: Option, + pub rules: Option +} + +#[derive(Debug)] +pub struct ServerInfo { pub protocol: u8, pub map: String, pub name: String, @@ -36,6 +43,33 @@ pub struct Response { pub extra_data: Option } +#[derive(Debug)] +pub struct ServerPlayers { + pub count: u8, + pub players: Vec +} + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32, + pub deaths: Option, //the_ship + pub money: Option, //the_ship +} + +#[derive(Debug)] +pub struct ServerRules { + pub count: u16, + pub rules: Vec +} + +#[derive(Debug)] +pub struct Rule { + pub name: String, + pub value: String +} + #[derive(Debug)] pub struct TheShip { pub mode: u8, @@ -53,24 +87,32 @@ pub struct ExtraData { pub game_id: Option } +#[derive(PartialEq)] pub enum Request { INFO, - PLAYER, + PLAYERS, RULES } #[derive(PartialEq)] pub enum App { TF2 = 440, + CSGO = 730, TheShip = 2400 } +pub struct GatheringSettings { + pub info: bool, + pub players: bool, + pub rules: bool +} + pub struct ValveProtocol { socket: UdpSocket, complete_address: String } -static DEFAULT_PACKET_SIZE: usize = 256; +static DEFAULT_PACKET_SIZE: usize = 2048; impl ValveProtocol { fn new(address: &str, port: u16) -> Self { @@ -91,45 +133,52 @@ impl ValveProtocol { Ok(buffer[..amt].to_vec()) } - pub fn do_request(&self, kind: Request) -> Result, GDError> { + pub fn get_request_data(&self, kind: Request) -> Result, GDError> { let info_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; - let player_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55]; - let rules_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56]; - - let no_challenge: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF]; + let players_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF]; + let rules_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0xFF, 0xFF, 0xFF, 0xFF]; let request_initial_packet = match kind { Request::INFO => info_initial_packet, - Request::PLAYER => concat_u8_arrays(&player_initial_packet, &no_challenge), - Request::RULES => concat_u8_arrays(&rules_initial_packet, &no_challenge) + Request::PLAYERS => players_initial_packet, + Request::RULES => rules_initial_packet }; self.send(&request_initial_packet)?; - let buffer = self.receive(DEFAULT_PACKET_SIZE)?; + let mut initial_receive = self.receive(DEFAULT_PACKET_SIZE)?; - if buffer.len() < 9 { + if initial_receive.len() < 9 { return Err(GDError::PacketOverflow("Any Valve Protocol response can't be under 9 bytes long.".to_string())); } - if buffer[4] != 41 { //'A' - return Ok(buffer); + if initial_receive[4] != 0x41 { //'A' + return Ok(initial_receive.drain(5..).collect()); } - let challenge: [u8; 4] = [buffer[5], buffer[6], buffer[7], buffer[8]]; - self.send(&concat_u8_arrays(&request_initial_packet, &challenge))?; + let challenge: [u8; 4] = [initial_receive[5], initial_receive[6], initial_receive[7], initial_receive[8]]; + let challenge_packet = match kind { + Request::INFO => concat_u8_arrays(&request_initial_packet, &challenge), + Request::PLAYERS => vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, challenge[0], challenge[1], challenge[2], challenge[3]], + Request::RULES => vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56, challenge[0], challenge[1], challenge[2], challenge[3]] + }; - Ok(self.receive(DEFAULT_PACKET_SIZE)?) + self.send(&challenge_packet)?; + + let mut after_challenge_receive = self.receive(DEFAULT_PACKET_SIZE)?; + + if kind == Request::RULES && after_challenge_receive[0] == 0xFE { + Ok(after_challenge_receive.drain(17..).collect()) + } + else { + Ok(after_challenge_receive.drain(5..).collect()) + } } -} -impl ValveProtocol { - pub(crate) fn query(app: App, address: &str, port: u16, gather_players: bool, gather_rules: bool) -> Result { - let client = ValveProtocol::new(address, port); + fn get_server_info(&self, app: &App) -> Result { + let buf = self.get_request_data(Request::INFO)?; + let mut pos = 0; - let buf = client.do_request(Request::INFO)?; - let mut pos = 4; - - Ok(Response { + Ok(ServerInfo { protocol: buffer::get_u8(&buf, &mut pos)?, name: buffer::get_string(&buf, &mut pos)?, map: buffer::get_string(&buf, &mut pos)?, @@ -151,7 +200,7 @@ impl ValveProtocol { }, has_password: buffer::get_u8(&buf, &mut pos)? == 1, vac_secured: buffer::get_u8(&buf, &mut pos)? == 1, - the_ship: match app == App::TheShip { + the_ship: match *app == App::TheShip { false => None, true => Some(TheShip { mode: buffer::get_u8(&buf, &mut pos)?, @@ -191,4 +240,80 @@ impl ValveProtocol { } }) } + + fn get_server_players(&self, app: &App) -> Result { + let buf = self.get_request_data(Request::PLAYERS)?; + let mut pos = 0; + + println!("{:x?}", buf); + + let count = buffer::get_u8(&buf, &mut pos)?; + let mut players: Vec = Vec::new(); + + for _ in 0..count { + pos += 1; //skip the index byte + players.push(Player { + name: buffer::get_string(&buf, &mut pos)?, + score: buffer::get_u32_le(&buf, &mut pos)?, + duration: buffer::get_f32_le(&buf, &mut pos)?, + deaths: match *app == App::TheShip { + false => None, + true => Some(buffer::get_u32_le(&buf, &mut pos)?) + }, + money: match *app == App::TheShip { + false => None, + true => Some(buffer::get_u32_le(&buf, &mut pos)?) + } + }); + } + + Ok(ServerPlayers { + count, + players + }) + } + + fn get_server_rules(&self, app: &App) -> Result { + let buf = self.get_request_data(Request::RULES)?; + let mut pos = 0; + + println!("{:x?}", buf); + + let count = buffer::get_u16_le(&buf, &mut pos)?; + let mut rules: Vec = Vec::new(); + + for _ in 0..count { + rules.push(Rule { + name: buffer::get_string(&buf, &mut pos)?, //might be truncated! + value: buffer::get_string(&buf, &mut pos)? + }); + println!("{} = {}", rules[rules.len() - 1].name, rules[rules.len() - 1].value); + } + + println!("{} {}", buf.len(), pos); + + Ok(ServerRules { + count, + rules + }) + } + + pub(crate) fn query(app: App, address: &str, port: u16, gather: GatheringSettings) -> Result { + let client = ValveProtocol::new(address, port); + + Ok(Response { + info: match gather.info { + false => None, + true => Some(client.get_server_info(&app)?) + }, + players: match gather.players { + false => None, + true => Some(client.get_server_players(&app)?) + }, + rules: match gather.rules { + false => None, + true => Some(client.get_server_rules(&app)?) + } + }) + } } diff --git a/src/utils.rs b/src/utils.rs index e0c5e55..6dd678d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,14 +9,6 @@ pub fn complete_address(address: &str, port: u16) -> String { String::from(address.to_owned() + ":").add(&*port.to_string()) } -pub fn combine_two_u8_le(high: u8, low: u8) -> u16 { - u16::from_be_bytes([high, low]) -} - -pub fn combine_eight_u8_le(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) -> u64 { - u64::from_be_bytes([a, b, c, d, e, f, g, h]) -} - pub mod buffer { use super::*; @@ -35,17 +27,37 @@ pub mod buffer { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } - let value = combine_two_u8_le(buf[*pos + 1], buf[*pos]); + let value = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]); *pos += 2; Ok(value) } + pub fn get_u32_le(buf: &[u8], pos: &mut usize) -> Result { + if buf.len() <= *pos + 3 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + } + + let value = u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); + *pos += 4; + Ok(value) + } + + pub fn get_f32_le(buf: &[u8], pos: &mut usize) -> Result { + if buf.len() <= *pos + 3 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + } + + let value = f32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); + *pos += 4; + Ok(value) + } + pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> Result { if buf.len() <= *pos + 7 { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } - let value = combine_eight_u8_le(buf[*pos + 7], buf[*pos + 6], buf[*pos + 5], buf[*pos + 4], buf[*pos + 3], buf[*pos + 2], buf[*pos + 1], buf[*pos]); + let value = u64::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3], buf[*pos + 4], buf[*pos + 5], buf[*pos + 6], buf[*pos + 7]]); *pos += 8; Ok(value) } @@ -81,16 +93,6 @@ mod tests { assert_eq!(complete_address(address, port), "192.168.0.1:27015"); } - #[test] - fn combine_two_u8_le_test() { - assert_eq!(combine_two_u8_le(49, 123), 12667); - } - - #[test] - fn combine_eight_u8_le_test() { - assert_eq!(combine_eight_u8_le(49, 123, 44, 52, 3, 250, 22, 0), 3565492131910522368); - } - #[test] fn get_u8_test() { let data = [72]; @@ -111,6 +113,26 @@ mod tests { assert_eq!(pos, 2); } + #[test] + fn get_u32_le_test() { + let data = [72, 29, 128, 100]; + let mut pos = 0; + assert_eq!(buffer::get_u32_le(&data, &mut pos).unwrap(), 1686117704); + assert_eq!(pos, 4); + assert!(buffer::get_u32_le(&data, &mut pos).is_err()); + assert_eq!(pos, 4); + } + + #[test] + fn get_f32_le_test() { + let data = [72, 29, 128, 100]; + let mut pos = 0; + assert_eq!(buffer::get_f32_le(&data, &mut pos).unwrap(), 1.8906345e22); + assert_eq!(pos, 4); + assert!(buffer::get_f32_le(&data, &mut pos).is_err()); + assert_eq!(pos, 4); + } + #[test] fn get_u64_le_test() { let data = [72, 29, 128, 99, 69, 4, 2, 0]; From 1cb00f826ad5d42f24852370389f0a79f4a09729 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 18 Oct 2022 11:51:29 +0300 Subject: [PATCH 014/597] Fixed rules response --- src/protocols/valve.rs | 48 +++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 984ea92..fb08afe 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::net::UdpSocket; use crate::errors::GDError; use crate::utils::{buffer, complete_address, concat_u8_arrays}; @@ -61,13 +62,7 @@ pub struct Player { #[derive(Debug)] pub struct ServerRules { pub count: u16, - pub rules: Vec -} - -#[derive(Debug)] -pub struct Rule { - pub name: String, - pub value: String + pub rules: HashMap } #[derive(Debug)] @@ -133,6 +128,18 @@ impl ValveProtocol { Ok(buffer[..amt].to_vec()) } + fn receive_truncated(&self, initial_packet: &[u8]) -> Result, GDError> { + let count = initial_packet[8] - 1; + let mut final_packet: Vec = initial_packet.to_vec().drain(17..).collect::>(); + + for _ in 0..count { + let mut packet = self.receive(DEFAULT_PACKET_SIZE)?; + final_packet.append(&mut packet.drain(13..).collect::>()); + } + + Ok(final_packet) + } + pub fn get_request_data(&self, kind: Request) -> Result, GDError> { let info_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; let players_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF]; @@ -164,13 +171,11 @@ impl ValveProtocol { self.send(&challenge_packet)?; - let mut after_challenge_receive = self.receive(DEFAULT_PACKET_SIZE)?; - - if kind == Request::RULES && after_challenge_receive[0] == 0xFE { - Ok(after_challenge_receive.drain(17..).collect()) - } - else { - Ok(after_challenge_receive.drain(5..).collect()) + let packet = self.receive(DEFAULT_PACKET_SIZE)?; + if packet[0] == 0xFE || (packet[0] == 0xFF && packet[4] == 0x45) { //'E' + self.receive_truncated(&packet) + } else { + Ok(packet) } } @@ -245,8 +250,6 @@ impl ValveProtocol { let buf = self.get_request_data(Request::PLAYERS)?; let mut pos = 0; - println!("{:x?}", buf); - let count = buffer::get_u8(&buf, &mut pos)?; let mut players: Vec = Vec::new(); @@ -277,21 +280,14 @@ impl ValveProtocol { let buf = self.get_request_data(Request::RULES)?; let mut pos = 0; - println!("{:x?}", buf); - let count = buffer::get_u16_le(&buf, &mut pos)?; - let mut rules: Vec = Vec::new(); + let mut rules: HashMap = HashMap::new(); for _ in 0..count { - rules.push(Rule { - name: buffer::get_string(&buf, &mut pos)?, //might be truncated! - value: buffer::get_string(&buf, &mut pos)? - }); - println!("{} = {}", rules[rules.len() - 1].name, rules[rules.len() - 1].value); + rules.insert(buffer::get_string(&buf, &mut pos)?, //name + buffer::get_string(&buf, &mut pos)?); //value } - println!("{} {}", buf.len(), pos); - Ok(ServerRules { count, rules From 192d50a11def2af72219556e6bb584e2d1c5d64a Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 18 Oct 2022 18:20:37 +0300 Subject: [PATCH 015/597] Specified MIT license and made the valve protocol check if the queried game is the right game --- Cargo.toml | 2 +- src/errors.rs | 8 ++++++-- src/games/tf2.rs | 5 ++--- src/protocols/valve.rs | 37 +++++++++++++++++++++++++------------ 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7447f84..557478a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "gamedig" version = "0.0.1" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] -license-file = "LICENSE.md" +license = "MIT" description = "Check out servers with this." homepage = "https://github.com/CosminPerRam/rust-gamedig" documentation = "https://docs.rs/gamedig/latest/gamedig/" diff --git a/src/errors.rs b/src/errors.rs index 782886e..657b163 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -7,7 +7,9 @@ pub enum GDError { PacketUnderflow(String), PacketBad(String), PacketSend(String), - PacketReceive(String) + PacketReceive(String), + UnknownEnumCast, + BadGame(String) } impl fmt::Display for GDError { @@ -17,7 +19,9 @@ impl fmt::Display for GDError { GDError::PacketUnderflow(details) => write!(f, "Packet underflow: {details}"), GDError::PacketBad(details) => write!(f, "Packet bad: {details}"), GDError::PacketSend(details) => write!(f, "Couldn't send a packet: {details}"), - GDError::PacketReceive(details) => write!(f, "Couldn't receive a packet: {details}") + GDError::PacketReceive(details) => write!(f, "Couldn't receive a packet: {details}"), + GDError::UnknownEnumCast => write!(f, "Unknown enum cast encountered."), + GDError::BadGame(details) => write!(f, "Queried another game that the supposed one: {details}"), } } } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 3b6ea8c..4f39798 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,5 +1,5 @@ use crate::errors::GDError; -use crate::valve::{Response, ValveProtocol, App, GatheringSettings}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; pub struct TF2; @@ -9,8 +9,7 @@ impl TF2 { None => 27015, Some(port) => port }, GatheringSettings { - info: false, - players: false, + players: true, rules: true }) } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index fb08afe..84c8a8f 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -19,7 +19,7 @@ pub enum Environment { #[derive(Debug)] pub struct Response { - pub info: Option, + pub info: ServerInfo, pub players: Option, pub rules: Option } @@ -62,7 +62,7 @@ pub struct Player { #[derive(Debug)] pub struct ServerRules { pub count: u16, - pub rules: HashMap + pub map: HashMap } #[derive(Debug)] @@ -96,8 +96,20 @@ pub enum App { TheShip = 2400 } +impl TryFrom for App { + type Error = GDError; + + fn try_from(value: u16) -> Result { + match value { + x if x == App::TF2 as u16 => Ok(App::TF2), + x if x == App::TF2 as u16 => Ok(App::CSGO), + x if x == App::TF2 as u16 => Ok(App::TheShip), + _ => Err(GDError::UnknownEnumCast), + } + } +} + pub struct GatheringSettings { - pub info: bool, pub players: bool, pub rules: bool } @@ -171,11 +183,11 @@ impl ValveProtocol { self.send(&challenge_packet)?; - let packet = self.receive(DEFAULT_PACKET_SIZE)?; + let mut packet = self.receive(DEFAULT_PACKET_SIZE)?; if packet[0] == 0xFE || (packet[0] == 0xFF && packet[4] == 0x45) { //'E' self.receive_truncated(&packet) } else { - Ok(packet) + Ok(packet.drain(5..).collect::>()) } } @@ -276,7 +288,7 @@ impl ValveProtocol { }) } - fn get_server_rules(&self, app: &App) -> Result { + fn get_server_rules(&self) -> Result { let buf = self.get_request_data(Request::RULES)?; let mut pos = 0; @@ -290,25 +302,26 @@ impl ValveProtocol { Ok(ServerRules { count, - rules + map: rules }) } pub(crate) fn query(app: App, address: &str, port: u16, gather: GatheringSettings) -> Result { let client = ValveProtocol::new(address, port); + let info = client.get_server_info(&app)?; + + App::try_from(info.id).map_err(|_| GDError::BadGame(format!("Found {} instead!", info.id)))?; + Ok(Response { - info: match gather.info { - false => None, - true => Some(client.get_server_info(&app)?) - }, + info, players: match gather.players { false => None, true => Some(client.get_server_players(&app)?) }, rules: match gather.rules { false => None, - true => Some(client.get_server_rules(&app)?) + true => Some(client.get_server_rules()?) } }) } From 5cf56152650e2d4139f5d175470082f3f7c5efe4 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Wed, 19 Oct 2022 22:47:52 +0300 Subject: [PATCH 016/597] Initial the ship support --- src/games/mod.rs | 2 ++ src/games/the_ship.rs | 16 ++++++++++++++++ src/protocols/valve.rs | 4 ++-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/games/the_ship.rs diff --git a/src/games/mod.rs b/src/games/mod.rs index eee99b5..02ecf0d 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1,4 +1,6 @@ pub mod tf2; +pub mod the_ship; pub use tf2::*; +pub use the_ship::*; diff --git a/src/games/the_ship.rs b/src/games/the_ship.rs new file mode 100644 index 0000000..6a159a5 --- /dev/null +++ b/src/games/the_ship.rs @@ -0,0 +1,16 @@ +use crate::errors::GDError; +use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; + +pub struct TheShip; + +impl TheShip { + pub fn query(address: &str, port: Option) -> Result { + ValveProtocol::query(App::TheShip, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: false, + rules: false + }) + } +} diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 84c8a8f..3870276 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -102,8 +102,8 @@ impl TryFrom for App { fn try_from(value: u16) -> Result { match value { x if x == App::TF2 as u16 => Ok(App::TF2), - x if x == App::TF2 as u16 => Ok(App::CSGO), - x if x == App::TF2 as u16 => Ok(App::TheShip), + x if x == App::CSGO as u16 => Ok(App::CSGO), + x if x == App::TheShip as u16 => Ok(App::TheShip), _ => Err(GDError::UnknownEnumCast), } } From 401d499d612a1cded3e1b420cb1e2c8649405fe9 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Thu, 20 Oct 2022 00:14:57 +0300 Subject: [PATCH 017/597] The ship support --- examples/the_ship.rs | 10 ++++++++++ src/games/the_ship.rs | 4 ++-- src/protocols/valve.rs | 14 +++++++------- 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 examples/the_ship.rs diff --git a/examples/the_ship.rs b/examples/the_ship.rs new file mode 100644 index 0000000..496fc6f --- /dev/null +++ b/examples/the_ship.rs @@ -0,0 +1,10 @@ + +use gamedig::TheShip; + +fn main() { + let response = TheShip::query("46.4.48.226", Some(27017)); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/the_ship.rs b/src/games/the_ship.rs index 6a159a5..ae86d92 100644 --- a/src/games/the_ship.rs +++ b/src/games/the_ship.rs @@ -9,8 +9,8 @@ impl TheShip { None => 27015, Some(port) => port }, GatheringSettings { - players: false, - rules: false + players: true, + rules: true }) } } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 3870276..ef027a2 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -152,7 +152,7 @@ impl ValveProtocol { Ok(final_packet) } - pub fn get_request_data(&self, kind: Request) -> Result, GDError> { + pub fn get_request_data(&self, app: &App, kind: Request) -> Result, GDError> { let info_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; let players_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF]; let rules_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0xFF, 0xFF, 0xFF, 0xFF]; @@ -184,7 +184,7 @@ impl ValveProtocol { self.send(&challenge_packet)?; let mut packet = self.receive(DEFAULT_PACKET_SIZE)?; - if packet[0] == 0xFE || (packet[0] == 0xFF && packet[4] == 0x45) { //'E' + if (packet[0] == 0xFE || (packet[0] == 0xFF && packet[4] == 0x45)) && (*app != App::TheShip) { //'E' self.receive_truncated(&packet) } else { Ok(packet.drain(5..).collect::>()) @@ -192,7 +192,7 @@ impl ValveProtocol { } fn get_server_info(&self, app: &App) -> Result { - let buf = self.get_request_data(Request::INFO)?; + let buf = self.get_request_data(app, Request::INFO)?; let mut pos = 0; Ok(ServerInfo { @@ -259,7 +259,7 @@ impl ValveProtocol { } fn get_server_players(&self, app: &App) -> Result { - let buf = self.get_request_data(Request::PLAYERS)?; + let buf = self.get_request_data(app, Request::PLAYERS)?; let mut pos = 0; let count = buffer::get_u8(&buf, &mut pos)?; @@ -288,8 +288,8 @@ impl ValveProtocol { }) } - fn get_server_rules(&self) -> Result { - let buf = self.get_request_data(Request::RULES)?; + fn get_server_rules(&self, app: &App) -> Result { + let buf = self.get_request_data(app, Request::RULES)?; let mut pos = 0; let count = buffer::get_u16_le(&buf, &mut pos)?; @@ -321,7 +321,7 @@ impl ValveProtocol { }, rules: match gather.rules { false => None, - true => Some(client.get_server_rules()?) + true => Some(client.get_server_rules(&app)?) } }) } From 38d7758c4c81f32ea777bb2451421852b02a987e Mon Sep 17 00:00:00 2001 From: cosminperram Date: Thu, 20 Oct 2022 00:20:07 +0300 Subject: [PATCH 018/597] Fixed bad environment_type linux value and added error branch --- src/protocols/valve.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index ef027a2..1f76783 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -208,12 +208,14 @@ impl ValveProtocol { server_type: match buffer::get_u8(&buf, &mut pos)? { 100 => Server::Dedicated, //'d' 108 => Server::NonDedicated, //'l' - _ => Server::SourceTV //'p' + 112 => Server::SourceTV, //'p' + _ => Err(GDError::UnknownEnumCast)? }, environment_type: match buffer::get_u8(&buf, &mut pos)? { - 100 => Environment::Linux, //'l' + 108 => Environment::Linux, //'l' 119 => Environment::Windows, //'w' - _ => Environment::Mac //'m' or 'o' + 109 | 111 => Environment::Mac, //'m' or 'o' + _ => Err(GDError::UnknownEnumCast)? }, has_password: buffer::get_u8(&buf, &mut pos)? == 1, vac_secured: buffer::get_u8(&buf, &mut pos)? == 1, From 8a93d2fb7dafd9f5fa0009b71d9f466454b34f75 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Thu, 20 Oct 2022 11:24:45 +0300 Subject: [PATCH 019/597] Initial CSGO support --- examples/csgo.rs | 10 ++++++++++ src/games/csgo.rs | 16 ++++++++++++++++ src/games/mod.rs | 2 ++ 3 files changed, 28 insertions(+) create mode 100644 examples/csgo.rs create mode 100644 src/games/csgo.rs diff --git a/examples/csgo.rs b/examples/csgo.rs new file mode 100644 index 0000000..0b20adb --- /dev/null +++ b/examples/csgo.rs @@ -0,0 +1,10 @@ + +use gamedig::CSGO; + +fn main() { + let response = CSGO::query("51.38.142.109", None); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/csgo.rs b/src/games/csgo.rs new file mode 100644 index 0000000..b99fdb6 --- /dev/null +++ b/src/games/csgo.rs @@ -0,0 +1,16 @@ +use crate::errors::GDError; +use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; + +pub struct CSGO; + +impl CSGO { + pub fn query(address: &str, port: Option) -> Result { + ValveProtocol::query(App::CSGO, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + }) + } +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 02ecf0d..e6cb505 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1,6 +1,8 @@ pub mod tf2; pub mod the_ship; +pub mod csgo; pub use tf2::*; pub use the_ship::*; +pub use csgo::*; From 40912bb192c61fd4c19dea429e11207dc34140b4 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Thu, 20 Oct 2022 11:33:31 +0300 Subject: [PATCH 020/597] CSGO support. --- src/protocols/valve.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 1f76783..2308f9c 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -290,7 +290,11 @@ impl ValveProtocol { }) } - fn get_server_rules(&self, app: &App) -> Result { + fn get_server_rules(&self, app: &App) -> Result, GDError> { + if *app == App::CSGO { //cause csgo response here is broken after feb 21 2014 + return Ok(None); + } + let buf = self.get_request_data(app, Request::RULES)?; let mut pos = 0; @@ -302,10 +306,10 @@ impl ValveProtocol { buffer::get_string(&buf, &mut pos)?); //value } - Ok(ServerRules { + Ok(Some(ServerRules { count, map: rules - }) + })) } pub(crate) fn query(app: App, address: &str, port: u16, gather: GatheringSettings) -> Result { @@ -323,7 +327,7 @@ impl ValveProtocol { }, rules: match gather.rules { false => None, - true => Some(client.get_server_rules(&app)?) + true => client.get_server_rules(&app)? } }) } From 00ead6d94664ee91f9222f4518e47119dbbb654f Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 20 Oct 2022 12:49:22 +0300 Subject: [PATCH 021/597] Documentation update --- Cargo.toml | 2 +- README.md | 9 +++---- examples/csgo.rs | 4 ++-- examples/tf2.rs | 4 ++-- examples/the_ship.rs | 4 ++-- src/errors.rs | 8 +++++++ src/games/csgo.rs | 20 +++++++--------- src/games/mod.rs | 6 ++--- src/games/tf2.rs | 20 +++++++--------- src/games/the_ship.rs | 20 +++++++--------- src/lib.rs | 18 +++++++++++++- src/protocols/mod.rs | 6 +++++ src/protocols/valve.rs | 54 ++++++++++++++++++++++++++++++++++++++---- 13 files changed, 117 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 557478a..0b5c3e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ homepage = "https://github.com/CosminPerRam/rust-gamedig" documentation = "https://docs.rs/gamedig/latest/gamedig/" repository = "https://github.com/CosminPerRam/rust-gamedig" readme = "README.md" -keywords = ["server", "valve", "games", "checker", "status"] +keywords = ["server", "verify", "game", "check", "status"] [package.metadata] msrv = "1.58.1" diff --git a/README.md b/README.md index 1b2a48b..a419e4f 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,10 @@ MSRV is `1.58.1` and the code is cross-platform. # Example Basic usage of the library is: ```rust -use gamedig::TF2; +use gamedig::games::tf2; fn main() { - let response = TF2::query("91.216.250.10", None); - //query your favorite game/protocol/service, some might come with different parameters - //here its just the IP and the port (if None, its gonna be the default from the protocol) - + let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) @@ -29,4 +26,4 @@ Curious about the history and what changed between versions? you can see just th To see the supported (or the planned to support) games, see [GAMES](GAMES.md). # Contributing -If you want see your favorite game/service being supported here, open an issue (or do a pull request if you want to implement it yourself)! +If you want see your favorite game/service being supported here, open an issue and I'll prioritize it! (or do a pull request if you want to implement it yourself) diff --git a/examples/csgo.rs b/examples/csgo.rs index 0b20adb..48f0943 100644 --- a/examples/csgo.rs +++ b/examples/csgo.rs @@ -1,8 +1,8 @@ -use gamedig::CSGO; +use gamedig::games::csgo; fn main() { - let response = CSGO::query("51.38.142.109", None); + let response = csgo::query("51.38.142.109", None); match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) diff --git a/examples/tf2.rs b/examples/tf2.rs index e77a699..f0fc2ec 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -1,8 +1,8 @@ -use gamedig::TF2; +use gamedig::games::tf2; fn main() { - let response = TF2::query("91.216.250.10", None); + let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) diff --git a/examples/the_ship.rs b/examples/the_ship.rs index 496fc6f..14524e1 100644 --- a/examples/the_ship.rs +++ b/examples/the_ship.rs @@ -1,8 +1,8 @@ -use gamedig::TheShip; +use gamedig::games::the_ship; fn main() { - let response = TheShip::query("46.4.48.226", Some(27017)); + let response = the_ship::query("46.4.48.226", Some(27017)); match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) diff --git a/src/errors.rs b/src/errors.rs index 657b163..6e2210f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,14 +1,22 @@ use core::fmt; use std::fmt::Formatter; +/// GameDigError, every error you can encounter using the library. #[derive(Debug, Clone)] pub enum GDError { + /// The received packet was bigger than the buffer size. PacketOverflow(String), + /// The received packet was shorter than the expected one. PacketUnderflow(String), + /// The received packet was badly formatted. PacketBad(String), + /// Couldn't send the packet. PacketSend(String), + /// Couldn't send the receive. PacketReceive(String), + /// Unknown cast while translating a value to an enum UnknownEnumCast, + /// The server queried is not from the queried game. BadGame(String) } diff --git a/src/games/csgo.rs b/src/games/csgo.rs index b99fdb6..cc45ab4 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,16 +1,12 @@ use crate::errors::GDError; use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; -pub struct CSGO; - -impl CSGO { - pub fn query(address: &str, port: Option) -> Result { - ValveProtocol::query(App::CSGO, address, match port { - None => 27015, - Some(port) => port - }, GatheringSettings { - players: true, - rules: true - }) - } +pub fn query(address: &str, port: Option) -> Result { + ValveProtocol::query(App::CSGO, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + }) } diff --git a/src/games/mod.rs b/src/games/mod.rs index e6cb505..a0f3e63 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1,8 +1,6 @@ +//! Currently supported games. + pub mod tf2; pub mod the_ship; pub mod csgo; - -pub use tf2::*; -pub use the_ship::*; -pub use csgo::*; diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 4f39798..dcee6fb 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,16 +1,12 @@ use crate::errors::GDError; use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; -pub struct TF2; - -impl TF2 { - pub fn query(address: &str, port: Option) -> Result { - ValveProtocol::query(App::TF2, address, match port { - None => 27015, - Some(port) => port - }, GatheringSettings { - players: true, - rules: true - }) - } +pub fn query(address: &str, port: Option) -> Result { + ValveProtocol::query(App::TF2, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + }) } diff --git a/src/games/the_ship.rs b/src/games/the_ship.rs index ae86d92..a9d1fba 100644 --- a/src/games/the_ship.rs +++ b/src/games/the_ship.rs @@ -1,16 +1,12 @@ use crate::errors::GDError; use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; -pub struct TheShip; - -impl TheShip { - pub fn query(address: &str, port: Option) -> Result { - ValveProtocol::query(App::TheShip, address, match port { - None => 27015, - Some(port) => port - }, GatheringSettings { - players: true, - rules: true - }) - } +pub fn query(address: &str, port: Option) -> Result { + ValveProtocol::query(App::TheShip, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + }) } diff --git a/src/lib.rs b/src/lib.rs index 59232a6..56c1905 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,24 @@ +//! Query many servers +//! +//! # Example +//! +//! ```no_run +//! use gamedig::games::tf2; +//! +//! fn main() { +//! let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port +//! match response { +//! Err(error) => println!("Couldn't query, error: {error}"), +//! Ok(r) => println!("{:?}", r) +//! } +//! } +//! ``` + pub mod errors; pub mod protocols; -mod utils; pub mod games; +mod utils; pub use errors::*; pub use protocols::*; diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 3deed53..f84b187 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -1,2 +1,8 @@ +//! Protocols that are currently implemented. +//! +//! A protocol will be here if it supports multiple entries, if not, its implementation will be +//! in that specific needed place, a protocol can be independently queried. + +/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) pub mod valve; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 2308f9c..0949d9a 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -3,6 +3,7 @@ use std::net::UdpSocket; use crate::errors::GDError; use crate::utils::{buffer, complete_address, concat_u8_arrays}; +/// The type of the server. #[derive(Debug)] pub enum Server { Dedicated, @@ -10,6 +11,7 @@ pub enum Server { SourceTV } +/// The Operating System that the server is on. #[derive(Debug)] pub enum Environment { Linux, @@ -17,6 +19,7 @@ pub enum Environment { Mac } +/// A query response. #[derive(Debug)] pub struct Response { pub info: ServerInfo, @@ -24,47 +27,73 @@ pub struct Response { pub rules: Option } +/// General server information's. #[derive(Debug)] pub struct ServerInfo { + /// Protocol used by the server. pub protocol: u8, - pub map: String, + /// Name of the server. pub name: String, + /// Map name. + pub map: String, + /// Name of the folder containing the game files. pub folder: String, + /// Full name of the game. pub game: String, + /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. pub id: u16, + /// Number of players on the server. pub players: u8, + /// Maximum number of players the server reports it can hold. pub max_players: u8, + /// Number of bots on the server. pub bots: u8, + /// Dedicated, NonDedicated or SourceTV pub server_type: Server, + /// The Operating System that the server is on. pub environment_type: Environment, + /// Indicated whether the server requires a password. pub has_password: bool, + /// Indicated whether the server uses VAC. pub vac_secured: bool, + /// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data pub the_ship: Option, + /// Version of the game installed on the server. pub version: String, + /// Some extra data that the server might provide or not. pub extra_data: Option } +/// Server's players. #[derive(Debug)] pub struct ServerPlayers { pub count: u8, pub players: Vec } +/// Data about a player #[derive(Debug)] pub struct Player { + /// Player's name. pub name: String, + /// General score. pub score: u32, + /// How long they've been on the server for. pub duration: f32, + /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): deaths count pub deaths: Option, //the_ship + /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): money amount pub money: Option, //the_ship } +/// Server's rules. #[derive(Debug)] pub struct ServerRules { pub count: u16, pub map: HashMap } +/// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). #[derive(Debug)] pub struct TheShip { pub mode: u8, @@ -72,23 +101,35 @@ pub struct TheShip { pub duration: u8 } +/// Some extra data that the server might provide or not. #[derive(Debug)] pub struct ExtraData { + /// The server's game port number. pub port: Option, + /// Server's SteamID. pub steam_id: Option, + /// Spectator port number for SourceTV. pub tv_port: Option, + /// Name of the spectator server for SourceTV. pub tv_name: Option, + /// Tags that describe the game according to the server. pub keywords: Option, + /// The server's 64-bit GameID. pub game_id: Option } +/// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). #[derive(PartialEq)] pub enum Request { + /// Known as `A2S_INFO` INFO, + /// Known as `A2S_PLAYERS` PLAYERS, + /// Known as `A2S_RULES` RULES } +/// Supported app id's #[derive(PartialEq)] pub enum App { TF2 = 440, @@ -109,6 +150,7 @@ impl TryFrom for App { } } +/// What data to gather, purely used only with the query function. pub struct GatheringSettings { pub players: bool, pub rules: bool @@ -152,6 +194,7 @@ impl ValveProtocol { Ok(final_packet) } + /// Ask for a specific request only. pub fn get_request_data(&self, app: &App, kind: Request) -> Result, GDError> { let info_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; let players_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF]; @@ -191,7 +234,8 @@ impl ValveProtocol { } } - fn get_server_info(&self, app: &App) -> Result { + /// Get the server information's. + pub fn get_server_info(&self, app: &App) -> Result { let buf = self.get_request_data(app, Request::INFO)?; let mut pos = 0; @@ -260,7 +304,8 @@ impl ValveProtocol { }) } - fn get_server_players(&self, app: &App) -> Result { + /// Get the server player's. + pub fn get_server_players(&self, app: &App) -> Result { let buf = self.get_request_data(app, Request::PLAYERS)?; let mut pos = 0; @@ -290,7 +335,8 @@ impl ValveProtocol { }) } - fn get_server_rules(&self, app: &App) -> Result, GDError> { + /// Get the server rules's. + pub fn get_server_rules(&self, app: &App) -> Result, GDError> { if *app == App::CSGO { //cause csgo response here is broken after feb 21 2014 return Ok(None); } From 3c6cbda0f5372ea384194dca4d95ec974d4c82f2 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 20 Oct 2022 12:56:10 +0300 Subject: [PATCH 022/597] Simplified Valve ServerRules and ServerPlayers --- src/protocols/valve.rs | 50 ++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 0949d9a..6bcc344 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::net::UdpSocket; use crate::errors::GDError; use crate::utils::{buffer, complete_address, concat_u8_arrays}; @@ -23,8 +22,8 @@ pub enum Environment { #[derive(Debug)] pub struct Response { pub info: ServerInfo, - pub players: Option, - pub rules: Option + pub players: Option>, + pub rules: Option> } /// General server information's. @@ -64,16 +63,9 @@ pub struct ServerInfo { pub extra_data: Option } -/// Server's players. +/// A server player. #[derive(Debug)] -pub struct ServerPlayers { - pub count: u8, - pub players: Vec -} - -/// Data about a player -#[derive(Debug)] -pub struct Player { +pub struct ServerPlayer { /// Player's name. pub name: String, /// General score. @@ -86,11 +78,11 @@ pub struct Player { pub money: Option, //the_ship } -/// Server's rules. +/// A server rule. #[derive(Debug)] -pub struct ServerRules { - pub count: u16, - pub map: HashMap +pub struct ServerRule { + pub name: String, + pub value: String } /// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). @@ -305,16 +297,16 @@ impl ValveProtocol { } /// Get the server player's. - pub fn get_server_players(&self, app: &App) -> Result { + pub fn get_server_players(&self, app: &App) -> Result, GDError> { let buf = self.get_request_data(app, Request::PLAYERS)?; let mut pos = 0; let count = buffer::get_u8(&buf, &mut pos)?; - let mut players: Vec = Vec::new(); + let mut players: Vec = Vec::new(); for _ in 0..count { pos += 1; //skip the index byte - players.push(Player { + players.push(ServerPlayer { name: buffer::get_string(&buf, &mut pos)?, score: buffer::get_u32_le(&buf, &mut pos)?, duration: buffer::get_f32_le(&buf, &mut pos)?, @@ -329,14 +321,11 @@ impl ValveProtocol { }); } - Ok(ServerPlayers { - count, - players - }) + Ok(players) } /// Get the server rules's. - pub fn get_server_rules(&self, app: &App) -> Result, GDError> { + pub fn get_server_rules(&self, app: &App) -> Result>, GDError> { if *app == App::CSGO { //cause csgo response here is broken after feb 21 2014 return Ok(None); } @@ -345,17 +334,16 @@ impl ValveProtocol { let mut pos = 0; let count = buffer::get_u16_le(&buf, &mut pos)?; - let mut rules: HashMap = HashMap::new(); + let mut rules: Vec = Vec::new(); for _ in 0..count { - rules.insert(buffer::get_string(&buf, &mut pos)?, //name - buffer::get_string(&buf, &mut pos)?); //value + rules.push(ServerRule { + name: buffer::get_string(&buf, &mut pos)?, + value: buffer::get_string(&buf, &mut pos)? + }) } - Ok(Some(ServerRules { - count, - map: rules - })) + Ok(Some(rules)) } pub(crate) fn query(app: App, address: &str, port: u16, gather: GatheringSettings) -> Result { From 21d2bc45a1b0d3352042aa602c9bd38d85cebc21 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 20 Oct 2022 13:09:11 +0300 Subject: [PATCH 023/597] Bumped version and modified README --- CHANGELOG.md | 18 ++++++++++++------ Cargo.toml | 2 +- README.md | 10 +++++----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82a9321..cfade97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,17 @@ Who knows what the future holds... -# 0.0.1 - 16/09/2022 -The first usable version of the crate, yay! -It brings: -- Initial implementation of the [valve server query protocol](https://developer.valvesoftware.com/wiki/Server_queries). -- Initial [Team Fortress 2](https://en.wikipedia.org/wiki/Team_Fortress_2) support. +# 0.0.2 - 20/10/2022 +Further implementation of the Valve protocol (PLAYERS and RULES queries). +[Counter Strike: Global Offensive](https://store.steampowered.com/app/730/CounterStrike_Global_Offensive/) implementation. +[The Ship](https://developer.valvesoftware.com/wiki/The_Ship) implementation. +The library now has error handling. -# 0.0.0 - 15/09/2022 +# 0.0.1 - 16/10/2022 +The first usable version of the crate, yay! +It brings: +Initial implementation of the [Valve server query protocol](https://developer.valvesoftware.com/wiki/Server_queries). +Initial [Team Fortress 2](https://en.wikipedia.org/wiki/Team_Fortress_2) support. + +# 0.0.0 - 15/10/2022 The first *markdown*, the crate is unusable as it doesn't contain anything helpful. diff --git a/Cargo.toml b/Cargo.toml index 0b5c3e9..fb8d613 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.0.1" +version = "0.0.2" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" diff --git a/README.md b/README.md index a419e4f..4ef0c8c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ rust-GameDig is a game server/services query library, capable of querying the st MSRV is `1.58.1` and the code is cross-platform. -# Example -Basic usage of the library is: +## Usage +Just pick a game, provide the ip and the port (can be optional) then query on it. ```rust use gamedig::games::tf2; @@ -18,12 +18,12 @@ fn main() { ``` To see more examples, see the [examples](examples) folder. -# Documentation +## Documentation The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). Curious about the history and what changed between versions? you can see just that in the [CHANGELOG](CHANGELOG.md) file. -# Games List +## Games List To see the supported (or the planned to support) games, see [GAMES](GAMES.md). -# Contributing +## Contributing If you want see your favorite game/service being supported here, open an issue and I'll prioritize it! (or do a pull request if you want to implement it yourself) From aac3a483c0e89ca06e015add77753b21c3aa2baa Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 20 Oct 2022 14:41:17 +0300 Subject: [PATCH 024/597] Added PROTOCOLS.md, modified README and updated GAMES --- GAMES.md | 16 ++++++++++------ PROTOCOLS.md | 8 ++++++++ README.md | 6 +++--- 3 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 PROTOCOLS.md diff --git a/GAMES.md b/GAMES.md index 13fd16b..777933e 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,7 +1,11 @@ -# Supported games: -| Type ID | Name | Notes | -|---------|-----------------|-------| -| TF2 | Team Fortress 2 | | -# Planned to add support: -All Valve titles. +# Supported games: +| ID | Name | Protocol | Notes | +|----------|----------------------------------|----------------|----------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| The_Ship | The Ship | Valve Protocol | | +| CSGO | Counter Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | + +## Planned to add support: +All Valve titles. +Minecraft. diff --git a/PROTOCOLS.md b/PROTOCOLS.md new file mode 100644 index 0000000..48aad3e --- /dev/null +++ b/PROTOCOLS.md @@ -0,0 +1,8 @@ + +# Supported protocols: +| Name | Documentation reference | Used by | +|----------------|---------------------------------------------------------------------------|---------------------| +| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, The_ship | + +## Planned to add support: +Minecraft protocol diff --git a/README.md b/README.md index 4ef0c8c..248cfaa 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ rust-GameDig is a game server/services query library, capable of querying the st MSRV is `1.58.1` and the code is cross-platform. +## Games/Protocols List +To see the supported (or the planned to support) games/protocols, see [GAMES](GAMES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. + ## Usage Just pick a game, provide the ip and the port (can be optional) then query on it. ```rust @@ -22,8 +25,5 @@ To see more examples, see the [examples](examples) folder. The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). Curious about the history and what changed between versions? you can see just that in the [CHANGELOG](CHANGELOG.md) file. -## Games List -To see the supported (or the planned to support) games, see [GAMES](GAMES.md). - ## Contributing If you want see your favorite game/service being supported here, open an issue and I'll prioritize it! (or do a pull request if you want to implement it yourself) From 526aef9acc01126fa0286820d3924225a5784347 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 20 Oct 2022 23:08:45 +0300 Subject: [PATCH 025/597] Games structures (#2) * tf2 skeleton * tf2 structure done * csgo structure * the_ship structure --- src/games/csgo.rs | 78 +++++++++++++++++++++++++++++++++++-- src/games/tf2.rs | 78 +++++++++++++++++++++++++++++++++++-- src/games/the_ship.rs | 90 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 236 insertions(+), 10 deletions(-) diff --git a/src/games/csgo.rs b/src/games/csgo.rs index cc45ab4..20d0416 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,12 +1,82 @@ use crate::errors::GDError; -use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; +use crate::valve; +use crate::valve::{ValveProtocol, App, GatheringSettings, ServerPlayer, Server}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: Vec, + pub online_players: u8, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + online_players: response.info.players, + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords + } + } +} pub fn query(address: &str, port: Option) -> Result { - ValveProtocol::query(App::CSGO, address, match port { + let valve_response = ValveProtocol::query(App::CSGO, address, match port { None => 27015, Some(port) => port }, GatheringSettings { players: true, - rules: true - }) + rules: false // cause csgo doesnt reply with rules anymore + })?; + + Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index dcee6fb..528961f 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,12 +1,84 @@ use crate::errors::GDError; -use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; +use crate::valve; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: Vec, + pub online_players: u8, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + online_players: response.info.players, + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} pub fn query(address: &str, port: Option) -> Result { - ValveProtocol::query(App::TF2, address, match port { + let valve_response = ValveProtocol::query(App::TF2, address, match port { None => 27015, Some(port) => port }, GatheringSettings { players: true, rules: true - }) + })?; + + Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/the_ship.rs b/src/games/the_ship.rs index a9d1fba..0561930 100644 --- a/src/games/the_ship.rs +++ b/src/games/the_ship.rs @@ -1,12 +1,96 @@ use crate::errors::GDError; -use crate::valve::{ValveProtocol, App, GatheringSettings, Response}; +use crate::valve; +use crate::valve::{ValveProtocol, App, GatheringSettings, ServerPlayer, Server, ServerRule}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32, + pub deaths: u32, + pub money: u32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration, + deaths: player.deaths.unwrap(), + money: player.money.unwrap() + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: Vec, + pub online_players: u8, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec, + pub mode: u8, + pub witnesses: u8, + pub duration: u8 +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + let the_unwrapped_ship = response.info.the_ship.unwrap(); + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + online_players: response.info.players, + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap(), + mode: the_unwrapped_ship.mode, + witnesses: the_unwrapped_ship.witnesses, + duration: the_unwrapped_ship.duration + } + } +} pub fn query(address: &str, port: Option) -> Result { - ValveProtocol::query(App::TheShip, address, match port { + let valve_response = ValveProtocol::query(App::TheShip, address, match port { None => 27015, Some(port) => port }, GatheringSettings { players: true, rules: true - }) + })?; + + Ok(Response::new_from_valve_response(valve_response)) } From 15e6ad5892bb73a7d236bebea3076024abff3dbf Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 20 Oct 2022 23:19:57 +0300 Subject: [PATCH 026/597] Replaced Result with GDResult --- src/errors.rs | 3 +++ src/games/csgo.rs | 5 ++--- src/games/tf2.rs | 5 ++--- src/games/the_ship.rs | 5 ++--- src/protocols/valve.rs | 18 +++++++++--------- src/utils.rs | 14 +++++++------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 6e2210f..f4713d4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,9 @@ use core::fmt; use std::fmt::Formatter; +/// Result of Type and GDError. +pub type GDResult = Result; + /// GameDigError, every error you can encounter using the library. #[derive(Debug, Clone)] pub enum GDError { diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 20d0416..d946e83 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,5 +1,4 @@ -use crate::errors::GDError; -use crate::valve; +use crate::{GDResult, valve}; use crate::valve::{ValveProtocol, App, GatheringSettings, ServerPlayer, Server}; #[derive(Debug)] @@ -69,7 +68,7 @@ impl Response { } } -pub fn query(address: &str, port: Option) -> Result { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::CSGO, address, match port { None => 27015, Some(port) => port diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 528961f..f25a94c 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,5 +1,4 @@ -use crate::errors::GDError; -use crate::valve; +use crate::{GDResult, valve}; use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; #[derive(Debug)] @@ -71,7 +70,7 @@ impl Response { } } -pub fn query(address: &str, port: Option) -> Result { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::TF2, address, match port { None => 27015, Some(port) => port diff --git a/src/games/the_ship.rs b/src/games/the_ship.rs index 0561930..6f27695 100644 --- a/src/games/the_ship.rs +++ b/src/games/the_ship.rs @@ -1,5 +1,4 @@ -use crate::errors::GDError; -use crate::valve; +use crate::{GDResult, valve}; use crate::valve::{ValveProtocol, App, GatheringSettings, ServerPlayer, Server, ServerRule}; #[derive(Debug)] @@ -83,7 +82,7 @@ impl Response { } } -pub fn query(address: &str, port: Option) -> Result { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::TheShip, address, match port { None => 27015, Some(port) => port diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 6bcc344..82e1a74 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,5 +1,5 @@ use std::net::UdpSocket; -use crate::errors::GDError; +use crate::{GDError, GDResult}; use crate::utils::{buffer, complete_address, concat_u8_arrays}; /// The type of the server. @@ -132,7 +132,7 @@ pub enum App { impl TryFrom for App { type Error = GDError; - fn try_from(value: u16) -> Result { + fn try_from(value: u16) -> GDResult { match value { x if x == App::TF2 as u16 => Ok(App::TF2), x if x == App::CSGO as u16 => Ok(App::CSGO), @@ -163,18 +163,18 @@ impl ValveProtocol { } } - fn send(&self, data: &[u8]) -> Result<(), GDError> { + fn send(&self, data: &[u8]) -> GDResult<()> { self.socket.send_to(&data, &self.complete_address).map_err(|e| GDError::PacketSend(e.to_string()))?; Ok(()) } - fn receive(&self, buffer_size: usize) -> Result, GDError> { + fn receive(&self, buffer_size: usize) -> GDResult> { let mut buffer: Vec = vec![0; buffer_size]; let (amt, _) = self.socket.recv_from(&mut buffer.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?; Ok(buffer[..amt].to_vec()) } - fn receive_truncated(&self, initial_packet: &[u8]) -> Result, GDError> { + fn receive_truncated(&self, initial_packet: &[u8]) -> GDResult> { let count = initial_packet[8] - 1; let mut final_packet: Vec = initial_packet.to_vec().drain(17..).collect::>(); @@ -187,7 +187,7 @@ impl ValveProtocol { } /// Ask for a specific request only. - pub fn get_request_data(&self, app: &App, kind: Request) -> Result, GDError> { + pub fn get_request_data(&self, app: &App, kind: Request) -> GDResult> { let info_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; let players_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF]; let rules_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0xFF, 0xFF, 0xFF, 0xFF]; @@ -227,7 +227,7 @@ impl ValveProtocol { } /// Get the server information's. - pub fn get_server_info(&self, app: &App) -> Result { + pub fn get_server_info(&self, app: &App) -> GDResult { let buf = self.get_request_data(app, Request::INFO)?; let mut pos = 0; @@ -297,7 +297,7 @@ impl ValveProtocol { } /// Get the server player's. - pub fn get_server_players(&self, app: &App) -> Result, GDError> { + pub fn get_server_players(&self, app: &App) -> GDResult> { let buf = self.get_request_data(app, Request::PLAYERS)?; let mut pos = 0; @@ -325,7 +325,7 @@ impl ValveProtocol { } /// Get the server rules's. - pub fn get_server_rules(&self, app: &App) -> Result>, GDError> { + pub fn get_server_rules(&self, app: &App) -> GDResult>> { if *app == App::CSGO { //cause csgo response here is broken after feb 21 2014 return Ok(None); } diff --git a/src/utils.rs b/src/utils.rs index 6dd678d..cdce785 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,5 @@ use std::ops::Add; -use crate::GDError; +use crate::{GDResult, GDError}; pub fn concat_u8_arrays(first: &[u8], second: &[u8]) -> Vec { [first, second].concat() @@ -12,7 +12,7 @@ pub fn complete_address(address: &str, port: u16) -> String { pub mod buffer { use super::*; - pub fn get_u8(buf: &[u8], pos: &mut usize) -> Result { + pub fn get_u8(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } @@ -22,7 +22,7 @@ pub mod buffer { Ok(value) } - pub fn get_u16_le(buf: &[u8], pos: &mut usize) -> Result { + pub fn get_u16_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 1 { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } @@ -32,7 +32,7 @@ pub mod buffer { Ok(value) } - pub fn get_u32_le(buf: &[u8], pos: &mut usize) -> Result { + pub fn get_u32_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 3 { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } @@ -42,7 +42,7 @@ pub mod buffer { Ok(value) } - pub fn get_f32_le(buf: &[u8], pos: &mut usize) -> Result { + pub fn get_f32_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 3 { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } @@ -52,7 +52,7 @@ pub mod buffer { Ok(value) } - pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> Result { + pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 7 { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } @@ -62,7 +62,7 @@ pub mod buffer { Ok(value) } - pub fn get_string(buf: &[u8], pos: &mut usize) -> Result { + pub fn get_string(buf: &[u8], pos: &mut usize) -> GDResult { let sub_buf = &buf[*pos..]; let first_null_position = sub_buf.iter().position(|&x| x == 0).ok_or(GDError::PacketBad("Unexpectedly formatted packet.".to_string()))?; let value = std::str::from_utf8(&sub_buf[..first_null_position]).unwrap().to_string(); From 675ed13493bfc59ac51987048065feae5f642b7b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 20 Oct 2022 23:25:17 +0300 Subject: [PATCH 027/597] Renamed players and online_players to players_details and players --- src/games/csgo.rs | 8 ++++---- src/games/tf2.rs | 8 ++++---- src/games/the_ship.rs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/games/csgo.rs b/src/games/csgo.rs index d946e83..3419228 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -24,8 +24,8 @@ pub struct Response { pub name: String, pub map: String, pub game: String, - pub players: Vec, - pub online_players: u8, + pub players: u8, + pub players_details: Vec, pub max_players: u8, pub bots: u8, pub server_type: Server, @@ -51,8 +51,8 @@ impl Response { name: response.info.name, map: response.info.map, game: response.info.game, - players: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - online_players: response.info.players, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), max_players: response.info.max_players, bots: response.info.bots, server_type: response.info.server_type, diff --git a/src/games/tf2.rs b/src/games/tf2.rs index f25a94c..56a5c1e 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -24,8 +24,8 @@ pub struct Response { pub name: String, pub map: String, pub game: String, - pub players: Vec, - pub online_players: u8, + pub players: u8, + pub players_details: Vec, pub max_players: u8, pub bots: u8, pub server_type: Server, @@ -52,8 +52,8 @@ impl Response { name: response.info.name, map: response.info.map, game: response.info.game, - players: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - online_players: response.info.players, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), max_players: response.info.max_players, bots: response.info.bots, server_type: response.info.server_type, diff --git a/src/games/the_ship.rs b/src/games/the_ship.rs index 6f27695..9d98b3b 100644 --- a/src/games/the_ship.rs +++ b/src/games/the_ship.rs @@ -28,8 +28,8 @@ pub struct Response { pub name: String, pub map: String, pub game: String, - pub players: Vec, - pub online_players: u8, + pub players: u8, + pub players_details: Vec, pub max_players: u8, pub bots: u8, pub server_type: Server, @@ -61,8 +61,8 @@ impl Response { name: response.info.name, map: response.info.map, game: response.info.game, - players: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - online_players: response.info.players, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), max_players: response.info.max_players, bots: response.info.bots, server_type: response.info.server_type, From b5141e8196e60b0d42b59bc19269239565742a29 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 20 Oct 2022 23:45:31 +0300 Subject: [PATCH 028/597] Modified README and added SERVICES.md --- README.md | 8 ++++---- SERVICES.md | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 SERVICES.md diff --git a/README.md b/README.md index 248cfaa..b648567 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # rust-gamedig -rust-GameDig is a game server/services query library, capable of querying the status of many games/services, this library brings what [node-GameDig](https://github.com/gamedig/node-gamedig) does, to pure Rust! +**rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! MSRV is `1.58.1` and the code is cross-platform. -## Games/Protocols List -To see the supported (or the planned to support) games/protocols, see [GAMES](GAMES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. +## Games/Services/Protocols List +To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. ## Usage -Just pick a game, provide the ip and the port (can be optional) then query on it. +Just pick a game/service, provide the ip and the port (can be optional) then query on it. ```rust use gamedig::games::tf2; diff --git a/SERVICES.md b/SERVICES.md new file mode 100644 index 0000000..38f4669 --- /dev/null +++ b/SERVICES.md @@ -0,0 +1,8 @@ + +# Supported games: +| ID | Name | Notes | +|-----|------|-------| +| --- | ---- | ----- | + +## Planned to add support: +TeamSpeak From aefd8cc43cc55383592bdf363bb8a4fe90d5d226 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 21 Oct 2022 12:35:33 +0300 Subject: [PATCH 029/597] Added support for Counter-Strike: Source --- GAMES.md | 3 +- examples/css.rs | 10 +++++ src/games/css.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/games/mod.rs | 1 + src/protocols/valve.rs | 2 + 5 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 examples/css.rs create mode 100644 src/games/css.rs diff --git a/GAMES.md b/GAMES.md index 777933e..e68a751 100644 --- a/GAMES.md +++ b/GAMES.md @@ -4,7 +4,8 @@ |----------|----------------------------------|----------------|----------------------------------| | TF2 | Team Fortress 2 | Valve Protocol | | | The_Ship | The Ship | Valve Protocol | | -| CSGO | Counter Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | +| CSS | Counter-Strike: Source | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/examples/css.rs b/examples/css.rs new file mode 100644 index 0000000..53aea99 --- /dev/null +++ b/examples/css.rs @@ -0,0 +1,10 @@ + +use gamedig::games::css; + +fn main() { + let response = css::query("104.128.58.206", None); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/css.rs b/src/games/css.rs new file mode 100644 index 0000000..9e23ad6 --- /dev/null +++ b/src/games/css.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::CSS, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index a0f3e63..fc3ddf8 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -4,3 +4,4 @@ pub mod tf2; pub mod the_ship; pub mod csgo; +pub mod css; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 82e1a74..6b0f364 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -124,6 +124,7 @@ pub enum Request { /// Supported app id's #[derive(PartialEq)] pub enum App { + CSS = 240, TF2 = 440, CSGO = 730, TheShip = 2400 @@ -134,6 +135,7 @@ impl TryFrom for App { fn try_from(value: u16) -> GDResult { match value { + x if x == App::CSS as u16 => Ok(App::CSS), x if x == App::TF2 as u16 => Ok(App::TF2), x if x == App::CSGO as u16 => Ok(App::CSGO), x if x == App::TheShip as u16 => Ok(App::TheShip), From a5f9e755ff98cdf146a7a73348255848d74f0405 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 21 Oct 2022 12:45:30 +0300 Subject: [PATCH 030/597] Added Day of Defeat: Source support and renamed The_Ship to TS --- GAMES.md | 13 ++--- examples/{the_ship.rs => dods.rs} | 20 ++++---- examples/ts.rs | 10 ++++ src/games/dods.rs | 83 +++++++++++++++++++++++++++++++ src/games/mod.rs | 3 +- src/games/{the_ship.rs => ts.rs} | 2 +- src/protocols/valve.rs | 14 +++--- 7 files changed, 121 insertions(+), 24 deletions(-) rename examples/{the_ship.rs => dods.rs} (58%) create mode 100644 examples/ts.rs create mode 100644 src/games/dods.rs rename src/games/{the_ship.rs => ts.rs} (93%) diff --git a/GAMES.md b/GAMES.md index e68a751..d1b32e9 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,11 +1,12 @@ # Supported games: -| ID | Name | Protocol | Notes | -|----------|----------------------------------|----------------|----------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| The_Ship | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | -| CSS | Counter-Strike: Source | Valve Protocol | | +| ID | Name | Protocol | Notes | +|------|----------------------------------|----------------|----------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| TS | The Ship | Valve Protocol | | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | +| CSS | Counter-Strike: Source | Valve Protocol | | +| DODS | Day of Defeat: Source | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/examples/the_ship.rs b/examples/dods.rs similarity index 58% rename from examples/the_ship.rs rename to examples/dods.rs index 14524e1..d5ebf40 100644 --- a/examples/the_ship.rs +++ b/examples/dods.rs @@ -1,10 +1,10 @@ - -use gamedig::games::the_ship; - -fn main() { - let response = the_ship::query("46.4.48.226", Some(27017)); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} + +use gamedig::games::dods; + +fn main() { + let response = dods::query("88.99.28.151", Some(27055)); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/examples/ts.rs b/examples/ts.rs new file mode 100644 index 0000000..5c97974 --- /dev/null +++ b/examples/ts.rs @@ -0,0 +1,10 @@ + +use gamedig::games::ts; + +fn main() { + let response = ts::query("46.4.48.226", Some(27017)); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/dods.rs b/src/games/dods.rs new file mode 100644 index 0000000..e535a3c --- /dev/null +++ b/src/games/dods.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::DODS, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index fc3ddf8..af2be47 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -2,6 +2,7 @@ //! Currently supported games. pub mod tf2; -pub mod the_ship; +pub mod ts; pub mod csgo; pub mod css; +pub mod dods; diff --git a/src/games/the_ship.rs b/src/games/ts.rs similarity index 93% rename from src/games/the_ship.rs rename to src/games/ts.rs index 9d98b3b..d9195eb 100644 --- a/src/games/the_ship.rs +++ b/src/games/ts.rs @@ -83,7 +83,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::TheShip, address, match port { + let valve_response = ValveProtocol::query(App::TS, address, match port { None => 27015, Some(port) => port }, GatheringSettings { diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 6b0f364..0ebaf83 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -125,9 +125,10 @@ pub enum Request { #[derive(PartialEq)] pub enum App { CSS = 240, + DODS = 300, TF2 = 440, CSGO = 730, - TheShip = 2400 + TS = 2400 } impl TryFrom for App { @@ -136,9 +137,10 @@ impl TryFrom for App { fn try_from(value: u16) -> GDResult { match value { x if x == App::CSS as u16 => Ok(App::CSS), + x if x == App::DODS as u16 => Ok(App::DODS), x if x == App::TF2 as u16 => Ok(App::TF2), x if x == App::CSGO as u16 => Ok(App::CSGO), - x if x == App::TheShip as u16 => Ok(App::TheShip), + x if x == App::TS as u16 => Ok(App::TS), _ => Err(GDError::UnknownEnumCast), } } @@ -221,7 +223,7 @@ impl ValveProtocol { self.send(&challenge_packet)?; let mut packet = self.receive(DEFAULT_PACKET_SIZE)?; - if (packet[0] == 0xFE || (packet[0] == 0xFF && packet[4] == 0x45)) && (*app != App::TheShip) { //'E' + if (packet[0] == 0xFE || (packet[0] == 0xFF && packet[4] == 0x45)) && (*app != App::TS) { //'E' self.receive_truncated(&packet) } else { Ok(packet.drain(5..).collect::>()) @@ -257,7 +259,7 @@ impl ValveProtocol { }, has_password: buffer::get_u8(&buf, &mut pos)? == 1, vac_secured: buffer::get_u8(&buf, &mut pos)? == 1, - the_ship: match *app == App::TheShip { + the_ship: match *app == App::TS { false => None, true => Some(TheShip { mode: buffer::get_u8(&buf, &mut pos)?, @@ -312,11 +314,11 @@ impl ValveProtocol { name: buffer::get_string(&buf, &mut pos)?, score: buffer::get_u32_le(&buf, &mut pos)?, duration: buffer::get_f32_le(&buf, &mut pos)?, - deaths: match *app == App::TheShip { + deaths: match *app == App::TS { false => None, true => Some(buffer::get_u32_le(&buf, &mut pos)?) }, - money: match *app == App::TheShip { + money: match *app == App::TS { false => None, true => Some(buffer::get_u32_le(&buf, &mut pos)?) } From 046544ea27a286f3dd5cc7468b535c7a2552f7fc Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 21 Oct 2022 12:55:17 +0300 Subject: [PATCH 031/597] Added Garry's Mod support --- examples/gm.rs | 10 +++++ src/games/gm.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/games/mod.rs | 1 + src/protocols/valve.rs | 10 ++++- 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 examples/gm.rs create mode 100644 src/games/gm.rs diff --git a/examples/gm.rs b/examples/gm.rs new file mode 100644 index 0000000..0ce21bb --- /dev/null +++ b/examples/gm.rs @@ -0,0 +1,10 @@ + +use gamedig::games::gm; + +fn main() { + let response = gm::query("148.59.74.84", None); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/gm.rs b/src/games/gm.rs new file mode 100644 index 0000000..12c4a90 --- /dev/null +++ b/src/games/gm.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::GM, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index af2be47..8e23504 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -6,3 +6,4 @@ pub mod ts; pub mod csgo; pub mod css; pub mod dods; +pub mod gm; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 0ebaf83..0c3670d 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -124,11 +124,18 @@ pub enum Request { /// Supported app id's #[derive(PartialEq)] pub enum App { + /// Counter-Strike: Source CSS = 240, + /// Day of Defeat: Sourcec DODS = 300, + /// Team Fortress 2 TF2 = 440, + /// Counter-Strike: Global Offensive CSGO = 730, - TS = 2400 + /// The Ship + TS = 2400, + /// Garry's Mod + GM = 4000, } impl TryFrom for App { @@ -141,6 +148,7 @@ impl TryFrom for App { x if x == App::TF2 as u16 => Ok(App::TF2), x if x == App::CSGO as u16 => Ok(App::CSGO), x if x == App::TS as u16 => Ok(App::TS), + x if x == App::GM as u16 => Ok(App::GM), _ => Err(GDError::UnknownEnumCast), } } From d477bbb178984e5b57e188697f404cb9b864e3c0 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 21 Oct 2022 13:03:15 +0300 Subject: [PATCH 032/597] Added support for Left 4 Dead and Left 4 Dead 2 --- GAMES.md | 2 + examples/l4d.rs | 10 +++++ examples/l4d2.rs | 10 +++++ src/games/l4d.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/games/l4d2.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/games/mod.rs | 2 + src/protocols/valve.rs | 6 +++ 7 files changed, 196 insertions(+) create mode 100644 examples/l4d.rs create mode 100644 examples/l4d2.rs create mode 100644 src/games/l4d.rs create mode 100644 src/games/l4d2.rs diff --git a/GAMES.md b/GAMES.md index d1b32e9..7ea42f0 100644 --- a/GAMES.md +++ b/GAMES.md @@ -7,6 +7,8 @@ | CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | | CSS | Counter-Strike: Source | Valve Protocol | | | DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/examples/l4d.rs b/examples/l4d.rs new file mode 100644 index 0000000..4c0895d --- /dev/null +++ b/examples/l4d.rs @@ -0,0 +1,10 @@ + +use gamedig::games::l4d; + +fn main() { + let response = l4d::query("207.246.72.170", Some(26999)); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/examples/l4d2.rs b/examples/l4d2.rs new file mode 100644 index 0000000..754815b --- /dev/null +++ b/examples/l4d2.rs @@ -0,0 +1,10 @@ + +use gamedig::games::l4d2; + +fn main() { + let response = l4d2::query("74.91.124.246", None); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/l4d.rs b/src/games/l4d.rs new file mode 100644 index 0000000..deb42e5 --- /dev/null +++ b/src/games/l4d.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::L4D, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs new file mode 100644 index 0000000..45a3e43 --- /dev/null +++ b/src/games/l4d2.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::L4D2, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 8e23504..4486384 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -7,3 +7,5 @@ pub mod csgo; pub mod css; pub mod dods; pub mod gm; +pub mod l4d; +pub mod l4d2; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 0c3670d..6fa10ea 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -130,6 +130,10 @@ pub enum App { DODS = 300, /// Team Fortress 2 TF2 = 440, + /// Left 4 Dead + L4D = 500, + /// Left 4 Dead + L4D2 = 550, /// Counter-Strike: Global Offensive CSGO = 730, /// The Ship @@ -146,6 +150,8 @@ impl TryFrom for App { x if x == App::CSS as u16 => Ok(App::CSS), x if x == App::DODS as u16 => Ok(App::DODS), x if x == App::TF2 as u16 => Ok(App::TF2), + x if x == App::L4D as u16 => Ok(App::L4D), + x if x == App::L4D2 as u16 => Ok(App::L4D2), x if x == App::CSGO as u16 => Ok(App::CSGO), x if x == App::TS as u16 => Ok(App::TS), x if x == App::GM as u16 => Ok(App::GM), From 4e9458f1022bd8b4a4965af24b1330ea969e2ab0 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 21 Oct 2022 13:10:09 +0300 Subject: [PATCH 033/597] Added support for Half-Life 2 Deathmatch --- GAMES.md | 19 +++++----- examples/hl2dm.rs | 10 +++++ src/games/hl2dm.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/games/mod.rs | 1 + src/protocols/valve.rs | 3 ++ 5 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 examples/hl2dm.rs create mode 100644 src/games/hl2dm.rs diff --git a/GAMES.md b/GAMES.md index 7ea42f0..8d8350a 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,14 +1,15 @@ # Supported games: -| ID | Name | Protocol | Notes | -|------|----------------------------------|----------------|----------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| TS | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | -| CSS | Counter-Strike: Source | Valve Protocol | | -| DODS | Day of Defeat: Source | Valve Protocol | | -| L4D | Left 4 Dead | Valve Protocol | | -| L4D2 | Left 4 Dead 2 | Valve Protocol | | +| ID | Name | Protocol | Notes | +|-------|----------------------------------|----------------|----------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| TS | The Ship | Valve Protocol | | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | +| CSS | Counter-Strike: Source | Valve Protocol | | +| DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | +| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/examples/hl2dm.rs b/examples/hl2dm.rs new file mode 100644 index 0000000..49d642e --- /dev/null +++ b/examples/hl2dm.rs @@ -0,0 +1,10 @@ + +use gamedig::games::hl2dm; + +fn main() { + let response = hl2dm::query("74.91.118.209", None); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs new file mode 100644 index 0000000..2ddf874 --- /dev/null +++ b/src/games/hl2dm.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::HL2DM, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 4486384..d5d8e73 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -9,3 +9,4 @@ pub mod dods; pub mod gm; pub mod l4d; pub mod l4d2; +pub mod hl2dm; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 6fa10ea..23e9d10 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -128,6 +128,8 @@ pub enum App { CSS = 240, /// Day of Defeat: Sourcec DODS = 300, + /// Half-Life 2 Deathmatch + HL2DM = 320, /// Team Fortress 2 TF2 = 440, /// Left 4 Dead @@ -148,6 +150,7 @@ impl TryFrom for App { fn try_from(value: u16) -> GDResult { match value { x if x == App::CSS as u16 => Ok(App::CSS), + x if x == App::HL2DM as u16 => Ok(App::HL2DM), x if x == App::DODS as u16 => Ok(App::DODS), x if x == App::TF2 as u16 => Ok(App::TF2), x if x == App::L4D as u16 => Ok(App::L4D), From e621a9aedd3ae34baba9966b38d94c6b7fb83083 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 22 Oct 2022 01:22:09 +0300 Subject: [PATCH 034/597] Better packet structure (#3) * [packet_structure] Initial implementation * [packet_structure] Fixed on tf2 * [packet_structure] Fixed info request --- GAMES.md | 20 ++--- examples/csgo.rs | 2 +- src/protocols/valve.rs | 185 ++++++++++++++++++++++++++++++----------- src/utils.rs | 15 ---- 4 files changed, 148 insertions(+), 74 deletions(-) diff --git a/GAMES.md b/GAMES.md index 8d8350a..bf3adf4 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,15 +1,15 @@ # Supported games: -| ID | Name | Protocol | Notes | -|-------|----------------------------------|----------------|----------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| TS | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | -| CSS | Counter-Strike: Source | Valve Protocol | | -| DODS | Day of Defeat: Source | Valve Protocol | | -| L4D | Left 4 Dead | Valve Protocol | | -| L4D2 | Left 4 Dead 2 | Valve Protocol | | -| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | +| ID | Name | Protocol | Notes | +|-------|----------------------------------|----------------|------------------------------------------------------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| TS | The Ship | Valve Protocol | | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | +| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, the Rules query crashes. | +| DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | +| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/examples/csgo.rs b/examples/csgo.rs index 48f0943..ca3512e 100644 --- a/examples/csgo.rs +++ b/examples/csgo.rs @@ -2,7 +2,7 @@ use gamedig::games::csgo; fn main() { - let response = csgo::query("51.38.142.109", None); + let response = csgo::query("216.52.148.47", None); match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 23e9d10..d320ce4 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,6 +1,6 @@ use std::net::UdpSocket; use crate::{GDError, GDResult}; -use crate::utils::{buffer, complete_address, concat_u8_arrays}; +use crate::utils::{buffer, complete_address}; /// The type of the server. #[derive(Debug)] @@ -111,14 +111,15 @@ pub struct ExtraData { } /// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). -#[derive(PartialEq)] +#[derive(PartialEq, Clone)] +#[repr(u8)] pub enum Request { /// Known as `A2S_INFO` - INFO, + INFO = 0x54, /// Known as `A2S_PLAYERS` - PLAYERS, + PLAYERS = 0x55, /// Known as `A2S_RULES` - RULES + RULES = 0x56 } /// Supported app id's @@ -176,6 +177,103 @@ pub struct ValveProtocol { static DEFAULT_PACKET_SIZE: usize = 2048; +#[derive(Debug, Clone)] +struct Packet { + pub header: u32, + pub kind: u8, + pub payload: Vec +} + +impl Packet { + fn new(buf: &[u8]) -> GDResult { + let mut pos = 0; + Ok(Self { + header: buffer::get_u32_le(&buf, &mut pos)?, + kind: buffer::get_u8(&buf, &mut pos)?, + payload: buf[pos..].to_vec() + }) + } + + fn challenge(kind: Request, challenge: Vec) -> Self { + let mut initial = Packet::initial(kind); + + Self { + header: initial.header, + kind: initial.kind, + payload: match initial.kind { + 0x54 => { + initial.payload.extend(challenge); + initial.payload + }, + _ => challenge + } + } + } + + fn initial(kind: Request) -> Self { + Self { + header: 4294967295, //FF FF FF FF + kind: kind as u8, + payload: match kind { + Request::INFO => String::from("Source Engine Query\0").into_bytes(), + _ => vec![0xFF, 0xFF, 0xFF, 0xFF] + } + } + } + + fn to_bytes(&self) -> Vec { + let mut buf = Vec::from(self.header.to_be_bytes()); + + buf.push(self.kind); + buf.extend(&self.payload); + + buf + } +} + +#[derive(Debug)] +#[allow(dead_code)] //remove this later on +struct SplitPacketInfo { + pub header: u32, + pub id: u32, + pub total: u8, + pub number: u8, + pub size: u16, + pub payload: Vec +} + +impl SplitPacketInfo { + fn new(_app: &App, buf: &[u8]) -> GDResult { + let mut pos = 0; + + let header = buffer::get_u32_le(&buf, &mut pos)?; + let id = buffer::get_u32_le(&buf, &mut pos)?; + let total = buffer::get_u8(&buf, &mut pos)?; + let number = buffer::get_u8(&buf, &mut pos)?; + let size = buffer::get_u16_le(&buf, &mut pos)?; + + let payload = match ((id >> 31) & 1) == 1 { + false => buf[pos..].to_vec(), + true => { + let _decompressed_size = buffer::get_u32_le(&buf, &mut pos)?; + let _uncompressed_crc32 = buffer::get_u32_le(&buf, &mut pos)?; + + //decompress... + vec![] + } + }; + + Ok(Self { + header, + id, + total, + number, + size, + payload + }) + } +} + impl ValveProtocol { fn new(address: &str, port: u16) -> Self { Self { @@ -189,62 +287,53 @@ impl ValveProtocol { Ok(()) } - fn receive(&self, buffer_size: usize) -> GDResult> { - let mut buffer: Vec = vec![0; buffer_size]; - let (amt, _) = self.socket.recv_from(&mut buffer.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?; - Ok(buffer[..amt].to_vec()) - } + fn receive_raw(&self, buffer_size: usize) -> GDResult> { + let mut buf: Vec = vec![0; buffer_size]; + let (amt, _) = self.socket.recv_from(&mut buf.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?; - fn receive_truncated(&self, initial_packet: &[u8]) -> GDResult> { - let count = initial_packet[8] - 1; - let mut final_packet: Vec = initial_packet.to_vec().drain(17..).collect::>(); - - for _ in 0..count { - let mut packet = self.receive(DEFAULT_PACKET_SIZE)?; - final_packet.append(&mut packet.drain(13..).collect::>()); + if amt < 9 { + return Err(GDError::PacketUnderflow("Any Valve Protocol response can't be under 9 bytes long.".to_string())); } - Ok(final_packet) + Ok(buf[..amt].to_vec()) + } + + fn receive(&self, app: &App, buffer_size: usize) -> GDResult { + let mut buf = self.receive_raw(buffer_size)?; + + if buf[0] == 0xFE { //the packet is split + let initial_split_packet_info = SplitPacketInfo::new(app, &buf)?; + let mut final_packet = Packet::new(&initial_split_packet_info.payload)?; + + for _ in 1..initial_split_packet_info.total { + buf = self.receive_raw(buffer_size)?; + let split_packet_info = SplitPacketInfo::new(app, &buf)?; + final_packet.payload.extend(split_packet_info.payload); + } + + Ok(final_packet) + } + else { + Packet::new(&buf) + } } /// Ask for a specific request only. pub fn get_request_data(&self, app: &App, kind: Request) -> GDResult> { - let info_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00]; - let players_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF]; - let rules_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0xFF, 0xFF, 0xFF, 0xFF]; - - let request_initial_packet = match kind { - Request::INFO => info_initial_packet, - Request::PLAYERS => players_initial_packet, - Request::RULES => rules_initial_packet - }; + let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); self.send(&request_initial_packet)?; - let mut initial_receive = self.receive(DEFAULT_PACKET_SIZE)?; + let packet = self.receive(app, DEFAULT_PACKET_SIZE)?; - if initial_receive.len() < 9 { - return Err(GDError::PacketOverflow("Any Valve Protocol response can't be under 9 bytes long.".to_string())); + if packet.kind != 0x41 { //'A' + return Ok(packet.payload.clone()); } - if initial_receive[4] != 0x41 { //'A' - return Ok(initial_receive.drain(5..).collect()); - } - - let challenge: [u8; 4] = [initial_receive[5], initial_receive[6], initial_receive[7], initial_receive[8]]; - let challenge_packet = match kind { - Request::INFO => concat_u8_arrays(&request_initial_packet, &challenge), - Request::PLAYERS => vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, challenge[0], challenge[1], challenge[2], challenge[3]], - Request::RULES => vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56, challenge[0], challenge[1], challenge[2], challenge[3]] - }; + let challenge = packet.payload; + let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); self.send(&challenge_packet)?; - - let mut packet = self.receive(DEFAULT_PACKET_SIZE)?; - if (packet[0] == 0xFE || (packet[0] == 0xFF && packet[4] == 0x45)) && (*app != App::TS) { //'E' - self.receive_truncated(&packet) - } else { - Ok(packet.drain(5..).collect::>()) - } + Ok(self.receive(app, DEFAULT_PACKET_SIZE)?.payload) } /// Get the server information's. @@ -347,7 +436,7 @@ impl ValveProtocol { /// Get the server rules's. pub fn get_server_rules(&self, app: &App) -> GDResult>> { - if *app == App::CSGO { //cause csgo response here is broken after feb 21 2014 + if *app == App::CSGO { //cause csgo wont respond to this since feb 21 2014 update return Ok(None); } diff --git a/src/utils.rs b/src/utils.rs index cdce785..bb91858 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,10 +1,6 @@ use std::ops::Add; use crate::{GDResult, GDError}; -pub fn concat_u8_arrays(first: &[u8], second: &[u8]) -> Vec { - [first, second].concat() -} - pub fn complete_address(address: &str, port: u16) -> String { String::from(address.to_owned() + ":").add(&*port.to_string()) } @@ -75,17 +71,6 @@ pub mod buffer { mod tests { use super::*; - #[test] - fn concat_u8_arrays_test() { - let a: [u8; 2] = [1, 2]; - let b: [u8; 2] = [3, 4]; - let combined = concat_u8_arrays(&a, &b); - assert_eq!(combined[0], a[0]); - assert_eq!(combined[1], a[1]); - assert_eq!(combined[2], b[0]); - assert_eq!(combined[3], b[1]); - } - #[test] fn complete_address_test() { let address = "192.168.0.1"; From 3b4dd9d9e4cbc9f9711508f61deb2ac3bbbeaf9c Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sat, 22 Oct 2022 02:27:11 +0300 Subject: [PATCH 035/597] Decompression support --- Cargo.toml | 2 ++ PROTOCOLS.md | 6 ++-- src/errors.rs | 5 ++- src/protocols/valve.rs | 72 ++++++++++++++++++++++++++++++------------ 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fb8d613..8e233de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,5 @@ keywords = ["server", "verify", "game", "check", "status"] msrv = "1.58.1" [dependencies] +crc32fast = "1.3.2" +bzip2-rs = "0.1.2" diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 48aad3e..ddb2a63 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,8 +1,8 @@ # Supported protocols: -| Name | Documentation reference | Used by | -|----------------|---------------------------------------------------------------------------|---------------------| -| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, The_ship | +| Name | Documentation reference | Used by | Notes | +|----------------|---------------------------------------------------------------------------|------------------------------------------------|----------------------------------------| +| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, TS, CSS, DODS, GM, HL2DM, L4D, L4D2 | Multi-packet decompression not tested. | ## Planned to add support: Minecraft protocol diff --git a/src/errors.rs b/src/errors.rs index f4713d4..a1d199a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,7 +17,9 @@ pub enum GDError { PacketSend(String), /// Couldn't send the receive. PacketReceive(String), - /// Unknown cast while translating a value to an enum + /// Couldn't decompress data. + Decompress(String), + /// Unknown cast while translating a value to an enum. UnknownEnumCast, /// The server queried is not from the queried game. BadGame(String) @@ -31,6 +33,7 @@ impl fmt::Display for GDError { GDError::PacketBad(details) => write!(f, "Packet bad: {details}"), GDError::PacketSend(details) => write!(f, "Couldn't send a packet: {details}"), GDError::PacketReceive(details) => write!(f, "Couldn't receive a packet: {details}"), + GDError::Decompress(details) => write!(f, "Couldn't decompress data: {details}"), GDError::UnknownEnumCast => write!(f, "Unknown enum cast encountered."), GDError::BadGame(details) => write!(f, "Queried another game that the supposed one: {details}"), } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index d320ce4..11c42da 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,4 +1,5 @@ use std::net::UdpSocket; +use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; use crate::utils::{buffer, complete_address}; @@ -233,16 +234,19 @@ impl Packet { #[derive(Debug)] #[allow(dead_code)] //remove this later on -struct SplitPacketInfo { +struct SplitPacket { pub header: u32, pub id: u32, pub total: u8, pub number: u8, pub size: u16, - pub payload: Vec + pub compressed: bool, + pub decompressed_size: Option, + pub uncompressed_crc32: Option, + payload: Vec } -impl SplitPacketInfo { +impl SplitPacket { fn new(_app: &App, buf: &[u8]) -> GDResult { let mut pos = 0; @@ -251,16 +255,10 @@ impl SplitPacketInfo { let total = buffer::get_u8(&buf, &mut pos)?; let number = buffer::get_u8(&buf, &mut pos)?; let size = buffer::get_u16_le(&buf, &mut pos)?; - - let payload = match ((id >> 31) & 1) == 1 { - false => buf[pos..].to_vec(), - true => { - let _decompressed_size = buffer::get_u32_le(&buf, &mut pos)?; - let _uncompressed_crc32 = buffer::get_u32_le(&buf, &mut pos)?; - - //decompress... - vec![] - } + let compressed = ((id >> 31) & 1) == 1; + let (decompressed_size, uncompressed_crc32) = match compressed { + false => (None, None), + true => (Some(buffer::get_u32_le(&buf, &mut pos)?), Some(buffer::get_u32_le(&buf, &mut pos)?)) }; Ok(Self { @@ -269,9 +267,44 @@ impl SplitPacketInfo { total, number, size, - payload + compressed, + decompressed_size, + uncompressed_crc32, + payload: buf[pos..].to_vec() }) } + + fn decompress(&self) -> GDResult> { + if !self.compressed { + let mut decoder = Decoder::new(); + decoder.write(&self.payload).map_err(|e| GDError::Decompress(e.to_string()))?; + + let decompressed_size = self.decompressed_size.unwrap() as usize; + + let mut decompressed_payload = Vec::with_capacity(decompressed_size); + decoder.read(&mut decompressed_payload).map_err(|e| GDError::Decompress(e.to_string()))?; + + if decompressed_payload.len() != decompressed_size { + Err(GDError::Decompress("Valve Protocol: The decompressed payload size doesn't match the expected one.".to_string())) + } + else if crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { + Err(GDError::Decompress("Valve Protocol: The decompressed crc32 hash does not match the expected one.".to_string())) + } + else { + Ok(decompressed_payload) + } + } else { //already decompressed + Ok(self.payload.clone()) + } + } + + fn get_payload(&self) -> GDResult> { + if self.compressed { + Ok(self.decompress()?) + } else { + Ok(self.payload.clone()) + } + } } impl ValveProtocol { @@ -302,16 +335,15 @@ impl ValveProtocol { let mut buf = self.receive_raw(buffer_size)?; if buf[0] == 0xFE { //the packet is split - let initial_split_packet_info = SplitPacketInfo::new(app, &buf)?; - let mut final_packet = Packet::new(&initial_split_packet_info.payload)?; + let mut main_packet = SplitPacket::new(app, &buf)?; - for _ in 1..initial_split_packet_info.total { + for _ in 1..main_packet.total { buf = self.receive_raw(buffer_size)?; - let split_packet_info = SplitPacketInfo::new(app, &buf)?; - final_packet.payload.extend(split_packet_info.payload); + let chunk_packet = SplitPacket::new(app, &buf)?; + main_packet.payload.extend(chunk_packet.payload); } - Ok(final_packet) + Ok(Packet::new(&main_packet.get_payload()?)?) } else { Packet::new(&buf) From 6159a7c38517baea28644dae1264e99743dc632c Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sat, 22 Oct 2022 02:39:06 +0300 Subject: [PATCH 036/597] Version bump! --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- GAMES.md | 2 +- README.md | 4 ++-- src/protocols/valve.rs | 4 ++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfade97..cb645b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ Who knows what the future holds... +# 0.0.3 - 22/10/2022 +Valve protocol now properly supports multi-packet responses (compressed ones not tested). +CSGO, TF2 and TS now have independent Responses, if you want a generic one, query the protocol. +[Counter Strike: Source](https://store.steampowered.com/app/240/CounterStrike_Source/) implementation (if protocol is 7, queries with multi-packet responses will crash). +[Day of Defeat: Source](https://store.steampowered.com/app/300/Day_of_Defeat_Source/) implementation. +[Garry's Mod](https://store.steampowered.com/app/4000/Garrys_Mod/) implementation. +[Half-Life 2 Deathmatch](https://store.steampowered.com/app/320/HalfLife_2_Deathmatch/) implementation. +[Left 4 Dead](https://store.steampowered.com/app/500/Left_4_Dead/) implementation. +[Left 4 Dead 2](https://store.steampowered.com/app/550/Left_4_Dead_2/) implementation. + # 0.0.2 - 20/10/2022 Further implementation of the Valve protocol (PLAYERS and RULES queries). [Counter Strike: Global Offensive](https://store.steampowered.com/app/730/CounterStrike_Global_Offensive/) implementation. diff --git a/Cargo.toml b/Cargo.toml index 8e233de..76251b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.0.2" +version = "0.0.3" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" diff --git a/GAMES.md b/GAMES.md index bf3adf4..63e1d14 100644 --- a/GAMES.md +++ b/GAMES.md @@ -5,7 +5,7 @@ | TF2 | Team Fortress 2 | Valve Protocol | | | TS | The Ship | Valve Protocol | | | CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | -| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, the Rules query crashes. | +| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, queries with multi-packet responses will crash. | | DODS | Day of Defeat: Source | Valve Protocol | | | L4D | Left 4 Dead | Valve Protocol | | | L4D2 | Left 4 Dead 2 | Valve Protocol | | diff --git a/README.md b/README.md index b648567..a7aa841 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ MSRV is `1.58.1` and the code is cross-platform. To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. ## Usage -Just pick a game/service, provide the ip and the port (can be optional) then query on it. +Just pick a game/service/protocol, provide the ip and the port (can be optional) then query on it. ```rust use gamedig::games::tf2; @@ -26,4 +26,4 @@ The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamed Curious about the history and what changed between versions? you can see just that in the [CHANGELOG](CHANGELOG.md) file. ## Contributing -If you want see your favorite game/service being supported here, open an issue and I'll prioritize it! (or do a pull request if you want to implement it yourself) +If you want see your favorite game/service being supported here, open an issue, and I'll prioritize it! (or do a pull request if you want to implement it yourself) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 11c42da..0e4f7c4 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -128,7 +128,7 @@ pub enum Request { pub enum App { /// Counter-Strike: Source CSS = 240, - /// Day of Defeat: Sourcec + /// Day of Defeat: Source DODS = 300, /// Half-Life 2 Deathmatch HL2DM = 320, @@ -254,7 +254,7 @@ impl SplitPacket { let id = buffer::get_u32_le(&buf, &mut pos)?; let total = buffer::get_u8(&buf, &mut pos)?; let number = buffer::get_u8(&buf, &mut pos)?; - let size = buffer::get_u16_le(&buf, &mut pos)?; + let size = buffer::get_u16_le(&buf, &mut pos)?; //if game is CSS and if protocol is 7, queries with multi-packet responses will crash let compressed = ((id >> 31) & 1) == 1; let (decompressed_size, uncompressed_crc32) = match compressed { false => (None, None), From e8cbe7b9f5db21b4bda24a75bb5caca1868c789d Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 22 Oct 2022 14:58:59 +0300 Subject: [PATCH 037/597] DNS Resolver Implementation (#4) * [dns-resolver] Added trust-dns-resolver and restored cargo.lock * [dns_resolver] Implemented feature --- .gitignore | 4 - Cargo.lock | 767 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/errors.rs | 7 +- src/protocols/valve.rs | 14 +- src/utils.rs | 22 +- 6 files changed, 796 insertions(+), 19 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 579ce9a..bf6d0cb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,6 @@ # will have compiled files and executables /target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..34a856e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,767 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "bzip2-rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beeb59e7e4c811ab37cc73680c798c7a5da77fc9989c62b09138e31ee740f735" +dependencies = [ + "crc32fast", + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gamedig" +version = "0.0.3" +dependencies = [ + "bzip2-rs", + "crc32fast", + "trust-dns-resolver", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ipconfig" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" +dependencies = [ + "socket2", + "widestring", + "winapi", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.36.1", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.42.0", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "winapi", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", + "trust-dns-proto", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna 0.3.0", + "percent-encoding", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml index 76251b7..c17d804 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ msrv = "1.58.1" [dependencies] crc32fast = "1.3.2" bzip2-rs = "0.1.2" +trust-dns-resolver = "0.22.0" diff --git a/src/errors.rs b/src/errors.rs index a1d199a..6a7049f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -22,11 +22,13 @@ pub enum GDError { /// Unknown cast while translating a value to an enum. UnknownEnumCast, /// The server queried is not from the queried game. - BadGame(String) + BadGame(String), + /// Problems occurred while dns resolving. + DnsResolve(String), } impl fmt::Display for GDError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { GDError::PacketOverflow(details) => write!(f, "Packet overflow: {details}"), GDError::PacketUnderflow(details) => write!(f, "Packet underflow: {details}"), @@ -36,6 +38,7 @@ impl fmt::Display for GDError { GDError::Decompress(details) => write!(f, "Couldn't decompress data: {details}"), GDError::UnknownEnumCast => write!(f, "Unknown enum cast encountered."), GDError::BadGame(details) => write!(f, "Queried another game that the supposed one: {details}"), + GDError::DnsResolve(details) => write!(f, "DNS Resolve: {details}"), } } } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 0e4f7c4..143dc84 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -308,11 +308,11 @@ impl SplitPacket { } impl ValveProtocol { - fn new(address: &str, port: u16) -> Self { - Self { + fn new(address: &str, port: u16) -> GDResult { + Ok(Self { socket: UdpSocket::bind("0.0.0.0:0").unwrap(), - complete_address: complete_address(address, port) - } + complete_address: complete_address(address, port)? + }) } fn send(&self, data: &[u8]) -> GDResult<()> { @@ -324,8 +324,8 @@ impl ValveProtocol { let mut buf: Vec = vec![0; buffer_size]; let (amt, _) = self.socket.recv_from(&mut buf.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?; - if amt < 9 { - return Err(GDError::PacketUnderflow("Any Valve Protocol response can't be under 9 bytes long.".to_string())); + if amt < 6 { + return Err(GDError::PacketUnderflow("Any Valve Protocol response can't be under 6 bytes long.".to_string())); } Ok(buf[..amt].to_vec()) @@ -489,7 +489,7 @@ impl ValveProtocol { } pub(crate) fn query(app: App, address: &str, port: u16, gather: GatheringSettings) -> Result { - let client = ValveProtocol::new(address, port); + let client = ValveProtocol::new(address, port)?; let info = client.get_server_info(&app)?; diff --git a/src/utils.rs b/src/utils.rs index bb91858..3cfdf1d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,19 @@ -use std::ops::Add; +use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; +use trust_dns_resolver::Resolver; use crate::{GDResult, GDError}; -pub fn complete_address(address: &str, port: u16) -> String { - String::from(address.to_owned() + ":").add(&*port.to_string()) +fn resolve_dns(address: &str) -> GDResult { + let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()) + .map_err(|e| GDError::DnsResolve(e.to_string()))?; + + let response = resolver.lookup_ip(address) + .map_err(|e| GDError::DnsResolve(e.to_string()))?; + + Ok(response.iter().next().ok_or(GDError::DnsResolve("Couldn't resolve the DNS address.".to_string()))?.to_string()) +} + +pub fn complete_address(address: &str, port: u16) -> GDResult { + Ok(resolve_dns(address)? + ":" + &*port.to_string()) } pub mod buffer { @@ -73,9 +84,8 @@ mod tests { #[test] fn complete_address_test() { - let address = "192.168.0.1"; - let port = 27015; - assert_eq!(complete_address(address, port), "192.168.0.1:27015"); + assert_eq!(complete_address("192.168.0.1", 27015).unwrap(), "192.168.0.1:27015"); + assert!(complete_address("not_existent_address", 9999).is_err()); } #[test] From a5bdd05c2400180861d154ffd3f33cb2c48fabd0 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sat, 22 Oct 2022 17:03:14 +0300 Subject: [PATCH 038/597] Added Alien Swarm and Alien Swamr: Reactive Drop support --- GAMES.md | 22 +++--- PROTOCOLS.md | 6 +- examples/asrd.rs | 10 +++ src/games/aliens.rs | 83 +++++++++++++++++++ src/games/asrd.rs | 83 +++++++++++++++++++ src/games/mod.rs | 13 +++ src/protocols/valve.rs | 176 ++++++++++++++++++++++------------------- 7 files changed, 297 insertions(+), 96 deletions(-) create mode 100644 examples/asrd.rs create mode 100644 src/games/aliens.rs create mode 100644 src/games/asrd.rs diff --git a/GAMES.md b/GAMES.md index 63e1d14..e5ce055 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,15 +1,17 @@ # Supported games: -| ID | Name | Protocol | Notes | -|-------|----------------------------------|----------------|------------------------------------------------------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| TS | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | -| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, queries with multi-packet responses will crash. | -| DODS | Day of Defeat: Source | Valve Protocol | | -| L4D | Left 4 Dead | Valve Protocol | | -| L4D2 | Left 4 Dead 2 | Valve Protocol | | -| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | +| ID | Name | Protocol | Notes | +|--------|----------------------------------|----------------|------------------------------------------------------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| TS | The Ship | Valve Protocol | | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | +| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, queries with multi-packet responses will crash. | +| DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | +| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | +| ALIENS | Alien Swarm | Valve Protocol | Not tested. | +| ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/PROTOCOLS.md b/PROTOCOLS.md index ddb2a63..a729dab 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,8 +1,8 @@ # Supported protocols: -| Name | Documentation reference | Used by | Notes | -|----------------|---------------------------------------------------------------------------|------------------------------------------------|----------------------------------------| -| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, TS, CSS, DODS, GM, HL2DM, L4D, L4D2 | Multi-packet decompression not tested. | +| Name | Documentation reference | Used by | Notes | +|----------------|---------------------------------------------------------------------------|--------------------------------------------------------------|----------------------------------------| +| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, TS, CSS, DODS, GM, HL2DM, L4D, L4D2, ALIENS, ASRD | Multi-packet decompression not tested. | ## Planned to add support: Minecraft protocol diff --git a/examples/asrd.rs b/examples/asrd.rs new file mode 100644 index 0000000..d0256a3 --- /dev/null +++ b/examples/asrd.rs @@ -0,0 +1,10 @@ + +use gamedig::games::asrd; + +fn main() { + let response = asrd::query("5.199.135.237", Some(30000)); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/aliens.rs b/src/games/aliens.rs new file mode 100644 index 0000000..17838cc --- /dev/null +++ b/src/games/aliens.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::ALIENS, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/asrd.rs b/src/games/asrd.rs new file mode 100644 index 0000000..a3d3963 --- /dev/null +++ b/src/games/asrd.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::ASRD, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index d5d8e73..82c677c 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1,12 +1,25 @@ //! Currently supported games. +/// Team Fortress 2 pub mod tf2; +/// The Ship pub mod ts; +/// Counter-Strike: Global Offensive pub mod csgo; +/// Counter-Strike: Source pub mod css; +/// Day of Defeat: Source pub mod dods; +/// Garry's Mod pub mod gm; +/// Left 4 Dead pub mod l4d; +/// Left 4 Dead 2 pub mod l4d2; +/// Half-Life 2 Deathmatch pub mod hl2dm; +/// Alien Swarm +pub mod aliens; +/// Alien Swarm: Reactive Drop +pub mod asrd; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 143dc84..fd2b98d 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -41,7 +41,7 @@ pub struct ServerInfo { /// Full name of the game. pub game: String, /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. - pub id: u16, + pub appid: u32, /// Number of players on the server. pub players: u8, /// Maximum number of players the server reports it can hold. @@ -124,7 +124,7 @@ pub enum Request { } /// Supported app id's -#[derive(PartialEq)] +#[derive(PartialEq, Clone)] pub enum App { /// Counter-Strike: Source CSS = 240, @@ -138,31 +138,16 @@ pub enum App { L4D = 500, /// Left 4 Dead L4D2 = 550, + /// Alien Swarm + ALIENS = 630, /// Counter-Strike: Global Offensive CSGO = 730, /// The Ship TS = 2400, /// Garry's Mod GM = 4000, -} - -impl TryFrom for App { - type Error = GDError; - - fn try_from(value: u16) -> GDResult { - match value { - x if x == App::CSS as u16 => Ok(App::CSS), - x if x == App::HL2DM as u16 => Ok(App::HL2DM), - x if x == App::DODS as u16 => Ok(App::DODS), - x if x == App::TF2 as u16 => Ok(App::TF2), - x if x == App::L4D as u16 => Ok(App::L4D), - x if x == App::L4D2 as u16 => Ok(App::L4D2), - x if x == App::CSGO as u16 => Ok(App::CSGO), - x if x == App::TS as u16 => Ok(App::TS), - x if x == App::GM as u16 => Ok(App::GM), - _ => Err(GDError::UnknownEnumCast), - } - } + /// Alien Swarm: Reactive Drop + ASRD = 563560, } /// What data to gather, purely used only with the query function. @@ -373,68 +358,90 @@ impl ValveProtocol { let buf = self.get_request_data(app, Request::INFO)?; let mut pos = 0; - Ok(ServerInfo { - protocol: buffer::get_u8(&buf, &mut pos)?, - name: buffer::get_string(&buf, &mut pos)?, - map: buffer::get_string(&buf, &mut pos)?, - folder: buffer::get_string(&buf, &mut pos)?, - game: buffer::get_string(&buf, &mut pos)?, - id: buffer::get_u16_le(&buf, &mut pos)?, - players: buffer::get_u8(&buf, &mut pos)?, - max_players: buffer::get_u8(&buf, &mut pos)?, - bots: buffer::get_u8(&buf, &mut pos)?, - server_type: match buffer::get_u8(&buf, &mut pos)? { - 100 => Server::Dedicated, //'d' - 108 => Server::NonDedicated, //'l' - 112 => Server::SourceTV, //'p' - _ => Err(GDError::UnknownEnumCast)? - }, - environment_type: match buffer::get_u8(&buf, &mut pos)? { - 108 => Environment::Linux, //'l' - 119 => Environment::Windows, //'w' - 109 | 111 => Environment::Mac, //'m' or 'o' - _ => Err(GDError::UnknownEnumCast)? - }, - has_password: buffer::get_u8(&buf, &mut pos)? == 1, - vac_secured: buffer::get_u8(&buf, &mut pos)? == 1, - the_ship: match *app == App::TS { - false => None, - true => Some(TheShip { - mode: buffer::get_u8(&buf, &mut pos)?, - witnesses: buffer::get_u8(&buf, &mut pos)?, - duration: buffer::get_u8(&buf, &mut pos)? - }) - }, - version: buffer::get_string(&buf, &mut pos)?, - extra_data: match buffer::get_u8(&buf, &mut pos) { - Err(_) => None, - Ok(value) => Some(ExtraData { - port: match (value & 0x80) > 0 { - false => None, - true => Some(buffer::get_u16_le(&buf, &mut pos)?) - }, - steam_id: match (value & 0x10) > 0 { - false => None, - true => Some(buffer::get_u64_le(&buf, &mut pos)?) - }, - tv_port: match (value & 0x40) > 0 { - false => None, - true => Some(buffer::get_u16_le(&buf, &mut pos)?) - }, - tv_name: match (value & 0x40) > 0 { - false => None, - true => Some(buffer::get_string(&buf, &mut pos)?) - }, - keywords: match (value & 0x20) > 0 { - false => None, - true => Some(buffer::get_string(&buf, &mut pos)?) - }, - game_id: match (value & 0x01) > 0 { - false => None, - true => Some(buffer::get_u64_le(&buf, &mut pos)?) + let protocol = buffer::get_u8(&buf, &mut pos)?; + let name = buffer::get_string(&buf, &mut pos)?; + let map = buffer::get_string(&buf, &mut pos)?; + let folder = buffer::get_string(&buf, &mut pos)?; + let game = buffer::get_string(&buf, &mut pos)?; + let mut appid = buffer::get_u16_le(&buf, &mut pos)? as u32; + let players = buffer::get_u8(&buf, &mut pos)?; + let max_players = buffer::get_u8(&buf, &mut pos)?; + let bots = buffer::get_u8(&buf, &mut pos)?; + let server_type = match buffer::get_u8(&buf, &mut pos)? { + 100 => Server::Dedicated, //'d' + 108 => Server::NonDedicated, //'l' + 112 => Server::SourceTV, //'p' + _ => Err(GDError::UnknownEnumCast)? + }; + let environment_type = match buffer::get_u8(&buf, &mut pos)? { + 108 => Environment::Linux, //'l' + 119 => Environment::Windows, //'w' + 109 | 111 => Environment::Mac, //'m' or 'o' + _ => Err(GDError::UnknownEnumCast)? + }; + let has_password = buffer::get_u8(&buf, &mut pos)? == 1; + let vac_secured = buffer::get_u8(&buf, &mut pos)? == 1; + let the_ship = match *app == App::TS { + false => None, + true => Some(TheShip { + mode: buffer::get_u8(&buf, &mut pos)?, + witnesses: buffer::get_u8(&buf, &mut pos)?, + duration: buffer::get_u8(&buf, &mut pos)? + }) + }; + let version = buffer::get_string(&buf, &mut pos)?; + let extra_data = match buffer::get_u8(&buf, &mut pos) { + Err(_) => None, + Ok(value) => Some(ExtraData { + port: match (value & 0x80) > 0 { + false => None, + true => Some(buffer::get_u16_le(&buf, &mut pos)?) + }, + steam_id: match (value & 0x10) > 0 { + false => None, + true => Some(buffer::get_u64_le(&buf, &mut pos)?) + }, + tv_port: match (value & 0x40) > 0 { + false => None, + true => Some(buffer::get_u16_le(&buf, &mut pos)?) + }, + tv_name: match (value & 0x40) > 0 { + false => None, + true => Some(buffer::get_string(&buf, &mut pos)?) + }, + keywords: match (value & 0x20) > 0 { + false => None, + true => Some(buffer::get_string(&buf, &mut pos)?) + }, + game_id: match (value & 0x01) > 0 { + false => None, + true => { + let gid = buffer::get_u64_le(&buf, &mut pos)?; + appid = (gid & ((1 << 24) - 1)) as u32; + + Some(gid) } - }) - } + } + }) + }; + + Ok(ServerInfo { + protocol, + name, + map, + folder, + game, + appid, + players, + max_players, + bots, + server_type, + environment_type, + has_password, + vac_secured, + the_ship, + version, + extra_data }) } @@ -493,7 +500,10 @@ impl ValveProtocol { let info = client.get_server_info(&app)?; - App::try_from(info.id).map_err(|_| GDError::BadGame(format!("Found {} instead!", info.id)))?; + let query_app_id = app.clone() as u32; + if info.appid != query_app_id { + return Err(GDError::BadGame(format!("Expected {}, found {} instead!", query_app_id, info.appid))); + } Ok(Response { info, From 14abf3d1ab05a7f97331873edc85023270ff06b9 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sat, 22 Oct 2022 17:11:14 +0300 Subject: [PATCH 039/597] Removed Cargo.lock and updated CHANGELOG --- .gitignore | 4 + CHANGELOG.md | 5 + Cargo.lock | 767 --------------------------------------------------- 3 files changed, 9 insertions(+), 767 deletions(-) delete mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index bf6d0cb..579ce9a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,10 @@ # will have compiled files and executables /target/ +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + # These are backup files generated by rustfmt **/*.rs.bk diff --git a/CHANGELOG.md b/CHANGELOG.md index cb645b8..5427d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Who knows what the future holds... +# 0.0.4 - ??/??/???? +Queries now support DNS resolve. +[Alien Swarm](https://store.steampowered.com/app/630/Alien_Swarm/) implementation. +[Alien Swarm: Reactive Drop](https://store.steampowered.com/app/563560/Alien_Swarm_Reactive_Drop/) implementation. + # 0.0.3 - 22/10/2022 Valve protocol now properly supports multi-packet responses (compressed ones not tested). CSGO, TF2 and TS now have independent Responses, if you want a generic one, query the protocol. diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 34a856e..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,767 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "async-trait" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "bzip2-rs" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beeb59e7e4c811ab37cc73680c798c7a5da77fc9989c62b09138e31ee740f735" -dependencies = [ - "crc32fast", - "tinyvec", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - -[[package]] -name = "enum-as-inner" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" - -[[package]] -name = "futures-io" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" - -[[package]] -name = "futures-task" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" - -[[package]] -name = "futures-util" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "gamedig" -version = "0.0.3" -dependencies = [ - "bzip2-rs", - "crc32fast", - "trust-dns-resolver", -] - -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "ipconfig" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" -dependencies = [ - "socket2", - "widestring", - "winapi", - "winreg", -] - -[[package]] -name = "ipnet" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.135" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.36.1", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys 0.42.0", -] - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro2" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "syn" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "socket2", - "winapi", -] - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "trust-dns-proto" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.2.3", - "ipnet", - "lazy_static", - "rand", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lazy_static", - "lru-cache", - "parking_lot", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "tracing", - "trust-dns-proto", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna 0.3.0", - "percent-encoding", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "widestring" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" - -[[package]] -name = "winreg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" -dependencies = [ - "winapi", -] From 83bbd5d428c488df339804a3432772621ffb1905 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sat, 22 Oct 2022 19:25:54 +0300 Subject: [PATCH 040/597] Added Insurgency and Insurgency: Sandstorm implementation. --- CHANGELOG.md | 2 + GAMES.md | 26 +++++++------ examples/ins.rs | 10 +++++ examples/inss.rs | 10 +++++ src/games/ins.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/games/inss.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/games/mod.rs | 4 ++ src/protocols/valve.rs | 4 ++ 8 files changed, 210 insertions(+), 12 deletions(-) create mode 100644 examples/ins.rs create mode 100644 examples/inss.rs create mode 100644 src/games/ins.rs create mode 100644 src/games/inss.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5427d4c..e9188b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Who knows what the future holds... Queries now support DNS resolve. [Alien Swarm](https://store.steampowered.com/app/630/Alien_Swarm/) implementation. [Alien Swarm: Reactive Drop](https://store.steampowered.com/app/563560/Alien_Swarm_Reactive_Drop/) implementation. +[Insurgency](https://store.steampowered.com/app/222880/Insurgency/) implementation. +[Insurgency: Sandstorm](https://store.steampowered.com/app/581320/Insurgency_Sandstorm/) implementation. # 0.0.3 - 22/10/2022 Valve protocol now properly supports multi-packet responses (compressed ones not tested). diff --git a/GAMES.md b/GAMES.md index e5ce055..925aa2d 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,17 +1,19 @@ # Supported games: -| ID | Name | Protocol | Notes | -|--------|----------------------------------|----------------|------------------------------------------------------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| TS | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | -| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, queries with multi-packet responses will crash. | -| DODS | Day of Defeat: Source | Valve Protocol | | -| L4D | Left 4 Dead | Valve Protocol | | -| L4D2 | Left 4 Dead 2 | Valve Protocol | | -| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | -| ALIENS | Alien Swarm | Valve Protocol | Not tested. | -| ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | +| ID | Name | Protocol | Notes | +|--------|-------------------------------------|----------------|------------------------------------------------------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| TS | The Ship | Valve Protocol | | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | +| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, queries with multi-packet responses will crash. | +| DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | +| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | +| ALIENS | Alien Swarm | Valve Protocol | Not tested. | +| ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | +| INS | Insurgency | Valve Protocol | | +| INSS | Insurgency: Sandstorm | Valve Protocol | Here you need to use the query port, not the server port. | ## Planned to add support: All Valve titles. diff --git a/examples/ins.rs b/examples/ins.rs new file mode 100644 index 0000000..5eb9882 --- /dev/null +++ b/examples/ins.rs @@ -0,0 +1,10 @@ + +use gamedig::games::ins; + +fn main() { + let response = ins::query("101.100.139.94", Some(27016)); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/examples/inss.rs b/examples/inss.rs new file mode 100644 index 0000000..ce88c6e --- /dev/null +++ b/examples/inss.rs @@ -0,0 +1,10 @@ + +use gamedig::games::inss; + +fn main() { + let response = inss::query("109.195.19.160", None); //The query port, not the server port + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/ins.rs b/src/games/ins.rs new file mode 100644 index 0000000..627ade8 --- /dev/null +++ b/src/games/ins.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::INS, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/inss.rs b/src/games/inss.rs new file mode 100644 index 0000000..af844d3 --- /dev/null +++ b/src/games/inss.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::INSS, address, match port { + None => 27131, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 82c677c..4dd9dcc 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -23,3 +23,7 @@ pub mod hl2dm; pub mod aliens; /// Alien Swarm: Reactive Drop pub mod asrd; +/// Insurgency +pub mod ins; +/// Insurgency: Sandstorm +pub mod inss; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index fd2b98d..f36a07b 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -146,6 +146,10 @@ pub enum App { TS = 2400, /// Garry's Mod GM = 4000, + /// Insurgency + INS = 222880, + /// Insurgency: Sandstorm + INSS = 581320, /// Alien Swarm: Reactive Drop ASRD = 563560, } From 88a4c821585982925582205ed6d24656b43847bc Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sat, 22 Oct 2022 23:50:32 +0300 Subject: [PATCH 041/597] Removed examples, added a master_querant change gather_settings to none (representing all) --- .gitignore | 3 +++ examples/asrd.rs | 10 ---------- examples/csgo.rs | 10 ---------- examples/css.rs | 10 ---------- examples/dods.rs | 10 ---------- examples/gm.rs | 10 ---------- examples/hl2dm.rs | 10 ---------- examples/ins.rs | 10 ---------- examples/inss.rs | 10 ---------- examples/l4d.rs | 10 ---------- examples/l4d2.rs | 10 ---------- examples/master_querant.rs | 41 ++++++++++++++++++++++++++++++++++++++ examples/tf2.rs | 2 +- examples/ts.rs | 10 ---------- src/games/aliens.rs | 7 ++----- src/games/asrd.rs | 7 ++----- src/games/csgo.rs | 4 ++-- src/games/css.rs | 7 ++----- src/games/dods.rs | 7 ++----- src/games/gm.rs | 7 ++----- src/games/hl2dm.rs | 7 ++----- src/games/ins.rs | 7 ++----- src/games/inss.rs | 7 ++----- src/games/l4d.rs | 7 ++----- src/games/l4d2.rs | 7 ++----- src/games/tf2.rs | 7 ++----- src/games/ts.rs | 7 ++----- src/protocols/valve.rs | 15 +++++++++++--- 28 files changed, 83 insertions(+), 176 deletions(-) delete mode 100644 examples/asrd.rs delete mode 100644 examples/csgo.rs delete mode 100644 examples/css.rs delete mode 100644 examples/dods.rs delete mode 100644 examples/gm.rs delete mode 100644 examples/hl2dm.rs delete mode 100644 examples/ins.rs delete mode 100644 examples/inss.rs delete mode 100644 examples/l4d.rs delete mode 100644 examples/l4d2.rs create mode 100644 examples/master_querant.rs delete mode 100644 examples/ts.rs diff --git a/.gitignore b/.gitignore index 579ce9a..23f7216 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ Cargo.lock # Others .idea/ +.venv/ + +test_everything.py diff --git a/examples/asrd.rs b/examples/asrd.rs deleted file mode 100644 index d0256a3..0000000 --- a/examples/asrd.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::asrd; - -fn main() { - let response = asrd::query("5.199.135.237", Some(30000)); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/csgo.rs b/examples/csgo.rs deleted file mode 100644 index ca3512e..0000000 --- a/examples/csgo.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::csgo; - -fn main() { - let response = csgo::query("216.52.148.47", None); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/css.rs b/examples/css.rs deleted file mode 100644 index 53aea99..0000000 --- a/examples/css.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::css; - -fn main() { - let response = css::query("104.128.58.206", None); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/dods.rs b/examples/dods.rs deleted file mode 100644 index d5ebf40..0000000 --- a/examples/dods.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::dods; - -fn main() { - let response = dods::query("88.99.28.151", Some(27055)); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/gm.rs b/examples/gm.rs deleted file mode 100644 index 0ce21bb..0000000 --- a/examples/gm.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::gm; - -fn main() { - let response = gm::query("148.59.74.84", None); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/hl2dm.rs b/examples/hl2dm.rs deleted file mode 100644 index 49d642e..0000000 --- a/examples/hl2dm.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::hl2dm; - -fn main() { - let response = hl2dm::query("74.91.118.209", None); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/ins.rs b/examples/ins.rs deleted file mode 100644 index 5eb9882..0000000 --- a/examples/ins.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::ins; - -fn main() { - let response = ins::query("101.100.139.94", Some(27016)); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/inss.rs b/examples/inss.rs deleted file mode 100644 index ce88c6e..0000000 --- a/examples/inss.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::inss; - -fn main() { - let response = inss::query("109.195.19.160", None); //The query port, not the server port - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/l4d.rs b/examples/l4d.rs deleted file mode 100644 index 4c0895d..0000000 --- a/examples/l4d.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::l4d; - -fn main() { - let response = l4d::query("207.246.72.170", Some(26999)); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/l4d2.rs b/examples/l4d2.rs deleted file mode 100644 index 754815b..0000000 --- a/examples/l4d2.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::l4d2; - -fn main() { - let response = l4d2::query("74.91.124.246", None); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/examples/master_querant.rs b/examples/master_querant.rs new file mode 100644 index 0000000..7be4b89 --- /dev/null +++ b/examples/master_querant.rs @@ -0,0 +1,41 @@ + +use std::env; +use gamedig::{aliens, asrd, csgo, css, dods, gm, hl2dm, ins, inss, l4d, l4d2, tf2, ts}; + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() == 1 || args[1] == "help".to_string() { + println!("Usage: "); + println!(" - any game, example: tf2"); + println!(" - an ip, example: 192.168.0.0"); + println!(" - an port, optional, example: 27015"); + return; + } else if args.len() < 3 { + println!("Minimum number of arguments: 3, try 'help' to see the details."); + return; + } + + let ip = args[2].as_str(); + let port = match args.len() == 4 { + false => None, + true => Some(args[3].parse::().expect("Invalid port!")) + }; + + match args[1].as_str() { + "aliens" => println!("{:?}", aliens::query(ip, port)), + "asrd" => println!("{:?}", asrd::query(ip, port)), + "csgo" => println!("{:?}", csgo::query(ip, port)), + "css" => println!("{:?}", css::query(ip, port)), + "dods" => println!("{:?}", dods::query(ip, port)), + "gm" => println!("{:?}", gm::query(ip, port)), + "hl2dm" => println!("{:?}", hl2dm::query(ip, port)), + "tf2" => println!("{:?}", tf2::query(ip, port)), + "ins" => println!("{:?}", ins::query(ip, port)), + "inss" => println!("{:?}", inss::query(ip, port)), + "l4d" => println!("{:?}", l4d::query(ip, port)), + "l4d2" => println!("{:?}", l4d2::query(ip, port)), + "ts" => println!("{:?}", ts::query(ip, port)), + _ => panic!("Undefined game: {}", args[1]) + }; +} diff --git a/examples/tf2.rs b/examples/tf2.rs index f0fc2ec..eb8c70a 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -2,7 +2,7 @@ use gamedig::games::tf2; fn main() { - let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port + let response = tf2::query("cosminperram.com", None); //or Some(27015), None is the default protocol port (which is 27015) match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) diff --git a/examples/ts.rs b/examples/ts.rs deleted file mode 100644 index 5c97974..0000000 --- a/examples/ts.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use gamedig::games::ts; - -fn main() { - let response = ts::query("46.4.48.226", Some(27017)); - match response { - Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) - } -} diff --git a/src/games/aliens.rs b/src/games/aliens.rs index 17838cc..6daa58f 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::ALIENS, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/asrd.rs b/src/games/asrd.rs index a3d3963..6849381 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::ASRD, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 3419228..1de0abb 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -72,10 +72,10 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::CSGO, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { + }, Some(GatheringSettings { players: true, rules: false // cause csgo doesnt reply with rules anymore - })?; + }))?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/css.rs b/src/games/css.rs index 9e23ad6..11d6341 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::CSS, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dods.rs b/src/games/dods.rs index e535a3c..9a2eff5 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::DODS, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index 12c4a90..fcd5d08 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::GM, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index 2ddf874..2f45195 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::HL2DM, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ins.rs b/src/games/ins.rs index 627ade8..07f2f43 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::INS, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/inss.rs b/src/games/inss.rs index af844d3..3cd1325 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::INSS, address, match port { None => 27131, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d.rs b/src/games/l4d.rs index deb42e5..38e652c 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::L4D, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 45a3e43..20f046d 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::L4D2, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 56a5c1e..1af77c7 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -74,10 +74,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::TF2, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ts.rs b/src/games/ts.rs index d9195eb..7ede50b 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,5 +1,5 @@ use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, ServerPlayer, Server, ServerRule}; +use crate::valve::{ValveProtocol, App, ServerPlayer, Server, ServerRule}; #[derive(Debug)] pub struct Player { @@ -86,10 +86,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = ValveProtocol::query(App::TS, address, match port { None => 27015, Some(port) => port - }, GatheringSettings { - players: true, - rules: true - })?; + }, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index f36a07b..053bed6 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -499,7 +499,8 @@ impl ValveProtocol { Ok(Some(rules)) } - pub(crate) fn query(app: App, address: &str, port: u16, gather: GatheringSettings) -> Result { + /// Query any app. + pub fn query(app: App, address: &str, port: u16, gather_settings: Option) -> Result { let client = ValveProtocol::new(address, port)?; let info = client.get_server_info(&app)?; @@ -509,13 +510,21 @@ impl ValveProtocol { return Err(GDError::BadGame(format!("Expected {}, found {} instead!", query_app_id, info.appid))); } + let (gather_players, gather_rules) = match gather_settings.is_some() { + false => (true, true), + true => { + let settings = gather_settings.unwrap(); + (settings.players, settings.rules) + } + }; + Ok(Response { info, - players: match gather.players { + players: match gather_players { false => None, true => Some(client.get_server_players(&app)?) }, - rules: match gather.rules { + rules: match gather_rules { false => None, true => client.get_server_rules(&app)? } From 8e2d76ecfb08549ebcce37dd10a822756ced943d Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sun, 23 Oct 2022 12:39:23 +0300 Subject: [PATCH 042/597] Insurgency: Modern Infantry Combat implementation. --- CHANGELOG.md | 3 +- GAMES.md | 29 +++++++------- PROTOCOLS.md | 6 +-- examples/master_querant.rs | 3 +- src/games/insmic.rs | 80 ++++++++++++++++++++++++++++++++++++++ src/games/mod.rs | 2 + src/protocols/valve.rs | 2 + 7 files changed, 106 insertions(+), 19 deletions(-) create mode 100644 src/games/insmic.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e9188b5..07d57cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ Who knows what the future holds... # 0.0.4 - ??/??/???? Queries now support DNS resolve. -[Alien Swarm](https://store.steampowered.com/app/630/Alien_Swarm/) implementation. +[Alien Swarm](https://store.steampowered.com/app/630/Alien_Swarm/) implementation (not tested). [Alien Swarm: Reactive Drop](https://store.steampowered.com/app/563560/Alien_Swarm_Reactive_Drop/) implementation. [Insurgency](https://store.steampowered.com/app/222880/Insurgency/) implementation. [Insurgency: Sandstorm](https://store.steampowered.com/app/581320/Insurgency_Sandstorm/) implementation. +[Insurgency: Modern Infantry Combat](https://store.steampowered.com/app/17700/INSURGENCY_Modern_Infantry_Combat/) implementation (not tested). # 0.0.3 - 22/10/2022 Valve protocol now properly supports multi-packet responses (compressed ones not tested). diff --git a/GAMES.md b/GAMES.md index 925aa2d..a332f47 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,19 +1,20 @@ # Supported games: -| ID | Name | Protocol | Notes | -|--------|-------------------------------------|----------------|------------------------------------------------------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| TS | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | -| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, queries with multi-packet responses will crash. | -| DODS | Day of Defeat: Source | Valve Protocol | | -| L4D | Left 4 Dead | Valve Protocol | | -| L4D2 | Left 4 Dead 2 | Valve Protocol | | -| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | -| ALIENS | Alien Swarm | Valve Protocol | Not tested. | -| ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | -| INS | Insurgency | Valve Protocol | | -| INSS | Insurgency: Sandstorm | Valve Protocol | Here you need to use the query port, not the server port. | +| ID | Name | Protocol | Notes | +|--------|------------------------------------|----------------|------------------------------------------------------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| TS | The Ship | Valve Protocol | | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | +| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, queries with multi-packet responses will crash. | +| DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | +| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | +| ALIENS | Alien Swarm | Valve Protocol | Not tested. | +| ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | +| INS | Insurgency | Valve Protocol | | +| INSS | Insurgency: Sandstorm | Valve Protocol | Here you need to use the query port, not the server port. | +| INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | Not tested. | ## Planned to add support: All Valve titles. diff --git a/PROTOCOLS.md b/PROTOCOLS.md index a729dab..c83528f 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,8 +1,8 @@ # Supported protocols: -| Name | Documentation reference | Used by | Notes | -|----------------|---------------------------------------------------------------------------|--------------------------------------------------------------|----------------------------------------| -| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, TS, CSS, DODS, GM, HL2DM, L4D, L4D2, ALIENS, ASRD | Multi-packet decompression not tested. | +| Name | Documentation reference | Used by | Notes | +|----------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------|----------------------------------------| +| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, TS, CSS, DODS, GM, HL2DM, L4D, L4D2, ALIENS, ASRD, INS, INSS, INSMIC | Multi-packet decompression not tested. | ## Planned to add support: Minecraft protocol diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 7be4b89..61012f2 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, asrd, csgo, css, dods, gm, hl2dm, ins, inss, l4d, l4d2, tf2, ts}; +use gamedig::{aliens, asrd, csgo, css, dods, gm, hl2dm, ins, insmic, inss, l4d, l4d2, tf2, ts}; fn main() { let args: Vec = env::args().collect(); @@ -31,6 +31,7 @@ fn main() { "gm" => println!("{:?}", gm::query(ip, port)), "hl2dm" => println!("{:?}", hl2dm::query(ip, port)), "tf2" => println!("{:?}", tf2::query(ip, port)), + "insmic" => println!("{:?}", insmic::query(ip, port)), "ins" => println!("{:?}", ins::query(ip, port)), "inss" => println!("{:?}", inss::query(ip, port)), "l4d" => println!("{:?}", l4d::query(ip, port)), diff --git a/src/games/insmic.rs b/src/games/insmic.rs new file mode 100644 index 0000000..6e35caf --- /dev/null +++ b/src/games/insmic.rs @@ -0,0 +1,80 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::INSMIC, address, match port { + None => 27015, + Some(port) => port + }, None)?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 4dd9dcc..f9f48c9 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -27,3 +27,5 @@ pub mod asrd; pub mod ins; /// Insurgency: Sandstorm pub mod inss; +/// Insurgency: Modern Infantry Combat +pub mod insmic; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 053bed6..30cfc72 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -146,6 +146,8 @@ pub enum App { TS = 2400, /// Garry's Mod GM = 4000, + /// Insurgency: Modern Infantry Combat + INSMIC = 17700, /// Insurgency INS = 222880, /// Insurgency: Sandstorm From 854d395aad5504f62aa15f68d36981ee6ccbf296 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sun, 23 Oct 2022 13:01:53 +0300 Subject: [PATCH 043/597] Updated CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d57cf..c2b0c83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Who knows what the future holds... # 0.0.4 - ??/??/???? Queries now support DNS resolve. +Changed Valve Protocol 3rd argument to Option, being None means everything should be gathered. +Better appid unknown cast error. [Alien Swarm](https://store.steampowered.com/app/630/Alien_Swarm/) implementation (not tested). [Alien Swarm: Reactive Drop](https://store.steampowered.com/app/563560/Alien_Swarm_Reactive_Drop/) implementation. [Insurgency](https://store.steampowered.com/app/222880/Insurgency/) implementation. From c0d07cf6f98cff4cd32efc249a856a73b1bb2ae4 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sun, 23 Oct 2022 13:34:40 +0300 Subject: [PATCH 044/597] Valve Protocol now support anonymously querying --- CHANGELOG.md | 8 ++++-- examples/master_querant.rs | 2 ++ src/games/aliens.rs | 4 +-- src/games/asrd.rs | 4 +-- src/games/csgo.rs | 4 +-- src/games/css.rs | 4 +-- src/games/dods.rs | 4 +-- src/games/gm.rs | 4 +-- src/games/hl2dm.rs | 4 +-- src/games/ins.rs | 4 +-- src/games/insmic.rs | 4 +-- src/games/inss.rs | 4 +-- src/games/l4d.rs | 4 +-- src/games/l4d2.rs | 4 +-- src/games/tf2.rs | 4 +-- src/games/ts.rs | 4 +-- src/protocols/valve.rs | 56 ++++++++++++++++++++++---------------- 17 files changed, 68 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b0c83..097976a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,12 @@ Who knows what the future holds... # 0.0.4 - ??/??/???? Queries now support DNS resolve. -Changed Valve Protocol 3rd argument to Option, being None means everything should be gathered. -Better appid unknown cast error. +Changed Valve Protocol parameters to (ip, port, app, gather_settings), changes include: +- the app is now optional, being None means to anonymously query the server. +- gather_settings is now also an optional, being None means all query settings. + +Valve Protocol now supports querying anonymous apps (see previous lines). +Better bad game error. [Alien Swarm](https://store.steampowered.com/app/630/Alien_Swarm/) implementation (not tested). [Alien Swarm: Reactive Drop](https://store.steampowered.com/app/563560/Alien_Swarm_Reactive_Drop/) implementation. [Insurgency](https://store.steampowered.com/app/222880/Insurgency/) implementation. diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 61012f2..7cc9c8f 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,7 @@ use std::env; use gamedig::{aliens, asrd, csgo, css, dods, gm, hl2dm, ins, insmic, inss, l4d, l4d2, tf2, ts}; +use gamedig::valve::ValveProtocol; fn main() { let args: Vec = env::args().collect(); @@ -37,6 +38,7 @@ fn main() { "l4d" => println!("{:?}", l4d::query(ip, port)), "l4d2" => println!("{:?}", l4d2::query(ip, port)), "ts" => println!("{:?}", ts::query(ip, port)), + "_" => println!("{:?}", ValveProtocol::query(ip, 27015, None, None)), _ => panic!("Undefined game: {}", args[1]) }; } diff --git a/src/games/aliens.rs b/src/games/aliens.rs index 6daa58f..15a2127 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::ALIENS, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::ALIENS), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 6849381..cceb0cd 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::ASRD, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::ASRD), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 1de0abb..d0cdca9 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -69,10 +69,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::CSGO, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, Some(GatheringSettings { + }, Some(App::CSGO), Some(GatheringSettings { players: true, rules: false // cause csgo doesnt reply with rules anymore }))?; diff --git a/src/games/css.rs b/src/games/css.rs index 11d6341..bcc2ca0 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::CSS, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::CSS), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dods.rs b/src/games/dods.rs index 9a2eff5..11ea284 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::DODS, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::DODS), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index fcd5d08..f28e6c4 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::GM, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::GM), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index 2f45195..3b4dd67 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::HL2DM, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::HL2DM), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ins.rs b/src/games/ins.rs index 07f2f43..146f8d4 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::INS, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::INS), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 6e35caf..4166f74 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::INSMIC, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::INSMIC), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/inss.rs b/src/games/inss.rs index 3cd1325..bf4d168 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::INSS, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27131, Some(port) => port - }, None)?; + }, Some(App::INSS), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 38e652c..0c1ed00 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::L4D, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::L4D), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 20f046d..d549568 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::L4D2, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::L4D2), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 1af77c7..1bab6d0 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -71,10 +71,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::TF2, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::TF2), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ts.rs b/src/games/ts.rs index 7ede50b..6a8b99d 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -83,10 +83,10 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(App::TS, address, match port { + let valve_response = ValveProtocol::query(address, match port { None => 27015, Some(port) => port - }, None)?; + }, Some(App::TS), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 30cfc72..b431ecf 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -238,7 +238,7 @@ struct SplitPacket { } impl SplitPacket { - fn new(_app: &App, buf: &[u8]) -> GDResult { + fn new(_appid: u32, buf: &[u8]) -> GDResult { let mut pos = 0; let header = buffer::get_u32_le(&buf, &mut pos)?; @@ -322,15 +322,15 @@ impl ValveProtocol { Ok(buf[..amt].to_vec()) } - fn receive(&self, app: &App, buffer_size: usize) -> GDResult { + fn receive(&self, appid: u32, buffer_size: usize) -> GDResult { let mut buf = self.receive_raw(buffer_size)?; if buf[0] == 0xFE { //the packet is split - let mut main_packet = SplitPacket::new(app, &buf)?; + let mut main_packet = SplitPacket::new(appid, &buf)?; for _ in 1..main_packet.total { buf = self.receive_raw(buffer_size)?; - let chunk_packet = SplitPacket::new(app, &buf)?; + let chunk_packet = SplitPacket::new(appid, &buf)?; main_packet.payload.extend(chunk_packet.payload); } @@ -342,11 +342,11 @@ impl ValveProtocol { } /// Ask for a specific request only. - pub fn get_request_data(&self, app: &App, kind: Request) -> GDResult> { + pub fn get_request_data(&self, appid: u32, kind: Request) -> GDResult> { let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); self.send(&request_initial_packet)?; - let packet = self.receive(app, DEFAULT_PACKET_SIZE)?; + let packet = self.receive(appid, DEFAULT_PACKET_SIZE)?; if packet.kind != 0x41 { //'A' return Ok(packet.payload.clone()); @@ -356,12 +356,12 @@ impl ValveProtocol { let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); self.send(&challenge_packet)?; - Ok(self.receive(app, DEFAULT_PACKET_SIZE)?.payload) + Ok(self.receive(appid, DEFAULT_PACKET_SIZE)?.payload) } /// Get the server information's. - pub fn get_server_info(&self, app: &App) -> GDResult { - let buf = self.get_request_data(app, Request::INFO)?; + pub fn get_server_info(&self, initial_appid: u32) -> GDResult { + let buf = self.get_request_data(initial_appid, Request::INFO)?; let mut pos = 0; let protocol = buffer::get_u8(&buf, &mut pos)?; @@ -387,7 +387,7 @@ impl ValveProtocol { }; let has_password = buffer::get_u8(&buf, &mut pos)? == 1; let vac_secured = buffer::get_u8(&buf, &mut pos)? == 1; - let the_ship = match *app == App::TS { + let the_ship = match appid == App::TS as u32 { false => None, true => Some(TheShip { mode: buffer::get_u8(&buf, &mut pos)?, @@ -452,8 +452,8 @@ impl ValveProtocol { } /// Get the server player's. - pub fn get_server_players(&self, app: &App) -> GDResult> { - let buf = self.get_request_data(app, Request::PLAYERS)?; + pub fn get_server_players(&self, appid: u32) -> GDResult> { + let buf = self.get_request_data(appid, Request::PLAYERS)?; let mut pos = 0; let count = buffer::get_u8(&buf, &mut pos)?; @@ -465,11 +465,11 @@ impl ValveProtocol { name: buffer::get_string(&buf, &mut pos)?, score: buffer::get_u32_le(&buf, &mut pos)?, duration: buffer::get_f32_le(&buf, &mut pos)?, - deaths: match *app == App::TS { + deaths: match appid == App::TS as u32 { false => None, true => Some(buffer::get_u32_le(&buf, &mut pos)?) }, - money: match *app == App::TS { + money: match appid == App::TS as u32 { false => None, true => Some(buffer::get_u32_le(&buf, &mut pos)?) } @@ -480,12 +480,12 @@ impl ValveProtocol { } /// Get the server rules's. - pub fn get_server_rules(&self, app: &App) -> GDResult>> { - if *app == App::CSGO { //cause csgo wont respond to this since feb 21 2014 update + pub fn get_server_rules(&self, appid: u32) -> GDResult>> { + if appid == App::CSGO as u32 { //cause csgo wont respond to this since feb 21 2014 update return Ok(None); } - let buf = self.get_request_data(app, Request::RULES)?; + let buf = self.get_request_data(appid, Request::RULES)?; let mut pos = 0; let count = buffer::get_u16_le(&buf, &mut pos)?; @@ -502,14 +502,22 @@ impl ValveProtocol { } /// Query any app. - pub fn query(app: App, address: &str, port: u16, gather_settings: Option) -> Result { + pub fn query(address: &str, port: u16, app: Option, gather_settings: Option) -> Result { let client = ValveProtocol::new(address, port)?; - let info = client.get_server_info(&app)?; + let mut query_app_id = match app { + None => 0, + Some(app) => app as u32 + }; - let query_app_id = app.clone() as u32; - if info.appid != query_app_id { - return Err(GDError::BadGame(format!("Expected {}, found {} instead!", query_app_id, info.appid))); + let info = client.get_server_info(query_app_id)?; + + if query_app_id != 0 { + if info.appid != query_app_id { + return Err(GDError::BadGame(format!("Expected {}, found {} instead!", query_app_id, info.appid))); + } + } else { + query_app_id = info.appid; } let (gather_players, gather_rules) = match gather_settings.is_some() { @@ -524,11 +532,11 @@ impl ValveProtocol { info, players: match gather_players { false => None, - true => Some(client.get_server_players(&app)?) + true => Some(client.get_server_players(query_app_id)?) }, rules: match gather_rules { false => None, - true => client.get_server_rules(&app)? + true => client.get_server_rules(query_app_id)? } }) } From 3ac6a8b603dbd6ec603965e103cdd034691e9d36 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sun, 23 Oct 2022 14:03:59 +0300 Subject: [PATCH 045/597] Changed uses to have a better structure --- CHANGELOG.md | 3 +- examples/master_querant.rs | 4 +- src/games/aliens.rs | 7 +-- src/games/asrd.rs | 7 +-- src/games/csgo.rs | 7 +-- src/games/css.rs | 7 +-- src/games/dods.rs | 7 +-- src/games/gm.rs | 7 +-- src/games/hl2dm.rs | 7 +-- src/games/ins.rs | 7 +-- src/games/insmic.rs | 7 +-- src/games/inss.rs | 7 +-- src/games/l4d.rs | 7 +-- src/games/l4d2.rs | 7 +-- src/games/tf2.rs | 7 +-- src/games/ts.rs | 7 +-- src/lib.rs | 1 - src/protocols/valve.rs | 104 +++++++++++++++++++------------------ 18 files changed, 113 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 097976a..d744674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ Who knows what the future holds... # 0.0.4 - ??/??/???? Queries now support DNS resolve. -Changed Valve Protocol parameters to (ip, port, app, gather_settings), changes include: +Changed uses a bit, from `use gamedig::valve::ValveProtocol::query` to `use gamedig::protocols::valve::query`. +Changed Valve Protocol Query parameters to (ip, port, app, gather_settings), changes include: - the app is now optional, being None means to anonymously query the server. - gather_settings is now also an optional, being None means all query settings. diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 7cc9c8f..72a961d 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,7 +1,7 @@ use std::env; use gamedig::{aliens, asrd, csgo, css, dods, gm, hl2dm, ins, insmic, inss, l4d, l4d2, tf2, ts}; -use gamedig::valve::ValveProtocol; +use gamedig::protocols::valve; fn main() { let args: Vec = env::args().collect(); @@ -38,7 +38,7 @@ fn main() { "l4d" => println!("{:?}", l4d::query(ip, port)), "l4d2" => println!("{:?}", l4d2::query(ip, port)), "ts" => println!("{:?}", ts::query(ip, port)), - "_" => println!("{:?}", ValveProtocol::query(ip, 27015, None, None)), + "_" => println!("{:?}", valve::query(ip, 27015, None, None)), _ => panic!("Undefined game: {}", args[1]) }; } diff --git a/src/games/aliens.rs b/src/games/aliens.rs index 15a2127..fa96d5d 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::ALIENS), None)?; diff --git a/src/games/asrd.rs b/src/games/asrd.rs index cceb0cd..1eefbe1 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::ASRD), None)?; diff --git a/src/games/csgo.rs b/src/games/csgo.rs index d0cdca9..38f62cf 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, GatheringSettings, ServerPlayer, Server}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerPlayer, GatheringSettings}; #[derive(Debug)] pub struct Player { @@ -69,7 +70,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::CSGO), Some(GatheringSettings { diff --git a/src/games/css.rs b/src/games/css.rs index bcc2ca0..db47618 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::CSS), None)?; diff --git a/src/games/dods.rs b/src/games/dods.rs index 11ea284..42fe9a5 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::DODS), None)?; diff --git a/src/games/gm.rs b/src/games/gm.rs index f28e6c4..cd08373 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::GM), None)?; diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index 3b4dd67..75c40c0 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::HL2DM), None)?; diff --git a/src/games/ins.rs b/src/games/ins.rs index 146f8d4..bca0d06 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::INS), None)?; diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 4166f74..1146e36 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::INSMIC), None)?; diff --git a/src/games/inss.rs b/src/games/inss.rs index bf4d168..f202002 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27131, Some(port) => port }, Some(App::INSS), None)?; diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 0c1ed00..4b945fa 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::L4D), None)?; diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index d549568..ca3d8d1 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::L4D2), None)?; diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 1bab6d0..7834d99 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, Server, ServerRule, ServerPlayer}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -71,7 +72,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::TF2), None)?; diff --git a/src/games/ts.rs b/src/games/ts.rs index 6a8b99d..b5edb3b 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,5 +1,6 @@ -use crate::{GDResult, valve}; -use crate::valve::{ValveProtocol, App, ServerPlayer, Server, ServerRule}; +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -83,7 +84,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = ValveProtocol::query(address, match port { + let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::TS), None)?; diff --git a/src/lib.rs b/src/lib.rs index 56c1905..4e2fc58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,5 +21,4 @@ pub mod games; mod utils; pub use errors::*; -pub use protocols::*; pub use games::*; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index b431ecf..663da90 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -162,13 +162,6 @@ pub struct GatheringSettings { pub rules: bool } -pub struct ValveProtocol { - socket: UdpSocket, - complete_address: String -} - -static DEFAULT_PACKET_SIZE: usize = 2048; - #[derive(Debug, Clone)] struct Packet { pub header: u32, @@ -298,6 +291,13 @@ impl SplitPacket { } } +struct ValveProtocol { + socket: UdpSocket, + complete_address: String +} + +static DEFAULT_PACKET_SIZE: usize = 2048; + impl ValveProtocol { fn new(address: &str, port: u16) -> GDResult { Ok(Self { @@ -342,7 +342,7 @@ impl ValveProtocol { } /// Ask for a specific request only. - pub fn get_request_data(&self, appid: u32, kind: Request) -> GDResult> { + fn get_request_data(&self, appid: u32, kind: Request) -> GDResult> { let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); self.send(&request_initial_packet)?; @@ -360,7 +360,7 @@ impl ValveProtocol { } /// Get the server information's. - pub fn get_server_info(&self, initial_appid: u32) -> GDResult { + fn get_server_info(&self, initial_appid: u32) -> GDResult { let buf = self.get_request_data(initial_appid, Request::INFO)?; let mut pos = 0; @@ -452,7 +452,7 @@ impl ValveProtocol { } /// Get the server player's. - pub fn get_server_players(&self, appid: u32) -> GDResult> { + fn get_server_players(&self, appid: u32) -> GDResult> { let buf = self.get_request_data(appid, Request::PLAYERS)?; let mut pos = 0; @@ -480,7 +480,7 @@ impl ValveProtocol { } /// Get the server rules's. - pub fn get_server_rules(&self, appid: u32) -> GDResult>> { + fn get_server_rules(&self, appid: u32) -> GDResult>> { if appid == App::CSGO as u32 { //cause csgo wont respond to this since feb 21 2014 update return Ok(None); } @@ -500,44 +500,46 @@ impl ValveProtocol { Ok(Some(rules)) } - - /// Query any app. - pub fn query(address: &str, port: u16, app: Option, gather_settings: Option) -> Result { - let client = ValveProtocol::new(address, port)?; - - let mut query_app_id = match app { - None => 0, - Some(app) => app as u32 - }; - - let info = client.get_server_info(query_app_id)?; - - if query_app_id != 0 { - if info.appid != query_app_id { - return Err(GDError::BadGame(format!("Expected {}, found {} instead!", query_app_id, info.appid))); - } - } else { - query_app_id = info.appid; - } - - let (gather_players, gather_rules) = match gather_settings.is_some() { - false => (true, true), - true => { - let settings = gather_settings.unwrap(); - (settings.players, settings.rules) - } - }; - - Ok(Response { - info, - players: match gather_players { - false => None, - true => Some(client.get_server_players(query_app_id)?) - }, - rules: match gather_rules { - false => None, - true => client.get_server_rules(query_app_id)? - } - }) - } +} + +/// Query a server, you need to provide the address, the port and optionally, the app and the +/// gather settings, the app being *None* means to anonymously query the server, and the gather +/// settings being *None* means to get the players and the rules. +pub fn query(address: &str, port: u16, app: Option, gather_settings: Option) -> Result { + let client = ValveProtocol::new(address, port)?; + + let mut query_app_id = match app { + None => 0, + Some(app) => app as u32 + }; + + let info = client.get_server_info(query_app_id)?; + + if query_app_id != 0 { + if info.appid != query_app_id { + return Err(GDError::BadGame(format!("Expected {}, found {} instead!", query_app_id, info.appid))); + } + } else { + query_app_id = info.appid; + } + + let (gather_players, gather_rules) = match gather_settings.is_some() { + false => (true, true), + true => { + let settings = gather_settings.unwrap(); + (settings.players, settings.rules) + } + }; + + Ok(Response { + info, + players: match gather_players { + false => None, + true => Some(client.get_server_players(query_app_id)?) + }, + rules: match gather_rules { + false => None, + true => client.get_server_rules(query_app_id)? + } + }) } From faaedf44f03cc08aed07deda0422f6d73d7068d0 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sun, 23 Oct 2022 14:06:08 +0300 Subject: [PATCH 046/597] Version bump! --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d744674..9794873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Who knows what the future holds... -# 0.0.4 - ??/??/???? +# 0.0.4 - 23/10/2022 Queries now support DNS resolve. Changed uses a bit, from `use gamedig::valve::ValveProtocol::query` to `use gamedig::protocols::valve::query`. Changed Valve Protocol Query parameters to (ip, port, app, gather_settings), changes include: diff --git a/Cargo.toml b/Cargo.toml index c17d804..ba0407d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.0.3" +version = "0.0.4" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" From 9df4bddc09280fd97a904c4754492530619032ab Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sun, 23 Oct 2022 17:34:22 +0300 Subject: [PATCH 047/597] Modularized reusable structs and changed files structure a bit --- CHANGELOG.md | 2 +- examples/tf2.rs | 2 +- src/games/aliens.rs | 75 +----- src/games/asrd.rs | 75 +----- src/games/csgo.rs | 18 +- src/games/css.rs | 75 +----- src/games/dods.rs | 75 +----- src/games/gm.rs | 75 +----- src/games/hl2dm.rs | 75 +----- src/games/ins.rs | 75 +----- src/games/insmic.rs | 75 +----- src/games/inss.rs | 75 +----- src/games/l4d.rs | 75 +----- src/games/l4d2.rs | 75 +----- src/games/tf2.rs | 75 +----- src/games/ts.rs | 22 +- src/protocols/valve/mod.rs | 5 + src/protocols/{valve.rs => valve/protocol.rs} | 160 +----------- src/protocols/valve/types.rs | 233 ++++++++++++++++++ 19 files changed, 279 insertions(+), 1063 deletions(-) create mode 100644 src/protocols/valve/mod.rs rename src/protocols/{valve.rs => valve/protocol.rs} (73%) create mode 100644 src/protocols/valve/types.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9794873..529a821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Who knows what the future holds... # 0.0.4 - 23/10/2022 Queries now support DNS resolve. -Changed uses a bit, from `use gamedig::valve::ValveProtocol::query` to `use gamedig::protocols::valve::query`. +Changed uses a bit, example: from `use gamedig::valve::ValveProtocol::query` to `use gamedig::protocols::valve::query`. Changed Valve Protocol Query parameters to (ip, port, app, gather_settings), changes include: - the app is now optional, being None means to anonymously query the server. - gather_settings is now also an optional, being None means all query settings. diff --git a/examples/tf2.rs b/examples/tf2.rs index eb8c70a..728f7ef 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -2,7 +2,7 @@ use gamedig::games::tf2; fn main() { - let response = tf2::query("cosminperram.com", None); //or Some(27015), None is the default protocol port (which is 27015) + let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port (which is 27015) match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) diff --git a/src/games/aliens.rs b/src/games/aliens.rs index fa96d5d..05bad88 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::ALIENS), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 1eefbe1..52c87d9 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::ASRD), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 38f62cf..9af139b 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,23 +1,7 @@ use crate::GDResult; use crate::protocols::valve; use crate::protocols::valve::{App, Server, ServerPlayer, GatheringSettings}; - -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} +use crate::protocols::valve::game::Player; #[derive(Debug)] pub struct Response { diff --git a/src/games/css.rs b/src/games/css.rs index db47618..004b870 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::CSS), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dods.rs b/src/games/dods.rs index 42fe9a5..4b3bfc1 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::DODS), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index cd08373..ff61d43 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::GM), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index 75c40c0..f84ab0c 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::HL2DM), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ins.rs b/src/games/ins.rs index bca0d06..bd51377 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::INS), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 1146e36..123e9c9 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::INSMIC), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/inss.rs b/src/games/inss.rs index f202002..e696c5d 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27131, Some(port) => port }, Some(App::INSS), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 4b945fa..f024be0 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::L4D), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index ca3d8d1..108bd84 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::L4D2), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 7834d99..a695ec2 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,81 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; +use crate::protocols::valve::{App, game}; -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration - } - } -} - -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: Vec -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap() - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port }, Some(App::TF2), None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ts.rs b/src/games/ts.rs index b5edb3b..ecc9baa 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,27 +1,7 @@ use crate::GDResult; use crate::protocols::valve; use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; - -#[derive(Debug)] -pub struct Player { - pub name: String, - pub score: u32, - pub duration: f32, - pub deaths: u32, - pub money: u32 -} - -impl Player { - fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration, - deaths: player.deaths.unwrap(), - money: player.money.unwrap() - } - } -} +use crate::protocols::valve::game::Player; #[derive(Debug)] pub struct Response { diff --git a/src/protocols/valve/mod.rs b/src/protocols/valve/mod.rs new file mode 100644 index 0000000..ebc8f9f --- /dev/null +++ b/src/protocols/valve/mod.rs @@ -0,0 +1,5 @@ +pub mod protocol; +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/src/protocols/valve.rs b/src/protocols/valve/protocol.rs similarity index 73% rename from src/protocols/valve.rs rename to src/protocols/valve/protocol.rs index 663da90..e5ca9c1 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve/protocol.rs @@ -1,167 +1,9 @@ use std::net::UdpSocket; use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; +use crate::protocols::valve::types::{App, Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip}; use crate::utils::{buffer, complete_address}; -/// The type of the server. -#[derive(Debug)] -pub enum Server { - Dedicated, - NonDedicated, - SourceTV -} - -/// The Operating System that the server is on. -#[derive(Debug)] -pub enum Environment { - Linux, - Windows, - Mac -} - -/// A query response. -#[derive(Debug)] -pub struct Response { - pub info: ServerInfo, - pub players: Option>, - pub rules: Option> -} - -/// General server information's. -#[derive(Debug)] -pub struct ServerInfo { - /// Protocol used by the server. - pub protocol: u8, - /// Name of the server. - pub name: String, - /// Map name. - pub map: String, - /// Name of the folder containing the game files. - pub folder: String, - /// Full name of the game. - pub game: String, - /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. - pub appid: u32, - /// Number of players on the server. - pub players: u8, - /// Maximum number of players the server reports it can hold. - pub max_players: u8, - /// Number of bots on the server. - pub bots: u8, - /// Dedicated, NonDedicated or SourceTV - pub server_type: Server, - /// The Operating System that the server is on. - pub environment_type: Environment, - /// Indicated whether the server requires a password. - pub has_password: bool, - /// Indicated whether the server uses VAC. - pub vac_secured: bool, - /// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data - pub the_ship: Option, - /// Version of the game installed on the server. - pub version: String, - /// Some extra data that the server might provide or not. - pub extra_data: Option -} - -/// A server player. -#[derive(Debug)] -pub struct ServerPlayer { - /// Player's name. - pub name: String, - /// General score. - pub score: u32, - /// How long they've been on the server for. - pub duration: f32, - /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): deaths count - pub deaths: Option, //the_ship - /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): money amount - pub money: Option, //the_ship -} - -/// A server rule. -#[derive(Debug)] -pub struct ServerRule { - pub name: String, - pub value: String -} - -/// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). -#[derive(Debug)] -pub struct TheShip { - pub mode: u8, - pub witnesses: u8, - pub duration: u8 -} - -/// Some extra data that the server might provide or not. -#[derive(Debug)] -pub struct ExtraData { - /// The server's game port number. - pub port: Option, - /// Server's SteamID. - pub steam_id: Option, - /// Spectator port number for SourceTV. - pub tv_port: Option, - /// Name of the spectator server for SourceTV. - pub tv_name: Option, - /// Tags that describe the game according to the server. - pub keywords: Option, - /// The server's 64-bit GameID. - pub game_id: Option -} - -/// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). -#[derive(PartialEq, Clone)] -#[repr(u8)] -pub enum Request { - /// Known as `A2S_INFO` - INFO = 0x54, - /// Known as `A2S_PLAYERS` - PLAYERS = 0x55, - /// Known as `A2S_RULES` - RULES = 0x56 -} - -/// Supported app id's -#[derive(PartialEq, Clone)] -pub enum App { - /// Counter-Strike: Source - CSS = 240, - /// Day of Defeat: Source - DODS = 300, - /// Half-Life 2 Deathmatch - HL2DM = 320, - /// Team Fortress 2 - TF2 = 440, - /// Left 4 Dead - L4D = 500, - /// Left 4 Dead - L4D2 = 550, - /// Alien Swarm - ALIENS = 630, - /// Counter-Strike: Global Offensive - CSGO = 730, - /// The Ship - TS = 2400, - /// Garry's Mod - GM = 4000, - /// Insurgency: Modern Infantry Combat - INSMIC = 17700, - /// Insurgency - INS = 222880, - /// Insurgency: Sandstorm - INSS = 581320, - /// Alien Swarm: Reactive Drop - ASRD = 563560, -} - -/// What data to gather, purely used only with the query function. -pub struct GatheringSettings { - pub players: bool, - pub rules: bool -} - #[derive(Debug, Clone)] struct Packet { pub header: u32, diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs new file mode 100644 index 0000000..9636197 --- /dev/null +++ b/src/protocols/valve/types.rs @@ -0,0 +1,233 @@ + +/// The type of the server. +#[derive(Debug)] +pub enum Server { + Dedicated, + NonDedicated, + SourceTV +} + +/// The Operating System that the server is on. +#[derive(Debug)] +pub enum Environment { + Linux, + Windows, + Mac +} + +/// A query response. +#[derive(Debug)] +pub struct Response { + pub info: ServerInfo, + pub players: Option>, + pub rules: Option> +} + +/// General server information's. +#[derive(Debug)] +pub struct ServerInfo { + /// Protocol used by the server. + pub protocol: u8, + /// Name of the server. + pub name: String, + /// Map name. + pub map: String, + /// Name of the folder containing the game files. + pub folder: String, + /// Full name of the game. + pub game: String, + /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. + pub appid: u32, + /// Number of players on the server. + pub players: u8, + /// Maximum number of players the server reports it can hold. + pub max_players: u8, + /// Number of bots on the server. + pub bots: u8, + /// Dedicated, NonDedicated or SourceTV + pub server_type: Server, + /// The Operating System that the server is on. + pub environment_type: Environment, + /// Indicated whether the server requires a password. + pub has_password: bool, + /// Indicated whether the server uses VAC. + pub vac_secured: bool, + /// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data + pub the_ship: Option, + /// Version of the game installed on the server. + pub version: String, + /// Some extra data that the server might provide or not. + pub extra_data: Option +} + +/// A server player. +#[derive(Debug)] +pub struct ServerPlayer { + /// Player's name. + pub name: String, + /// General score. + pub score: u32, + /// How long they've been on the server for. + pub duration: f32, + /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): deaths count + pub deaths: Option, //the_ship + /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): money amount + pub money: Option, //the_ship +} + +/// A server rule. +#[derive(Debug)] +pub struct ServerRule { + pub name: String, + pub value: String +} + +/// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). +#[derive(Debug)] +pub struct TheShip { + pub mode: u8, + pub witnesses: u8, + pub duration: u8 +} + +/// Some extra data that the server might provide or not. +#[derive(Debug)] +pub struct ExtraData { + /// The server's game port number. + pub port: Option, + /// Server's SteamID. + pub steam_id: Option, + /// Spectator port number for SourceTV. + pub tv_port: Option, + /// Name of the spectator server for SourceTV. + pub tv_name: Option, + /// Tags that describe the game according to the server. + pub keywords: Option, + /// The server's 64-bit GameID. + pub game_id: Option +} + +/// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). +#[derive(PartialEq, Clone)] +#[repr(u8)] +pub enum Request { + /// Known as `A2S_INFO` + INFO = 0x54, + /// Known as `A2S_PLAYERS` + PLAYERS = 0x55, + /// Known as `A2S_RULES` + RULES = 0x56 +} + +/// Supported app id's +#[derive(PartialEq, Clone)] +pub enum App { + /// Counter-Strike: Source + CSS = 240, + /// Day of Defeat: Source + DODS = 300, + /// Half-Life 2 Deathmatch + HL2DM = 320, + /// Team Fortress 2 + TF2 = 440, + /// Left 4 Dead + L4D = 500, + /// Left 4 Dead + L4D2 = 550, + /// Alien Swarm + ALIENS = 630, + /// Counter-Strike: Global Offensive + CSGO = 730, + /// The Ship + TS = 2400, + /// Garry's Mod + GM = 4000, + /// Insurgency: Modern Infantry Combat + INSMIC = 17700, + /// Insurgency + INS = 222880, + /// Insurgency: Sandstorm + INSS = 581320, + /// Alien Swarm: Reactive Drop + ASRD = 563560, +} + +/// What data to gather, purely used only with the query function. +pub struct GatheringSettings { + pub players: bool, + pub rules: bool +} + +pub mod game { + use super::{Server, ServerRule, ServerPlayer}; + + #[derive(Debug)] + pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 + } + + impl Player { + pub fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } + } + + #[derive(Debug)] + pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec + } + + impl Response { + pub fn new_from_valve_response(response: super::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } + } +} + From 96c2c8a335769f2961bb92430e3081fa8f2c1ba8 Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sun, 23 Oct 2022 18:09:13 +0300 Subject: [PATCH 048/597] Fixed uncomplete the ship player struct and added some docs... --- src/games/csgo.rs | 7 ++----- src/games/ts.rs | 33 +++++++++++++++++++++++++-------- src/protocols/valve/types.rs | 17 +++++++++++++---- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 9af139b..4b18678 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,6 +1,6 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerPlayer, GatheringSettings}; +use crate::protocols::valve::{App, Server, GatheringSettings, get_optional_extracted_data}; use crate::protocols::valve::game::Player; #[derive(Debug)] @@ -26,10 +26,7 @@ pub struct Response { impl Response { pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; + let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); Self { protocol: response.info.protocol, diff --git a/src/games/ts.rs b/src/games/ts.rs index ecc9baa..ad7ee8c 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,7 +1,27 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer}; -use crate::protocols::valve::game::Player; +use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer, get_optional_extracted_data}; + +#[derive(Debug)] +pub struct TheShipPlayer { + pub name: String, + pub score: u32, + pub duration: f32, + pub deaths: u32, + pub money: u32 +} + +impl TheShipPlayer { + pub fn new_from_valve_player(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration, + deaths: player.deaths.unwrap(), + money: player.money.unwrap() + } + } +} #[derive(Debug)] pub struct Response { @@ -10,7 +30,7 @@ pub struct Response { pub map: String, pub game: String, pub players: u8, - pub players_details: Vec, + pub players_details: Vec, pub max_players: u8, pub bots: u8, pub server_type: Server, @@ -30,10 +50,7 @@ pub struct Response { impl Response { pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; + let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); let the_unwrapped_ship = response.info.the_ship.unwrap(); @@ -43,7 +60,7 @@ impl Response { map: response.info.map, game: response.info.game, players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + players_details: response.players.unwrap().iter().map(|p| TheShipPlayer::new_from_valve_player(p)).collect(), max_players: response.info.max_players, bots: response.info.bots, server_type: response.info.server_type, diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 9636197..b06c793 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,4 +1,6 @@ +//! All types used by the Valve protocol + /// The type of the server. #[derive(Debug)] pub enum Server { @@ -107,6 +109,13 @@ pub struct ExtraData { pub game_id: Option } +pub fn get_optional_extracted_data(data: Option) -> (Option, Option, Option, Option, Option) { + match data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + } +} + /// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). #[derive(PartialEq, Clone)] #[repr(u8)] @@ -158,7 +167,10 @@ pub struct GatheringSettings { pub rules: bool } +/// Generic response types that are used by many games, they are the protocol ones, but without the +/// unnecessary bits (example: the **The Ship**-only fields) pub mod game { + use crate::protocols::valve::types::get_optional_extracted_data; use super::{Server, ServerRule, ServerPlayer}; #[derive(Debug)] @@ -202,10 +214,7 @@ pub mod game { impl Response { pub fn new_from_valve_response(response: super::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) - }; + let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); Self { protocol: response.info.protocol, From d3a1dba3c1e0514290957af313522ef17a876782 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 27 Oct 2022 01:01:11 +0300 Subject: [PATCH 049/597] Restructured app format, goldsrc full support and added implementation for Day of Defeat and Counter-Strike: Condition Zero (#5) * [valve_app_restructure] Initial change * [valve_app_restructure] Some GoldSrc split packet changes * [valve_app_restructure] Counter-Strike: Condition Zero implementation. * [valve_app_restructure] Docs changes * [valve_app_restructure] Added obsolete gold src response * [valve_app_restructure] Day of Defeat implementation. --- CHANGELOG.md | 11 ++ GAMES.md | 4 +- PROTOCOLS.md | 6 +- examples/master_querant.rs | 45 ++++---- examples/tf2.rs | 2 +- src/errors.rs | 5 +- src/games/aliens.rs | 4 +- src/games/asrd.rs | 4 +- src/games/cscz.rs | 12 +++ src/games/csgo.rs | 4 +- src/games/css.rs | 4 +- src/games/dod.rs | 12 +++ src/games/dods.rs | 4 +- src/games/gm.rs | 4 +- src/games/hl2dm.rs | 4 +- src/games/ins.rs | 4 +- src/games/insmic.rs | 4 +- src/games/inss.rs | 4 +- src/games/l4d.rs | 4 +- src/games/l4d2.rs | 4 +- src/games/mod.rs | 4 + src/games/tf2.rs | 4 +- src/games/ts.rs | 4 +- src/protocols/valve/mod.rs | 3 + src/protocols/valve/protocol.rs | 175 ++++++++++++++++++++++---------- src/protocols/valve/types.rs | 53 ++++++++-- src/utils.rs | 4 + 27 files changed, 276 insertions(+), 116 deletions(-) create mode 100644 src/games/cscz.rs create mode 100644 src/games/dod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 529a821..464fe93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ Who knows what the future holds... +# 0.0.5 - ??/??/2022 +Support for GoldSrc split packets and obsolete A2S_INFO response. +Changed the Valve Protocol app parameter to represent the engine responses. +It is now an enum of: +- `Source(Option)` - A Source response with optionally, the id (if the id is present and the response id is not the same, the query fails) +- `GoldSrc(bool)` - A GoldSrc response with the option to enforce the obsolete A2S_INFO response. + +[Counter-Strike: Condition Zero](https://store.steampowered.com/app/80/CounterStrike_Condition_Zero/) implementation. +[Day of Defeat](https://store.steampowered.com/app/30/Day_of_Defeat/) implementation. +Games besides CSGO and TS now have the same response structure. + # 0.0.4 - 23/10/2022 Queries now support DNS resolve. Changed uses a bit, example: from `use gamedig::valve::ValveProtocol::query` to `use gamedig::protocols::valve::query`. diff --git a/GAMES.md b/GAMES.md index a332f47..f8c2657 100644 --- a/GAMES.md +++ b/GAMES.md @@ -13,8 +13,10 @@ | ALIENS | Alien Swarm | Valve Protocol | Not tested. | | ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | | INS | Insurgency | Valve Protocol | | -| INSS | Insurgency: Sandstorm | Valve Protocol | Here you need to use the query port, not the server port. | +| INSS | Insurgency: Sandstorm | Valve Protocol | Use the query port, not the server port. | | INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | Not tested. | +| CSCZ | Counter-Strike: Condition Zero | Valve Protocol | | +| DOD | Day of Defeat | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/PROTOCOLS.md b/PROTOCOLS.md index c83528f..60fee65 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,8 +1,8 @@ # Supported protocols: -| Name | Documentation reference | Used by | Notes | -|----------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------|----------------------------------------| -| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, TS, CSS, DODS, GM, HL2DM, L4D, L4D2, ALIENS, ASRD, INS, INSS, INSMIC | Multi-packet decompression not tested. | +| Name | Documentation reference | Notes | +|----------------|---------------------------------------------------------------------------|----------------------------------------| +| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | ## Planned to add support: Minecraft protocol diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 72a961d..4270f0c 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,9 +1,10 @@ use std::env; -use gamedig::{aliens, asrd, csgo, css, dods, gm, hl2dm, ins, insmic, inss, l4d, l4d2, tf2, ts}; +use gamedig::{aliens, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, tf2, ts}; use gamedig::protocols::valve; +use gamedig::protocols::valve::App; -fn main() { +fn main() -> GDResult<()> { let args: Vec = env::args().collect(); if args.len() == 1 || args[1] == "help".to_string() { @@ -11,10 +12,10 @@ fn main() { println!(" - any game, example: tf2"); println!(" - an ip, example: 192.168.0.0"); println!(" - an port, optional, example: 27015"); - return; + return Ok(()); } else if args.len() < 3 { println!("Minimum number of arguments: 3, try 'help' to see the details."); - return; + return Ok(()); } let ip = args[2].as_str(); @@ -24,21 +25,27 @@ fn main() { }; match args[1].as_str() { - "aliens" => println!("{:?}", aliens::query(ip, port)), - "asrd" => println!("{:?}", asrd::query(ip, port)), - "csgo" => println!("{:?}", csgo::query(ip, port)), - "css" => println!("{:?}", css::query(ip, port)), - "dods" => println!("{:?}", dods::query(ip, port)), - "gm" => println!("{:?}", gm::query(ip, port)), - "hl2dm" => println!("{:?}", hl2dm::query(ip, port)), - "tf2" => println!("{:?}", tf2::query(ip, port)), - "insmic" => println!("{:?}", insmic::query(ip, port)), - "ins" => println!("{:?}", ins::query(ip, port)), - "inss" => println!("{:?}", inss::query(ip, port)), - "l4d" => println!("{:?}", l4d::query(ip, port)), - "l4d2" => println!("{:?}", l4d2::query(ip, port)), - "ts" => println!("{:?}", ts::query(ip, port)), - "_" => println!("{:?}", valve::query(ip, 27015, None, None)), + "aliens" => println!("{:?}", aliens::query(ip, port)?), + "asrd" => println!("{:?}", asrd::query(ip, port)?), + "csgo" => println!("{:?}", csgo::query(ip, port)?), + "css" => println!("{:?}", css::query(ip, port)?), + "dods" => println!("{:?}", dods::query(ip, port)?), + "gm" => println!("{:?}", gm::query(ip, port)?), + "hl2dm" => println!("{:?}", hl2dm::query(ip, port)?), + "tf2" => println!("{:?}", tf2::query(ip, port)?), + "insmic" => println!("{:?}", insmic::query(ip, port)?), + "ins" => println!("{:?}", ins::query(ip, port)?), + "inss" => println!("{:?}", inss::query(ip, port)?), + "l4d" => println!("{:?}", l4d::query(ip, port)?), + "l4d2" => println!("{:?}", l4d2::query(ip, port)?), + "ts" => println!("{:?}", ts::query(ip, port)?), + "cscz" => println!("{:?}", cscz::query(ip, port)?), + "dod" => println!("{:?}", dod::query(ip, port)?), + "_src" => println!("{:?}", valve::query(ip, 27015, App::Source(None), None)?), + "_gld" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(false), None)?), + "_gld_f" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(true), None)?), _ => panic!("Undefined game: {}", args[1]) }; + + Ok(()) } diff --git a/examples/tf2.rs b/examples/tf2.rs index 728f7ef..eb8c70a 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -2,7 +2,7 @@ use gamedig::games::tf2; fn main() { - let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port (which is 27015) + let response = tf2::query("cosminperram.com", None); //or Some(27015), None is the default protocol port (which is 27015) match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) diff --git a/src/errors.rs b/src/errors.rs index 6a7049f..68e1528 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,10 +1,13 @@ + +//! The library's possible errors. + use core::fmt; use std::fmt::Formatter; /// Result of Type and GDError. pub type GDResult = Result; -/// GameDigError, every error you can encounter using the library. +/// GameDigError. #[derive(Debug, Clone)] pub enum GDError { /// The received packet was bigger than the buffer size. diff --git a/src/games/aliens.rs b/src/games/aliens.rs index 05bad88..e5ace35 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::ALIENS), None)?; + }, SteamID::ALIENS.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 52c87d9..32432c3 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::ASRD), None)?; + }, SteamID::ASRD.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cscz.rs b/src/games/cscz.rs new file mode 100644 index 0000000..ac11aa9 --- /dev/null +++ b/src/games/cscz.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::CSCZ.app(), None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 4b18678..3f4785e 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,6 +1,6 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, GatheringSettings, get_optional_extracted_data}; +use crate::protocols::valve::{Server, GatheringSettings, get_optional_extracted_data, SteamID}; use crate::protocols::valve::game::Player; #[derive(Debug)] @@ -54,7 +54,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::CSGO), Some(GatheringSettings { + }, SteamID::CSGO.app(), Some(GatheringSettings { players: true, rules: false // cause csgo doesnt reply with rules anymore }))?; diff --git a/src/games/css.rs b/src/games/css.rs index 004b870..cc45ebe 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::CSS), None)?; + }, SteamID::CSS.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dod.rs b/src/games/dod.rs new file mode 100644 index 0000000..136541c --- /dev/null +++ b/src/games/dod.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::DOD.app(), None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/dods.rs b/src/games/dods.rs index 4b3bfc1..fa4c772 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::DODS), None)?; + }, SteamID::DODS.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index ff61d43..6d9a0ee 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::GM), None)?; + }, SteamID::GM.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index f84ab0c..42e7fae 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::HL2DM), None)?; + }, SteamID::HL2DM.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ins.rs b/src/games/ins.rs index bd51377..f1091ec 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::INS), None)?; + }, SteamID::INS.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 123e9c9..6e1a869 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::INSMIC), None)?; + }, SteamID::INSMIC.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/inss.rs b/src/games/inss.rs index e696c5d..47ae02d 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27131, Some(port) => port - }, Some(App::INSS), None)?; + }, SteamID::INSS.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d.rs b/src/games/l4d.rs index f024be0..3054aaa 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::L4D), None)?; + }, SteamID::L4D.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 108bd84..32074f4 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::L4D2), None)?; + }, SteamID::L4D2.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/mod.rs b/src/games/mod.rs index f9f48c9..6ea01c2 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -29,3 +29,7 @@ pub mod ins; pub mod inss; /// Insurgency: Modern Infantry Combat pub mod insmic; +/// Counter Strike: Condition Zero +pub mod cscz; +/// Day of Defeat +pub mod dod; diff --git a/src/games/tf2.rs b/src/games/tf2.rs index a695ec2..256eb4b 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, game}; +use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::TF2), None)?; + }, SteamID::TF2.app(), None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ts.rs b/src/games/ts.rs index ad7ee8c..80b0f05 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,6 +1,6 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{App, Server, ServerRule, ServerPlayer, get_optional_extracted_data}; +use crate::protocols::valve::{Server, ServerRule, ServerPlayer, get_optional_extracted_data, SteamID}; #[derive(Debug)] pub struct TheShipPlayer { @@ -84,7 +84,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, Some(App::TS), None)?; + }, SteamID::TS.app(), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/protocols/valve/mod.rs b/src/protocols/valve/mod.rs index ebc8f9f..44527b4 100644 --- a/src/protocols/valve/mod.rs +++ b/src/protocols/valve/mod.rs @@ -1,4 +1,7 @@ + +/// The implementation. pub mod protocol; +/// All types used by the implementation. pub mod types; pub use protocol::*; diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index e5ca9c1..4589848 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,8 +1,9 @@ use std::net::UdpSocket; use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; -use crate::protocols::valve::types::{App, Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip}; -use crate::utils::{buffer, complete_address}; +use crate::protocols::valve::{App, ModData, SteamID}; +use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip}; +use crate::utils::{buffer, complete_address, u8_lower_upper}; #[derive(Debug, Clone)] struct Packet { @@ -73,18 +74,27 @@ struct SplitPacket { } impl SplitPacket { - fn new(_appid: u32, buf: &[u8]) -> GDResult { + fn new(app: &App, buf: &[u8]) -> GDResult { let mut pos = 0; let header = buffer::get_u32_le(&buf, &mut pos)?; let id = buffer::get_u32_le(&buf, &mut pos)?; - let total = buffer::get_u8(&buf, &mut pos)?; - let number = buffer::get_u8(&buf, &mut pos)?; - let size = buffer::get_u16_le(&buf, &mut pos)?; //if game is CSS and if protocol is 7, queries with multi-packet responses will crash - let compressed = ((id >> 31) & 1) == 1; - let (decompressed_size, uncompressed_crc32) = match compressed { - false => (None, None), - true => (Some(buffer::get_u32_le(&buf, &mut pos)?), Some(buffer::get_u32_le(&buf, &mut pos)?)) + let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match app { + App::GoldSrc(_) => { + let (lower, upper) = u8_lower_upper(buffer::get_u8(&buf, &mut pos)?); + (lower, upper, 0, false, None, None) + } + App::Source(_) => { + let total = buffer::get_u8(&buf, &mut pos)?; + let number = buffer::get_u8(&buf, &mut pos)?; + let size = buffer::get_u16_le(&buf, &mut pos)?; //if game is CSS and if protocol is 7, queries with multi-packet responses will crash + let compressed = ((id >> 31) & 1) == 1; + let (decompressed_size, uncompressed_crc32) = match compressed { + false => (None, None), + true => (Some(buffer::get_u32_le(&buf, &mut pos)?), Some(buffer::get_u32_le(&buf, &mut pos)?)) + }; + (total, number, size, compressed, decompressed_size, uncompressed_crc32) + } }; Ok(Self { @@ -100,8 +110,8 @@ impl SplitPacket { }) } - fn decompress(&self) -> GDResult> { - if !self.compressed { + fn get_payload(&self) -> GDResult> { + if self.compressed { let mut decoder = Decoder::new(); decoder.write(&self.payload).map_err(|e| GDError::Decompress(e.to_string()))?; @@ -119,14 +129,6 @@ impl SplitPacket { else { Ok(decompressed_payload) } - } else { //already decompressed - Ok(self.payload.clone()) - } - } - - fn get_payload(&self) -> GDResult> { - if self.compressed { - Ok(self.decompress()?) } else { Ok(self.payload.clone()) } @@ -164,15 +166,15 @@ impl ValveProtocol { Ok(buf[..amt].to_vec()) } - fn receive(&self, appid: u32, buffer_size: usize) -> GDResult { + fn receive(&self, app: &App, buffer_size: usize) -> GDResult { let mut buf = self.receive_raw(buffer_size)?; if buf[0] == 0xFE { //the packet is split - let mut main_packet = SplitPacket::new(appid, &buf)?; + let mut main_packet = SplitPacket::new(&app, &buf)?; for _ in 1..main_packet.total { buf = self.receive_raw(buffer_size)?; - let chunk_packet = SplitPacket::new(appid, &buf)?; + let chunk_packet = SplitPacket::new(&app, &buf)?; main_packet.payload.extend(chunk_packet.payload); } @@ -184,11 +186,11 @@ impl ValveProtocol { } /// Ask for a specific request only. - fn get_request_data(&self, appid: u32, kind: Request) -> GDResult> { + fn get_request_data(&self, app: &App, kind: Request) -> GDResult> { let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); self.send(&request_initial_packet)?; - let packet = self.receive(appid, DEFAULT_PACKET_SIZE)?; + let packet = self.receive(app, DEFAULT_PACKET_SIZE)?; if packet.kind != 0x41 { //'A' return Ok(packet.payload.clone()); @@ -198,12 +200,79 @@ impl ValveProtocol { let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); self.send(&challenge_packet)?; - Ok(self.receive(appid, DEFAULT_PACKET_SIZE)?.payload) + Ok(self.receive(app, DEFAULT_PACKET_SIZE)?.payload) + } + + fn get_goldsrc_server_info(buf: &[u8]) -> GDResult { + let mut pos = 0; + + buffer::get_u8(&buf, &mut pos)?; //get the header (useless info) + buffer::get_string(&buf, &mut pos)?; //get the server address (useless info) + let name = buffer::get_string(&buf, &mut pos)?; + let map = buffer::get_string(&buf, &mut pos)?; + let folder = buffer::get_string(&buf, &mut pos)?; + let game = buffer::get_string(&buf, &mut pos)?; + let players = buffer::get_u8(&buf, &mut pos)?; + let max_players = buffer::get_u8(&buf, &mut pos)?; + let protocol = buffer::get_u8(&buf, &mut pos)?; + let server_type = match buffer::get_u8(&buf, &mut pos)? { + 68 => Server::Dedicated, //'D' + 76 => Server::NonDedicated, //'L' + 80 => Server::TV, //'P' + _ => Err(GDError::UnknownEnumCast)? + }; + let environment_type = match buffer::get_u8(&buf, &mut pos)? { + 76 => Environment::Linux, //'L' + 87 => Environment::Windows, //'W' + _ => Err(GDError::UnknownEnumCast)? + }; + let has_password = buffer::get_u8(&buf, &mut pos)? == 1; + let is_mod = buffer::get_u8(&buf, &mut pos)? == 1; + let mod_data = match is_mod { + false => None, + true => Some(ModData { + link: buffer::get_string(&buf, &mut pos)?, + download_link: buffer::get_string(&buf, &mut pos)?, + version: buffer::get_u32_le(&buf, &mut pos)?, + size: buffer::get_u32_le(&buf, &mut pos)?, + multiplayer_only: buffer::get_u8(&buf, &mut pos)? == 1, + has_own_dll: buffer::get_u8(&buf, &mut pos)? == 1 + }) + }; + let vac_secured = buffer::get_u8(&buf, &mut pos)? == 1; + let bots = buffer::get_u8(&buf, &mut pos)?; + + Ok(ServerInfo { + protocol, + name, + map, + folder, + game, + appid: 0, //not present in the obsolete response + players, + max_players, + bots, + server_type, + environment_type, + has_password, + vac_secured, + the_ship: None, + version: "".to_string(), //a version field only for the mod + extra_data: None, + is_mod, + mod_data + }) } /// Get the server information's. - fn get_server_info(&self, initial_appid: u32) -> GDResult { - let buf = self.get_request_data(initial_appid, Request::INFO)?; + fn get_server_info(&self, app: &App) -> GDResult { + let buf = self.get_request_data(&app, Request::INFO)?; + if let App::GoldSrc(force) = app { + if *force { + return ValveProtocol::get_goldsrc_server_info(&buf); + } + } + let mut pos = 0; let protocol = buffer::get_u8(&buf, &mut pos)?; @@ -218,7 +287,7 @@ impl ValveProtocol { let server_type = match buffer::get_u8(&buf, &mut pos)? { 100 => Server::Dedicated, //'d' 108 => Server::NonDedicated, //'l' - 112 => Server::SourceTV, //'p' + 112 => Server::TV, //'p' _ => Err(GDError::UnknownEnumCast)? }; let environment_type = match buffer::get_u8(&buf, &mut pos)? { @@ -229,7 +298,7 @@ impl ValveProtocol { }; let has_password = buffer::get_u8(&buf, &mut pos)? == 1; let vac_secured = buffer::get_u8(&buf, &mut pos)? == 1; - let the_ship = match appid == App::TS as u32 { + let the_ship = match *app == SteamID::TS.app() { false => None, true => Some(TheShip { mode: buffer::get_u8(&buf, &mut pos)?, @@ -289,13 +358,15 @@ impl ValveProtocol { vac_secured, the_ship, version, - extra_data + extra_data, + is_mod: false, + mod_data: None }) } /// Get the server player's. - fn get_server_players(&self, appid: u32) -> GDResult> { - let buf = self.get_request_data(appid, Request::PLAYERS)?; + fn get_server_players(&self, app: &App) -> GDResult> { + let buf = self.get_request_data(&app, Request::PLAYERS)?; let mut pos = 0; let count = buffer::get_u8(&buf, &mut pos)?; @@ -307,11 +378,11 @@ impl ValveProtocol { name: buffer::get_string(&buf, &mut pos)?, score: buffer::get_u32_le(&buf, &mut pos)?, duration: buffer::get_f32_le(&buf, &mut pos)?, - deaths: match appid == App::TS as u32 { + deaths: match *app == SteamID::TS.app() { false => None, true => Some(buffer::get_u32_le(&buf, &mut pos)?) }, - money: match appid == App::TS as u32 { + money: match *app == SteamID::TS.app() { false => None, true => Some(buffer::get_u32_le(&buf, &mut pos)?) } @@ -322,12 +393,12 @@ impl ValveProtocol { } /// Get the server rules's. - fn get_server_rules(&self, appid: u32) -> GDResult>> { - if appid == App::CSGO as u32 { //cause csgo wont respond to this since feb 21 2014 update + fn get_server_rules(&self, app: &App) -> GDResult>> { + if *app == SteamID::CSGO.app() { //cause csgo wont respond to this since feb 21 2014 update return Ok(None); } - let buf = self.get_request_data(appid, Request::RULES)?; + let buf = self.get_request_data(&app, Request::RULES)?; let mut pos = 0; let count = buffer::get_u16_le(&buf, &mut pos)?; @@ -344,25 +415,19 @@ impl ValveProtocol { } } -/// Query a server, you need to provide the address, the port and optionally, the app and the -/// gather settings, the app being *None* means to anonymously query the server, and the gather -/// settings being *None* means to get the players and the rules. -pub fn query(address: &str, port: u16, app: Option, gather_settings: Option) -> Result { +/// Query a server by providing the address, the port, the app and the gather settings, the settings +/// being *None* means to also get the players and the rules. +pub fn query(address: &str, port: u16, app: App, gather_settings: Option) -> GDResult { let client = ValveProtocol::new(address, port)?; - let mut query_app_id = match app { - None => 0, - Some(app) => app as u32 - }; + let info = client.get_server_info(&app)?; - let info = client.get_server_info(query_app_id)?; - - if query_app_id != 0 { - if info.appid != query_app_id { - return Err(GDError::BadGame(format!("Expected {}, found {} instead!", query_app_id, info.appid))); + if let App::Source(x) = &app { + if let Some(appid) = x { + if *appid != info.appid { + return Err(GDError::BadGame(format!("Expected {}, found {} instead!", *appid, info.appid))); + } } - } else { - query_app_id = info.appid; } let (gather_players, gather_rules) = match gather_settings.is_some() { @@ -377,11 +442,11 @@ pub fn query(address: &str, port: u16, app: Option, gather_settings: Option info, players: match gather_players { false => None, - true => Some(client.get_server_players(query_app_id)?) + true => Some(client.get_server_players(&app)?) }, rules: match gather_rules { false => None, - true => client.get_server_rules(query_app_id)? + true => client.get_server_rules(&app)? } }) } diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index b06c793..6d612d5 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,12 +1,10 @@ -//! All types used by the Valve protocol - /// The type of the server. #[derive(Debug)] pub enum Server { Dedicated, NonDedicated, - SourceTV + TV } /// The Operating System that the server is on. @@ -50,16 +48,20 @@ pub struct ServerInfo { pub server_type: Server, /// The Operating System that the server is on. pub environment_type: Environment, - /// Indicated whether the server requires a password. + /// Indicates whether the server requires a password. pub has_password: bool, - /// Indicated whether the server uses VAC. + /// Indicates whether the server uses VAC. pub vac_secured: bool, /// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data pub the_ship: Option, /// Version of the game installed on the server. pub version: String, /// Some extra data that the server might provide or not. - pub extra_data: Option + pub extra_data: Option, + /// GoldSrc only: Indicates whether the hosted game is a mod. + pub is_mod: bool, + /// GoldSrc only: If the game is a mod, provide additional data. + pub mod_data: Option } /// A server player. @@ -109,6 +111,16 @@ pub struct ExtraData { pub game_id: Option } +#[derive(Debug)] +pub struct ModData { + pub link: String, + pub download_link: String, + pub version: u32, + pub size: u32, + pub multiplayer_only: bool, + pub has_own_dll: bool +} + pub fn get_optional_extracted_data(data: Option) -> (Option, Option, Option, Option, Option) { match data { None => (None, None, None, None, None), @@ -128,9 +140,13 @@ pub enum Request { RULES = 0x56 } -/// Supported app id's +/// Supported steam apps id's #[derive(PartialEq, Clone)] -pub enum App { +pub enum SteamID { + /// Day of Defeat + DOD = 30, + /// Counter-Strike: Condition Zero + CSCZ = 80, /// Counter-Strike: Source CSS = 240, /// Day of Defeat: Source @@ -161,6 +177,27 @@ pub enum App { ASRD = 563560, } +impl SteamID { + pub fn app(self) -> App { + match self { + SteamID::CSCZ | SteamID::DOD => App::GoldSrc(false), + x => App::Source(Some(x as u32)) + } + } +} + +/// App type +#[derive(PartialEq, Clone)] +pub enum App { + /// A Source game, the argument represents the wanted response steam app id, if its **None**, + /// let the query find it, if its **Some**, the query fails if the response id is not the + /// specified one. + Source(Option), + /// A GoldSrc game, the argument indicates whether to enforce getting the obsolete A2S_INFO + /// goldsrc response or not. + GoldSrc(bool) +} + /// What data to gather, purely used only with the query function. pub struct GatheringSettings { pub players: bool, diff --git a/src/utils.rs b/src/utils.rs index 3cfdf1d..6ca978b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,6 +16,10 @@ pub fn complete_address(address: &str, port: u16) -> GDResult { Ok(resolve_dns(address)? + ":" + &*port.to_string()) } +pub fn u8_lower_upper(n: u8) -> (u8, u8) { + (n & 15, n >> 4) +} + pub mod buffer { use super::*; From ac9d385fb60c1d7f47bc77da5f11059df96936cc Mon Sep 17 00:00:00 2001 From: cosminperram Date: Thu, 27 Oct 2022 11:09:59 +0300 Subject: [PATCH 050/597] Fixed multipacket response when protocol = 7 with certain apps --- CHANGELOG.md | 3 ++- GAMES.md | 2 +- src/protocols/valve/protocol.rs | 34 ++++++++++++++++++--------------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 464fe93..3e65158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ It is now an enum of: [Counter-Strike: Condition Zero](https://store.steampowered.com/app/80/CounterStrike_Condition_Zero/) implementation. [Day of Defeat](https://store.steampowered.com/app/30/Day_of_Defeat/) implementation. -Games besides CSGO and TS now have the same response structure. +Games besides CSGO and TS now have the same response structure. +Fixed Source multipacket response crash due to when a certain app with a certain protocol doesnt have the Size field. # 0.0.4 - 23/10/2022 Queries now support DNS resolve. diff --git a/GAMES.md b/GAMES.md index f8c2657..37af31e 100644 --- a/GAMES.md +++ b/GAMES.md @@ -5,7 +5,7 @@ | TF2 | Team Fortress 2 | Valve Protocol | | | TS | The Ship | Valve Protocol | | | CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | -| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, queries with multi-packet responses will crash. | +| CSS | Counter-Strike: Source | Valve Protocol | | | DODS | Day of Defeat: Source | Valve Protocol | | | L4D | Left 4 Dead | Valve Protocol | | | L4D2 | Left 4 Dead 2 | Valve Protocol | | diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 4589848..8ac9c14 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -74,7 +74,7 @@ struct SplitPacket { } impl SplitPacket { - fn new(app: &App, buf: &[u8]) -> GDResult { + fn new(app: &App, protocol: u8, buf: &[u8]) -> GDResult { let mut pos = 0; let header = buffer::get_u32_le(&buf, &mut pos)?; @@ -87,7 +87,10 @@ impl SplitPacket { App::Source(_) => { let total = buffer::get_u8(&buf, &mut pos)?; let number = buffer::get_u8(&buf, &mut pos)?; - let size = buffer::get_u16_le(&buf, &mut pos)?; //if game is CSS and if protocol is 7, queries with multi-packet responses will crash + let size = match protocol == 7 && (*app == SteamID::CSS.app()) { //certain apps with protocol = 7 doesnt have this field + false => buffer::get_u16_le(&buf, &mut pos)?, + true => 1248 + }; let compressed = ((id >> 31) & 1) == 1; let (decompressed_size, uncompressed_crc32) = match compressed { false => (None, None), @@ -166,15 +169,15 @@ impl ValveProtocol { Ok(buf[..amt].to_vec()) } - fn receive(&self, app: &App, buffer_size: usize) -> GDResult { + fn receive(&self, app: &App, protocol: u8, buffer_size: usize) -> GDResult { let mut buf = self.receive_raw(buffer_size)?; if buf[0] == 0xFE { //the packet is split - let mut main_packet = SplitPacket::new(&app, &buf)?; + let mut main_packet = SplitPacket::new(&app, protocol, &buf)?; for _ in 1..main_packet.total { buf = self.receive_raw(buffer_size)?; - let chunk_packet = SplitPacket::new(&app, &buf)?; + let chunk_packet = SplitPacket::new(&app, protocol, &buf)?; main_packet.payload.extend(chunk_packet.payload); } @@ -186,11 +189,11 @@ impl ValveProtocol { } /// Ask for a specific request only. - fn get_request_data(&self, app: &App, kind: Request) -> GDResult> { + fn get_request_data(&self, app: &App, protocol: u8, kind: Request) -> GDResult> { let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); self.send(&request_initial_packet)?; - let packet = self.receive(app, DEFAULT_PACKET_SIZE)?; + let packet = self.receive(app, protocol, DEFAULT_PACKET_SIZE)?; if packet.kind != 0x41 { //'A' return Ok(packet.payload.clone()); @@ -200,7 +203,7 @@ impl ValveProtocol { let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); self.send(&challenge_packet)?; - Ok(self.receive(app, DEFAULT_PACKET_SIZE)?.payload) + Ok(self.receive(app, protocol, DEFAULT_PACKET_SIZE)?.payload) } fn get_goldsrc_server_info(buf: &[u8]) -> GDResult { @@ -266,7 +269,7 @@ impl ValveProtocol { /// Get the server information's. fn get_server_info(&self, app: &App) -> GDResult { - let buf = self.get_request_data(&app, Request::INFO)?; + let buf = self.get_request_data(&app, 0, Request::INFO)?; if let App::GoldSrc(force) = app { if *force { return ValveProtocol::get_goldsrc_server_info(&buf); @@ -365,8 +368,8 @@ impl ValveProtocol { } /// Get the server player's. - fn get_server_players(&self, app: &App) -> GDResult> { - let buf = self.get_request_data(&app, Request::PLAYERS)?; + fn get_server_players(&self, app: &App, protocol: u8) -> GDResult> { + let buf = self.get_request_data(&app, protocol, Request::PLAYERS)?; let mut pos = 0; let count = buffer::get_u8(&buf, &mut pos)?; @@ -393,12 +396,12 @@ impl ValveProtocol { } /// Get the server rules's. - fn get_server_rules(&self, app: &App) -> GDResult>> { + fn get_server_rules(&self, app: &App, protocol: u8) -> GDResult>> { if *app == SteamID::CSGO.app() { //cause csgo wont respond to this since feb 21 2014 update return Ok(None); } - let buf = self.get_request_data(&app, Request::RULES)?; + let buf = self.get_request_data(&app, protocol, Request::RULES)?; let mut pos = 0; let count = buffer::get_u16_le(&buf, &mut pos)?; @@ -421,6 +424,7 @@ pub fn query(address: &str, port: u16, app: App, gather_settings: Option None, - true => Some(client.get_server_players(&app)?) + true => Some(client.get_server_players(&app, protocol)?) }, rules: match gather_rules { false => None, - true => client.get_server_rules(&app)? + true => client.get_server_rules(&app, protocol)? } }) } From d3b71fccf6170d7b4961ab201dd6715847f8e6e6 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 15 Nov 2022 17:20:01 +0200 Subject: [PATCH 051/597] Changed packet size to protocol specified size --- src/protocols/valve/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 8ac9c14..7c8c2f5 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -143,7 +143,7 @@ struct ValveProtocol { complete_address: String } -static DEFAULT_PACKET_SIZE: usize = 2048; +static DEFAULT_PACKET_SIZE: usize = 1400; impl ValveProtocol { fn new(address: &str, port: u16) -> GDResult { From caa7329a68bb03b78d67a5a59bb62d8276090bab Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 15 Nov 2022 21:07:15 +0200 Subject: [PATCH 052/597] Added socket timeout capability and reduced PACKET_SIZE to 1400 as specified from protocol --- CHANGELOG.md | 8 +++- examples/master_querant.rs | 6 +-- src/errors.rs | 6 +++ src/games/aliens.rs | 2 +- src/games/asrd.rs | 2 +- src/games/cscz.rs | 2 +- src/games/csgo.rs | 4 +- src/games/css.rs | 2 +- src/games/dod.rs | 2 +- src/games/dods.rs | 2 +- src/games/gm.rs | 2 +- src/games/hl2dm.rs | 2 +- src/games/ins.rs | 2 +- src/games/insmic.rs | 2 +- src/games/inss.rs | 2 +- src/games/l4d.rs | 2 +- src/games/l4d2.rs | 2 +- src/games/tf2.rs | 2 +- src/games/ts.rs | 2 +- src/protocols/valve/protocol.rs | 53 ++++++++++++++------------ src/protocols/valve/types.rs | 67 ++++++++++++++++++++++++++++++++- 21 files changed, 125 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e65158..7c1750d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,20 @@ Who knows what the future holds... # 0.0.5 - ??/??/2022 +Added `SocketBind` error, regarding failing to bind a socket. +Socket custom timeout capability (with an error if provided durations are zero). +Because of this, a parameter similar to GatherSettings has been added on the Valve Protocol Query. Support for GoldSrc split packets and obsolete A2S_INFO response. Changed the Valve Protocol app parameter to represent the engine responses. It is now an enum of: -- `Source(Option)` - A Source response with optionally, the id (if the id is present and the response id is not the same, the query fails) +- `Source(Option)` - A Source response with optionally, the id (if the id is present and the response id is not the same, the query fails), if it isn't provided, find it. - `GoldSrc(bool)` - A GoldSrc response with the option to enforce the obsolete A2S_INFO response. +Fixed Source multi-packet response crash due to when a certain app with a certain protocol doesn't have the Size field. +Reduced Valve Protocol `PACKET_SIZE` to be as specified from 2048 to 1400. [Counter-Strike: Condition Zero](https://store.steampowered.com/app/80/CounterStrike_Condition_Zero/) implementation. [Day of Defeat](https://store.steampowered.com/app/30/Day_of_Defeat/) implementation. Games besides CSGO and TS now have the same response structure. -Fixed Source multipacket response crash due to when a certain app with a certain protocol doesnt have the Size field. # 0.0.4 - 23/10/2022 Queries now support DNS resolve. diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 4270f0c..015374d 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -41,9 +41,9 @@ fn main() -> GDResult<()> { "ts" => println!("{:?}", ts::query(ip, port)?), "cscz" => println!("{:?}", cscz::query(ip, port)?), "dod" => println!("{:?}", dod::query(ip, port)?), - "_src" => println!("{:?}", valve::query(ip, 27015, App::Source(None), None)?), - "_gld" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(false), None)?), - "_gld_f" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(true), None)?), + "_src" => println!("{:?}", valve::query(ip, 27015, App::Source(None), None, None)?), + "_gld" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), + "_gld_f" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/errors.rs b/src/errors.rs index 68e1528..584337e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -28,6 +28,10 @@ pub enum GDError { BadGame(String), /// Problems occurred while dns resolving. DnsResolve(String), + /// Couldn't bind a socket. + SocketBind(String), + /// Invalid input. + InvalidInput(String), } impl fmt::Display for GDError { @@ -42,6 +46,8 @@ impl fmt::Display for GDError { GDError::UnknownEnumCast => write!(f, "Unknown enum cast encountered."), GDError::BadGame(details) => write!(f, "Queried another game that the supposed one: {details}"), GDError::DnsResolve(details) => write!(f, "DNS Resolve: {details}"), + GDError::SocketBind(details) => write!(f, "Socket bind: {details}"), + GDError::InvalidInput(details) => write!(f, "Invalid input: {details}"), } } } diff --git a/src/games/aliens.rs b/src/games/aliens.rs index e5ace35..3e8dc43 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::ALIENS.app(), None)?; + }, SteamID::ALIENS.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 32432c3..4852233 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::ASRD.app(), None)?; + }, SteamID::ASRD.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cscz.rs b/src/games/cscz.rs index ac11aa9..56106dc 100644 --- a/src/games/cscz.rs +++ b/src/games/cscz.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CSCZ.app(), None)?; + }, SteamID::CSCZ.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 3f4785e..2ad965e 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -54,10 +54,10 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CSGO.app(), Some(GatheringSettings { + }, SteamID::CSGO.as_app(), Some(GatheringSettings { players: true, rules: false // cause csgo doesnt reply with rules anymore - }))?; + }), None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/css.rs b/src/games/css.rs index cc45ebe..383a3f6 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CSS.app(), None)?; + }, SteamID::CSS.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dod.rs b/src/games/dod.rs index 136541c..928f94f 100644 --- a/src/games/dod.rs +++ b/src/games/dod.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::DOD.app(), None)?; + }, SteamID::DOD.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dods.rs b/src/games/dods.rs index fa4c772..0e3cdd1 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::DODS.app(), None)?; + }, SteamID::DODS.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index 6d9a0ee..7040c28 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::GM.app(), None)?; + }, SteamID::GM.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index 42e7fae..c45741e 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::HL2DM.app(), None)?; + }, SteamID::HL2DM.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ins.rs b/src/games/ins.rs index f1091ec..82341b3 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::INS.app(), None)?; + }, SteamID::INS.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 6e1a869..cf31b69 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::INSMIC.app(), None)?; + }, SteamID::INSMIC.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/inss.rs b/src/games/inss.rs index 47ae02d..d57c036 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27131, Some(port) => port - }, SteamID::INSS.app(), None)?; + }, SteamID::INSS.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 3054aaa..58a861c 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::L4D.app(), None)?; + }, SteamID::L4D.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 32074f4..23dded8 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::L4D2.app(), None)?; + }, SteamID::L4D2.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 256eb4b..ba00201 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -6,7 +6,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::TF2.app(), None)?; + }, SteamID::TF2.as_app(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ts.rs b/src/games/ts.rs index 80b0f05..a59c4ae 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -84,7 +84,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::TS.app(), None)?; + }, SteamID::TS.as_app(), None, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 7c8c2f5..8aeceab 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,7 +1,7 @@ use std::net::UdpSocket; use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; -use crate::protocols::valve::{App, ModData, SteamID}; +use crate::protocols::valve::{App, ModData, SteamID, TimeoutSettings}; use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip}; use crate::utils::{buffer, complete_address, u8_lower_upper}; @@ -87,7 +87,7 @@ impl SplitPacket { App::Source(_) => { let total = buffer::get_u8(&buf, &mut pos)?; let number = buffer::get_u8(&buf, &mut pos)?; - let size = match protocol == 7 && (*app == SteamID::CSS.app()) { //certain apps with protocol = 7 doesnt have this field + let size = match protocol == 7 && (*app == SteamID::CSS.as_app()) { //certain apps with protocol = 7 doesnt have this field false => buffer::get_u16_le(&buf, &mut pos)?, true => 1248 }; @@ -143,12 +143,17 @@ struct ValveProtocol { complete_address: String } -static DEFAULT_PACKET_SIZE: usize = 1400; +static PACKET_SIZE: usize = 1400; impl ValveProtocol { - fn new(address: &str, port: u16) -> GDResult { + fn new(address: &str, port: u16, timeout_settings: TimeoutSettings) -> GDResult { + let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| GDError::SocketBind(e.to_string()))?; + + socket.set_read_timeout(timeout_settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new + socket.set_write_timeout(timeout_settings.get_write()).unwrap();//checks if these are 0 and throws an error + Ok(Self { - socket: UdpSocket::bind("0.0.0.0:0").unwrap(), + socket, complete_address: complete_address(address, port)? }) } @@ -193,7 +198,7 @@ impl ValveProtocol { let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); self.send(&request_initial_packet)?; - let packet = self.receive(app, protocol, DEFAULT_PACKET_SIZE)?; + let packet = self.receive(app, protocol, PACKET_SIZE)?; if packet.kind != 0x41 { //'A' return Ok(packet.payload.clone()); @@ -203,7 +208,7 @@ impl ValveProtocol { let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); self.send(&challenge_packet)?; - Ok(self.receive(app, protocol, DEFAULT_PACKET_SIZE)?.payload) + Ok(self.receive(app, protocol, PACKET_SIZE)?.payload) } fn get_goldsrc_server_info(buf: &[u8]) -> GDResult { @@ -301,7 +306,7 @@ impl ValveProtocol { }; let has_password = buffer::get_u8(&buf, &mut pos)? == 1; let vac_secured = buffer::get_u8(&buf, &mut pos)? == 1; - let the_ship = match *app == SteamID::TS.app() { + let the_ship = match *app == SteamID::TS.as_app() { false => None, true => Some(TheShip { mode: buffer::get_u8(&buf, &mut pos)?, @@ -381,11 +386,11 @@ impl ValveProtocol { name: buffer::get_string(&buf, &mut pos)?, score: buffer::get_u32_le(&buf, &mut pos)?, duration: buffer::get_f32_le(&buf, &mut pos)?, - deaths: match *app == SteamID::TS.app() { + deaths: match *app == SteamID::TS.as_app() { false => None, true => Some(buffer::get_u32_le(&buf, &mut pos)?) }, - money: match *app == SteamID::TS.app() { + money: match *app == SteamID::TS.as_app() { false => None, true => Some(buffer::get_u32_le(&buf, &mut pos)?) } @@ -397,7 +402,7 @@ impl ValveProtocol { /// Get the server rules's. fn get_server_rules(&self, app: &App, protocol: u8) -> GDResult>> { - if *app == SteamID::CSGO.app() { //cause csgo wont respond to this since feb 21 2014 update + if *app == SteamID::CSGO.as_app() { //cause csgo wont respond to this since feb 21 2014 update return Ok(None); } @@ -418,10 +423,16 @@ impl ValveProtocol { } } -/// Query a server by providing the address, the port, the app and the gather settings, the settings -/// being *None* means to also get the players and the rules. -pub fn query(address: &str, port: u16, app: App, gather_settings: Option) -> GDResult { - let client = ValveProtocol::new(address, port)?; +/// Query a server by providing the address, the port, the app, gather and timeout settings. +/// Providing None to the settings results in using the default values for them (GatherSettings::[default](GatheringSettings::default), TimeoutSettings::[default](TimeoutSettings::default)). +pub fn query(address: &str, port: u16, app: App, gather_settings: Option, timeout_settings: Option) -> GDResult { + let response_gather_settings = gather_settings.unwrap_or(GatheringSettings::default()); + let response_timeout_settings = timeout_settings.unwrap_or(TimeoutSettings::default()); + get_response(address, port, app, response_gather_settings, response_timeout_settings) +} + +fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSettings, timeout_settings: TimeoutSettings) -> GDResult { + let client = ValveProtocol::new(address, port, timeout_settings)?; let info = client.get_server_info(&app)?; let protocol = info.protocol; @@ -434,21 +445,13 @@ pub fn query(address: &str, port: u16, app: App, gather_settings: Option (true, true), - true => { - let settings = gather_settings.unwrap(); - (settings.players, settings.rules) - } - }; - Ok(Response { info, - players: match gather_players { + players: match gather_settings.players { false => None, true => Some(client.get_server_players(&app, protocol)?) }, - rules: match gather_rules { + rules: match gather_settings.rules { false => None, true => client.get_server_rules(&app, protocol)? } diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 6d612d5..a3819d2 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,3 +1,5 @@ +use std::time::Duration; +use crate::{GDError, GDResult}; /// The type of the server. #[derive(Debug)] @@ -111,6 +113,7 @@ pub struct ExtraData { pub game_id: Option } +/// Data related to GoldSrc Mod response. #[derive(Debug)] pub struct ModData { pub link: String, @@ -141,6 +144,7 @@ pub enum Request { } /// Supported steam apps id's +#[repr(u32)] #[derive(PartialEq, Clone)] pub enum SteamID { /// Day of Defeat @@ -178,10 +182,11 @@ pub enum SteamID { } impl SteamID { - pub fn app(self) -> App { + /// Get ID as App (the engine is specified). + pub fn as_app(&self) -> App { match self { SteamID::CSCZ | SteamID::DOD => App::GoldSrc(false), - x => App::Source(Some(x as u32)) + x => App::Source(Some(x.clone() as u32)) } } } @@ -204,6 +209,64 @@ pub struct GatheringSettings { pub rules: bool } +impl Default for GatheringSettings { + /// Default values are true for both the players and the rules. + fn default() -> Self { + Self { + players: true, + rules: true + } + } +} + +/// Timeout settings for socket operations +pub struct TimeoutSettings { + read: Option, + write: Option +} + +impl TimeoutSettings { + /// Construct new settings, passing None will block indefinitely. Passing zero Duration throws GDError::[InvalidInput](GDError::InvalidInput). + pub fn new(read: Option, write: Option) -> GDResult { + if let Some(read_duration) = read { + if read_duration == Duration::new(0, 0) { + return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_owned())) + } + } + + if let Some(write_duration) = write { + if write_duration == Duration::new(0, 0) { + return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_owned())) + } + } + + Ok(Self { + read, + write + }) + } + + /// Get the read timeout. + pub fn get_read(&self) -> Option { + self.read + } + + /// Get the write timeout. + pub fn get_write(&self) -> Option { + self.write + } +} + +impl Default for TimeoutSettings { + /// Default values are 4 seconds for both read and write. + fn default() -> Self { + Self { + read: Some(Duration::from_secs(4)), + write: Some(Duration::from_secs(4)) + } + } +} + /// Generic response types that are used by many games, they are the protocol ones, but without the /// unnecessary bits (example: the **The Ship**-only fields) pub mod game { From a08afb27129007bb6e00da9f4940dd1e8ed82cbe Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 15 Nov 2022 21:09:51 +0200 Subject: [PATCH 053/597] Bumped version to 0.05 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1750d..549830f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Who knows what the future holds... -# 0.0.5 - ??/??/2022 +# 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. Socket custom timeout capability (with an error if provided durations are zero). Because of this, a parameter similar to GatherSettings has been added on the Valve Protocol Query. From f04c883269d2676b70d15f1b076a506edc2da7ef Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 15 Nov 2022 21:10:31 +0200 Subject: [PATCH 054/597] Forgot cargo.toml version bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ba0407d..94e1b52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.0.4" +version = "0.0.5" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" From 974e093e2305db7a9988f01f86ec0d4468ca211f Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Wed, 16 Nov 2022 00:06:13 +0200 Subject: [PATCH 055/597] Move TimeoutSettings to be a type that can be used by multiple protocols --- src/protocols/mod.rs | 2 ++ src/protocols/types.rs | 50 +++++++++++++++++++++++++++++++++ src/protocols/valve/protocol.rs | 3 +- src/protocols/valve/types.rs | 48 ------------------------------- 4 files changed, 54 insertions(+), 49 deletions(-) create mode 100644 src/protocols/types.rs diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index f84b187..0e4e770 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -6,3 +6,5 @@ /// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) pub mod valve; +/// General types that are used by all protocols. +pub mod types; diff --git a/src/protocols/types.rs b/src/protocols/types.rs new file mode 100644 index 0000000..4d8be27 --- /dev/null +++ b/src/protocols/types.rs @@ -0,0 +1,50 @@ +use std::time::Duration; +use crate::{GDError, GDResult}; + +/// Timeout settings for socket operations +pub struct TimeoutSettings { + read: Option, + write: Option +} + +impl TimeoutSettings { + /// Construct new settings, passing None will block indefinitely. Passing zero Duration throws GDError::[InvalidInput](GDError::InvalidInput). + pub fn new(read: Option, write: Option) -> GDResult { + if let Some(read_duration) = read { + if read_duration == Duration::new(0, 0) { + return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_owned())) + } + } + + if let Some(write_duration) = write { + if write_duration == Duration::new(0, 0) { + return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_owned())) + } + } + + Ok(Self { + read, + write + }) + } + + /// Get the read timeout. + pub fn get_read(&self) -> Option { + self.read + } + + /// Get the write timeout. + pub fn get_write(&self) -> Option { + self.write + } +} + +impl Default for TimeoutSettings { + /// Default values are 4 seconds for both read and write. + fn default() -> Self { + Self { + read: Some(Duration::from_secs(4)), + write: Some(Duration::from_secs(4)) + } + } +} diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 8aeceab..ac9ac25 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,7 +1,8 @@ use std::net::UdpSocket; use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; -use crate::protocols::valve::{App, ModData, SteamID, TimeoutSettings}; +use crate::protocols::types::TimeoutSettings; +use crate::protocols::valve::{App, ModData, SteamID}; use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip}; use crate::utils::{buffer, complete_address, u8_lower_upper}; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index a3819d2..6767e27 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -219,54 +219,6 @@ impl Default for GatheringSettings { } } -/// Timeout settings for socket operations -pub struct TimeoutSettings { - read: Option, - write: Option -} - -impl TimeoutSettings { - /// Construct new settings, passing None will block indefinitely. Passing zero Duration throws GDError::[InvalidInput](GDError::InvalidInput). - pub fn new(read: Option, write: Option) -> GDResult { - if let Some(read_duration) = read { - if read_duration == Duration::new(0, 0) { - return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_owned())) - } - } - - if let Some(write_duration) = write { - if write_duration == Duration::new(0, 0) { - return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_owned())) - } - } - - Ok(Self { - read, - write - }) - } - - /// Get the read timeout. - pub fn get_read(&self) -> Option { - self.read - } - - /// Get the write timeout. - pub fn get_write(&self) -> Option { - self.write - } -} - -impl Default for TimeoutSettings { - /// Default values are 4 seconds for both read and write. - fn default() -> Self { - Self { - read: Some(Duration::from_secs(4)), - write: Some(Duration::from_secs(4)) - } - } -} - /// Generic response types that are used by many games, they are the protocol ones, but without the /// unnecessary bits (example: the **The Ship**-only fields) pub mod game { From ee0223a7a3949557e58e184fa8eeb6d7d58c7f47 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 24 Nov 2022 22:52:54 +0200 Subject: [PATCH 056/597] Minecraft implementation (#6) * Initial minecraft support * Made previews_chat an option * Better error handling and removed version structure * Minecraft Server types * Fixed compilation and renamed stuff * 'extract till you drop!' extracted sockets * extracted java version and fixed socket udp receive * Legacy 1.4 and 1.6 implementation (incomplete) * Furter implementation * Implementations work * Protocol beta v1.8+ implemented * Removed bedrock support * Added auto query * Renamed minecraft to mc and added to md's * Docs, renames and small optimization changes * Changed java version to be able to return None on players sample --- CHANGELOG.md | 3 + Cargo.toml | 7 +- GAMES.md | 4 +- PROTOCOLS.md | 3 +- examples/master_querant.rs | 8 +- examples/minecraft.rs | 10 ++ src/errors.rs | 9 ++ src/games/mc.rs | 18 +++ src/games/mod.rs | 2 + src/lib.rs | 3 + src/protocols/minecraft/mod.rs | 9 ++ src/protocols/minecraft/protocol/java.rs | 127 +++++++++++++++ .../minecraft/protocol/legacy_bv1_8.rs | 71 ++++++++ .../minecraft/protocol/legacy_v1_4.rs | 76 +++++++++ .../minecraft/protocol/legacy_v1_6.rs | 103 ++++++++++++ src/protocols/minecraft/protocol/mod.rs | 45 ++++++ src/protocols/minecraft/types.rs | 151 ++++++++++++++++++ src/protocols/mod.rs | 6 +- src/protocols/types.rs | 5 +- src/protocols/valve/protocol.rs | 103 +++++------- src/protocols/valve/types.rs | 2 - src/socket.rs | 88 ++++++++++ src/utils.rs | 37 ++++- 23 files changed, 810 insertions(+), 80 deletions(-) create mode 100644 examples/minecraft.rs create mode 100644 src/games/mc.rs create mode 100644 src/protocols/minecraft/mod.rs create mode 100644 src/protocols/minecraft/protocol/java.rs create mode 100644 src/protocols/minecraft/protocol/legacy_bv1_8.rs create mode 100644 src/protocols/minecraft/protocol/legacy_v1_4.rs create mode 100644 src/protocols/minecraft/protocol/legacy_v1_6.rs create mode 100644 src/protocols/minecraft/protocol/mod.rs create mode 100644 src/protocols/minecraft/types.rs create mode 100644 src/socket.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 549830f..c1fe3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Who knows what the future holds... +# 0.0.6 - ??/??/2022 +[Minecraft](https://www.minecraft.com) implementation (bedrock not supported yet). + # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. Socket custom timeout capability (with an error if provided durations are zero). diff --git a/Cargo.toml b/Cargo.toml index 94e1b52..5000cf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ keywords = ["server", "verify", "game", "check", "status"] msrv = "1.58.1" [dependencies] +bzip2-rs = "0.1.2" # for compression crc32fast = "1.3.2" -bzip2-rs = "0.1.2" -trust-dns-resolver = "0.22.0" + +trust-dns-resolver = "0.22.0" # dns resolving + +serde_json = "1.0.87" # json to structs diff --git a/GAMES.md b/GAMES.md index 37af31e..208c05e 100644 --- a/GAMES.md +++ b/GAMES.md @@ -17,7 +17,7 @@ | INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | Not tested. | | CSCZ | Counter-Strike: Condition Zero | Valve Protocol | | | DOD | Day of Defeat | Valve Protocol | | +| MC | Minecraft | Proprietary | Bedrock not supported yet. | ## Planned to add support: -All Valve titles. -Minecraft. +_ diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 60fee65..bc2dc85 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -3,6 +3,7 @@ | Name | Documentation reference | Notes | |----------------|---------------------------------------------------------------------------|----------------------------------------| | Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | +| Minecraft | [List Server Protocol](https://wiki.vg/Server_List_Ping) | Bedrock not yet supported. | ## Planned to add support: -Minecraft protocol +_ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 015374d..8a69de3 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,7 @@ use std::env; -use gamedig::{aliens, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, tf2, ts}; +use gamedig::{aliens, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, tf2, ts}; +use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -41,6 +42,11 @@ fn main() -> GDResult<()> { "ts" => println!("{:?}", ts::query(ip, port)?), "cscz" => println!("{:?}", cscz::query(ip, port)?), "dod" => println!("{:?}", dod::query(ip, port)?), + "mc" => println!("{:?}", mc::query(ip, port)?), + "mc_java" => println!("{:?}", mc::query_specific(Server::Java, ip, port)?), + "mc_legacy_v1_4" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port)?), + "mc_legacy_v1_6" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port)?), + "mc_legacy_vb1_8" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port)?), "_src" => println!("{:?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/examples/minecraft.rs b/examples/minecraft.rs new file mode 100644 index 0000000..791f958 --- /dev/null +++ b/examples/minecraft.rs @@ -0,0 +1,10 @@ + +use gamedig::games::mc; + +fn main() { + let response = mc::query("localhost", None); //or Some(25565), None is the default protocol port (which is 25565) + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/errors.rs b/src/errors.rs index 584337e..6ed3bf9 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -32,6 +32,12 @@ pub enum GDError { SocketBind(String), /// Invalid input. InvalidInput(String), + /// Couldn't create a socket connection. + SocketConnect(String), + /// Couldn't parse a json string. + JsonParse(String), + /// Couldn't parse a json string. + AutoQuery(String), } impl fmt::Display for GDError { @@ -48,6 +54,9 @@ impl fmt::Display for GDError { GDError::DnsResolve(details) => write!(f, "DNS Resolve: {details}"), GDError::SocketBind(details) => write!(f, "Socket bind: {details}"), GDError::InvalidInput(details) => write!(f, "Invalid input: {details}"), + GDError::SocketConnect(details) => write!(f, "Socket connect: {details}"), + GDError::JsonParse(details) => write!(f, "Json parse: {details}"), + GDError::AutoQuery(details) => write!(f, "Auto query: {details}"), } } } diff --git a/src/games/mc.rs b/src/games/mc.rs new file mode 100644 index 0000000..125150f --- /dev/null +++ b/src/games/mc.rs @@ -0,0 +1,18 @@ +use crate::GDResult; +use crate::protocols::minecraft; +use crate::protocols::minecraft::{Server, Response}; + +pub fn query(address: &str, port: Option) -> GDResult { + minecraft::query(address, port_or_default(port), None) +} + +pub fn query_specific(mc_type: Server, address: &str, port: Option) -> GDResult { + minecraft::query_specific(mc_type, address, port_or_default(port), None) +} + +fn port_or_default(port: Option) -> u16 { + match port { + None => 25565, + Some(port) => port + } +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 6ea01c2..6bee154 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -33,3 +33,5 @@ pub mod insmic; pub mod cscz; /// Day of Defeat pub mod dod; +/// Minecraft +pub mod mc; diff --git a/src/lib.rs b/src/lib.rs index 4e2fc58..fc429d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,10 +15,13 @@ //! } //! ``` +extern crate core; + pub mod errors; pub mod protocols; pub mod games; mod utils; +mod socket; pub use errors::*; pub use games::*; diff --git a/src/protocols/minecraft/mod.rs b/src/protocols/minecraft/mod.rs new file mode 100644 index 0000000..8e0718c --- /dev/null +++ b/src/protocols/minecraft/mod.rs @@ -0,0 +1,9 @@ + +/// The implementation. +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use types::*; +pub use protocol::*; diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs new file mode 100644 index 0000000..06b87f6 --- /dev/null +++ b/src/protocols/minecraft/protocol/java.rs @@ -0,0 +1,127 @@ +use serde_json::Value; +use crate::{GDError, GDResult}; +use crate::protocols::minecraft::{as_varint, get_string, get_varint, Player, Response, Server}; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, TcpSocket}; + +pub struct Java { + socket: TcpSocket +} + +impl Java { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { + socket + }) + } + + fn send(&mut self, data: Vec) -> GDResult<()> { + self.socket.send(&[as_varint(data.len() as i32), data].concat()) + } + + fn receive(&mut self) -> GDResult> { + let buf = self.socket.receive(None)?; + let mut pos = 0; + + let _packet_length = get_varint(&buf, &mut pos)? as usize; + //this declared 'packet length' from within the packet might be wrong (?), not checking with it... + + Ok(buf[pos..].to_vec()) + } + + fn send_handshake(&mut self) -> GDResult<()> { + self.send([ + //Packet ID (0) + 0x00, + //Protocol Version (-1 to determine version) + 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, + //Server address (can be anything) + 0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67, + //Server port (can be anything) + 0x00, 0x00, + //Next state (1 for status) + 0x01].to_vec())?; + + Ok(()) + } + + fn send_status_request(&mut self) -> GDResult<()> { + self.send([ + //Packet ID (0) + 0x00].to_vec())?; + + Ok(()) + } + + fn send_ping_request(&mut self) -> GDResult<()> { + self.send([ + //Packet ID (1) + 0x01].to_vec())?; + + Ok(()) + } + + fn get_info(&mut self) -> GDResult { + self.send_handshake()?; + self.send_status_request()?; + self.send_ping_request()?; + + let buf = self.receive()?; + let mut pos = 0; + + if get_varint(&buf, &mut pos)? != 0 { //first var int is the packet id + return Err(GDError::PacketBad("Bad receive packet id.".to_string())); + } + + let json_response = get_string(&buf, &mut pos)?; + let value_response: Value = serde_json::from_str(&json_response) + .map_err(|e| GDError::JsonParse(e.to_string()))?; + + let version_name = value_response["version"]["name"].as_str() + .ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string(); + let version_protocol = value_response["version"]["protocol"].as_i64() + .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as i32; + + let max_players = value_response["players"]["max"].as_u64() + .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as u32; + let online_players = value_response["players"]["online"].as_u64() + .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as u32; + let sample_players: Option> = match value_response["players"]["sample"].is_null() { + true => None, + false => Some({ + let players_values = value_response["players"]["sample"].as_array() + .ok_or(GDError::PacketBad("Couldn't get expected array.".to_string()))?; + + let mut players = Vec::with_capacity(players_values.len()); + for player in players_values { + players.push(Player { + name: player["name"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string(), + id: player["id"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string() + }) + } + + players + }) + }; + + Ok(Response { + version_name, + version_protocol, + max_players, + online_players, + sample_players, + description: value_response["description"].to_string(), + favicon: value_response["favicon"].as_str().map(str::to_string), + previews_chat: value_response["previewsChat"].as_bool(), + enforces_secure_chat: value_response["enforcesSecureChat"].as_bool(), + server_type: Server::Java + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Java::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs new file mode 100644 index 0000000..abfac32 --- /dev/null +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -0,0 +1,71 @@ + +use crate::{GDError, GDResult}; +use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, TcpSocket}; +use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; + +pub struct LegacyBV1_8 { + socket: TcpSocket +} + +impl LegacyBV1_8 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { + socket + }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { + self.socket.send(&[0xFE]) + } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let buf = self.socket.receive(None)?; + let mut pos = 0; + + if get_u8(&buf, &mut pos)? != 0xFF { + return Err(GDError::PacketBad("Expected 0xFF".to_string())); + } + + let length = get_u16_be(&buf, &mut pos)? * 2; + if buf.len() != (length + 3) as usize { //+ 3 because of the first byte and the u16 + return Err(GDError::PacketBad("Not right size".to_string())); + } + + let packet_string = get_string_utf16_be(&buf, &mut pos)?; + + let split: Vec<&str> = packet_string.split("§").collect(); + if split.len() != 3 { + return Err(GDError::PacketBad("Not right size".to_string())); + } + + let description = split[0].to_string(); + let online_players = split[1].parse() + .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + let max_players = split[2].parse() + .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + + Ok(Response { + version_name: "Beta 1.8+".to_string(), + version_protocol: -1, + max_players, + online_players, + sample_players: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::VB1_8) + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyBV1_8::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs new file mode 100644 index 0000000..853a24a --- /dev/null +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -0,0 +1,76 @@ + +use crate::{GDError, GDResult}; +use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, TcpSocket}; +use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; + +pub struct LegacyV1_4 { + socket: TcpSocket +} + +impl LegacyV1_4 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { + socket + }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { + self.socket.send(&[0xFE, 0x01]) + } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let buf = self.socket.receive(None)?; + let mut pos = 0; + + if get_u8(&buf, &mut pos)? != 0xFF { + return Err(GDError::PacketBad("Expected 0xFF".to_string())); + } + + let length = get_u16_be(&buf, &mut pos)? * 2; + if buf.len() != (length + 3) as usize { //+ 3 because of the first byte and the u16 + return Err(GDError::PacketBad("Not right size".to_string())); + } + + if LegacyV1_6::is_protocol(&buf, &mut pos)? { + return LegacyV1_6::get_response(&buf, &mut pos); + } + + let packet_string = get_string_utf16_be(&buf, &mut pos)?; + + let split: Vec<&str> = packet_string.split("§").collect(); + if split.len() != 3 { + return Err(GDError::PacketBad("Not right size".to_string())); + } + + let description = split[0].to_string(); + let online_players = split[1].parse() + .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + let max_players = split[2].parse() + .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + + Ok(Response { + version_name: "1.4+".to_string(), + version_protocol: -1, + max_players, + online_players, + sample_players: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::V1_4) + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyV1_4::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs new file mode 100644 index 0000000..edbaa73 --- /dev/null +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -0,0 +1,103 @@ +use crate::{GDError, GDResult}; +use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, TcpSocket}; +use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; + +pub struct LegacyV1_6 { + socket: TcpSocket +} + +impl LegacyV1_6 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { + socket + }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { + self.socket.send(&[ + // Packet ID (FE) + 0xfe, + // Ping payload (01) + 0x01, + // Packet identifier for plugin message + 0xfa, + // Length of 'GameDig' string (7) as unsigned short + 0x00, 0x07, + // 'GameDig' string as UTF-16BE + 0x00, 0x47, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67])?; + + Ok(()) + } + + pub fn is_protocol(buf: &[u8], pos: &mut usize) -> GDResult { + let state = buf[*pos..].starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]); + + if state { + *pos += 6; + } + + Ok(state) + } + + pub fn get_response(buf: &[u8], pos: &mut usize) -> GDResult { + let packet_string = get_string_utf16_be(&buf, pos)?; + + let split: Vec<&str> = packet_string.split("\x00").collect(); + if split.len() != 5 { + return Err(GDError::PacketBad("Not right split size".to_string())); + } + + let version_protocol = split[0].parse() + .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + let version_name = split[1].to_string(); + let description = split[2].to_string(); + let max_players = split[3].parse() + .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + let online_players = split[4].parse() + .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + + Ok(Response { + version_name, + version_protocol, + max_players, + online_players, + sample_players: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::V1_6) + }) + } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let buf = self.socket.receive(None)?; + let mut pos = 0; + + if get_u8(&buf, &mut pos)? != 0xFF { + return Err(GDError::PacketBad("Expected 0xFF".to_string())); + } + + let length = get_u16_be(&buf, &mut pos)? * 2; + if buf.len() != (length + 3) as usize { //+ 3 because of the first byte and the u16 + return Err(GDError::PacketBad("Not right size".to_string())); + } + + if !LegacyV1_6::is_protocol(&buf, &mut pos)? { + return Err(GDError::PacketBad("Not good".to_string())); + } + + LegacyV1_6::get_response(&buf, &mut pos) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyV1_6::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs new file mode 100644 index 0000000..5e575a8 --- /dev/null +++ b/src/protocols/minecraft/protocol/mod.rs @@ -0,0 +1,45 @@ +use crate::{GDError, GDResult}; +use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::minecraft::protocol::java::Java; +use crate::protocols::minecraft::protocol::legacy_v1_4::LegacyV1_4; +use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; +use crate::protocols::minecraft::protocol::legacy_bv1_8::LegacyBV1_8; +use crate::protocols::types::TimeoutSettings; + +mod java; +mod legacy_v1_4; +mod legacy_v1_6; +mod legacy_bv1_8; + +/// Queries a Minecraft server. +pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + if let Ok(response) = query_specific(Server::Java, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::V1_6), address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::V1_4), address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::VB1_8), address, port, timeout_settings.clone()) { + return Ok(response); + } + + Err(GDError::AutoQuery("No protocol returned a response.".to_string())) +} + +/// Queries a specific Minecraft Server type. +pub fn query_specific(mc_type: Server, address: &str, port: u16, timeout_settings: Option) -> GDResult { + match mc_type { + Server::Java => Java::query(address, port, timeout_settings), + Server::Legacy(category) => match category { + LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), + LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), + LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings), + } + } +} diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs new file mode 100644 index 0000000..c56bdb9 --- /dev/null +++ b/src/protocols/minecraft/types.rs @@ -0,0 +1,151 @@ + +/* + +This file contains lightly modified versions of the original code. (using only the varint parts) +Code reference: https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs + +MIT License + +Copyright (c) 2021-2022 Jaiden Bernard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +use crate::{GDError, GDResult}; +use crate::utils::buffer::get_u8; + +/// The type of Minecraft Server you want to query +#[derive(Debug)] +pub enum Server { + /// Java Edition + Java, + /// Legacy Java + Legacy(LegacyGroup) +} + +/// Legacy Java (Versions) Groups +#[derive(Debug)] +pub enum LegacyGroup { + /// 1.6 + V1_6, + /// 1.4 - 1.5 + V1_4, + /// Beta 1.8 - 1.3 + VB1_8 +} + +/// Information about a player +#[derive(Debug)] +pub struct Player { + pub name: String, + pub id: String +} + +/// A query response +#[derive(Debug)] +pub struct Response { + /// Version name, example: "1.19.2" + pub version_name: String, + /// Version protocol, example: 760 (for 1.19.2) + pub version_protocol: i32, + /// Number of server capacity + pub max_players: u32, + /// Number of online players + pub online_players: u32, + /// Some online players (can be missing) + pub sample_players: Option>, + /// Server's description or MOTD + pub description: String, + /// The favicon (can be missing) + pub favicon: Option, + /// Tells if the chat preview is enabled (can be missing) + pub previews_chat: Option, + /// Tells if secure chat is enforced (can be missing) + pub enforces_secure_chat: Option, + /// Tell's the server type + pub server_type: Server +} + +pub fn get_varint(buf: &[u8], pos: &mut usize) -> GDResult { + let mut result = 0; + + let msb: u8 = 0b10000000; + let mask: u8 = !msb; + + for i in 0..5 { + let current_byte = get_u8(buf, pos)?; + + result |= ((current_byte & mask) as i32) << (7 * i); + + // The 5th byte is only allowed to have the 4 smallest bits set + if i == 4 && (current_byte & 0xf0 != 0) { + return Err(GDError::PacketBad("VarInt Overflow".to_string())) + } + + if (current_byte & msb) == 0 { + break; + } + } + + Ok(result) +} + +pub fn as_varint(value: i32) -> Vec { + let mut bytes = vec![]; + let mut reading_value = value; + + let msb: u8 = 0b10000000; + let mask: i32 = 0b01111111; + + for _ in 0..5 { + let tmp = (reading_value & mask) as u8; + + reading_value &= !mask; + reading_value = reading_value.rotate_right(7); + + if reading_value != 0 { + bytes.push(tmp | msb); + } else { + bytes.push(tmp); + break; + } + } + + bytes +} + +pub fn get_string(buf: &[u8], pos: &mut usize) -> GDResult { + let length = get_varint(buf, pos)? as usize; + let mut text = vec![0; length]; + + for i in 0..length { + text[i] = get_u8(buf, pos)?; + } + + Ok(String::from_utf8(text) + .map_err(|_| GDError::PacketBad("Minecraft bad String".to_string()))?) +} + +pub fn as_string(value: String) -> Vec { + let mut buf = as_varint(value.len() as i32); + buf.extend(value.as_bytes().to_vec()); + + buf +} diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 0e4e770..08f3ec2 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -4,7 +4,9 @@ //! A protocol will be here if it supports multiple entries, if not, its implementation will be //! in that specific needed place, a protocol can be independently queried. -/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) -pub mod valve; /// General types that are used by all protocols. pub mod types; +/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) +pub mod valve; +/// Reference: [Server List Ping](https://wiki.vg/Server_List_Ping) +pub mod minecraft; diff --git a/src/protocols/types.rs b/src/protocols/types.rs index 4d8be27..c274541 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -2,6 +2,7 @@ use std::time::Duration; use crate::{GDError, GDResult}; /// Timeout settings for socket operations +#[derive(Clone)] pub struct TimeoutSettings { read: Option, write: Option @@ -12,13 +13,13 @@ impl TimeoutSettings { pub fn new(read: Option, write: Option) -> GDResult { if let Some(read_duration) = read { if read_duration == Duration::new(0, 0) { - return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_owned())) + return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_string())) } } if let Some(write_duration) = write { if write_duration == Duration::new(0, 0) { - return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_owned())) + return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_string())) } } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index ac9ac25..780ce81 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,10 +1,10 @@ -use std::net::UdpSocket; use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; use crate::protocols::types::TimeoutSettings; use crate::protocols::valve::{App, ModData, SteamID}; use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip}; -use crate::utils::{buffer, complete_address, u8_lower_upper}; +use crate::socket::{Socket, UdpSocket}; +use crate::utils::{buffer, u8_lower_upper}; #[derive(Debug, Clone)] struct Packet { @@ -42,7 +42,7 @@ impl Packet { fn initial(kind: Request) -> Self { Self { header: 4294967295, //FF FF FF FF - kind: kind as u8, + kind: kind.clone() as u8, payload: match kind { Request::INFO => String::from("Source Engine Query\0").into_bytes(), _ => vec![0xFF, 0xFF, 0xFF, 0xFF] @@ -140,49 +140,29 @@ impl SplitPacket { } struct ValveProtocol { - socket: UdpSocket, - complete_address: String + socket: UdpSocket } static PACKET_SIZE: usize = 1400; impl ValveProtocol { - fn new(address: &str, port: u16, timeout_settings: TimeoutSettings) -> GDResult { - let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| GDError::SocketBind(e.to_string()))?; - - socket.set_read_timeout(timeout_settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new - socket.set_write_timeout(timeout_settings.get_write()).unwrap();//checks if these are 0 and throws an error + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; Ok(Self { - socket, - complete_address: complete_address(address, port)? + socket }) } - fn send(&self, data: &[u8]) -> GDResult<()> { - self.socket.send_to(&data, &self.complete_address).map_err(|e| GDError::PacketSend(e.to_string()))?; - Ok(()) - } - - fn receive_raw(&self, buffer_size: usize) -> GDResult> { - let mut buf: Vec = vec![0; buffer_size]; - let (amt, _) = self.socket.recv_from(&mut buf.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?; - - if amt < 6 { - return Err(GDError::PacketUnderflow("Any Valve Protocol response can't be under 6 bytes long.".to_string())); - } - - Ok(buf[..amt].to_vec()) - } - - fn receive(&self, app: &App, protocol: u8, buffer_size: usize) -> GDResult { - let mut buf = self.receive_raw(buffer_size)?; + fn receive(&mut self, app: &App, protocol: u8, buffer_size: usize) -> GDResult { + let mut buf = self.socket.receive(Some(buffer_size))?; if buf[0] == 0xFE { //the packet is split let mut main_packet = SplitPacket::new(&app, protocol, &buf)?; for _ in 1..main_packet.total { - buf = self.receive_raw(buffer_size)?; + buf = self.socket.receive(Some(buffer_size))?; let chunk_packet = SplitPacket::new(&app, protocol, &buf)?; main_packet.payload.extend(chunk_packet.payload); } @@ -195,10 +175,10 @@ impl ValveProtocol { } /// Ask for a specific request only. - fn get_request_data(&self, app: &App, protocol: u8, kind: Request) -> GDResult> { + fn get_request_data(&mut self, app: &App, protocol: u8, kind: Request) -> GDResult> { let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); - self.send(&request_initial_packet)?; + self.socket.send(&request_initial_packet)?; let packet = self.receive(app, protocol, PACKET_SIZE)?; if packet.kind != 0x41 { //'A' @@ -208,7 +188,7 @@ impl ValveProtocol { let challenge = packet.payload; let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); - self.send(&challenge_packet)?; + self.socket.send(&challenge_packet)?; Ok(self.receive(app, protocol, PACKET_SIZE)?.payload) } @@ -216,11 +196,11 @@ impl ValveProtocol { let mut pos = 0; buffer::get_u8(&buf, &mut pos)?; //get the header (useless info) - buffer::get_string(&buf, &mut pos)?; //get the server address (useless info) - let name = buffer::get_string(&buf, &mut pos)?; - let map = buffer::get_string(&buf, &mut pos)?; - let folder = buffer::get_string(&buf, &mut pos)?; - let game = buffer::get_string(&buf, &mut pos)?; + buffer::get_string_utf8_le(&buf, &mut pos)?; //get the server address (useless info) + let name = buffer::get_string_utf8_le(&buf, &mut pos)?; + let map = buffer::get_string_utf8_le(&buf, &mut pos)?; + let folder = buffer::get_string_utf8_le(&buf, &mut pos)?; + let game = buffer::get_string_utf8_le(&buf, &mut pos)?; let players = buffer::get_u8(&buf, &mut pos)?; let max_players = buffer::get_u8(&buf, &mut pos)?; let protocol = buffer::get_u8(&buf, &mut pos)?; @@ -240,8 +220,8 @@ impl ValveProtocol { let mod_data = match is_mod { false => None, true => Some(ModData { - link: buffer::get_string(&buf, &mut pos)?, - download_link: buffer::get_string(&buf, &mut pos)?, + link: buffer::get_string_utf8_le(&buf, &mut pos)?, + download_link: buffer::get_string_utf8_le(&buf, &mut pos)?, version: buffer::get_u32_le(&buf, &mut pos)?, size: buffer::get_u32_le(&buf, &mut pos)?, multiplayer_only: buffer::get_u8(&buf, &mut pos)? == 1, @@ -274,7 +254,7 @@ impl ValveProtocol { } /// Get the server information's. - fn get_server_info(&self, app: &App) -> GDResult { + fn get_server_info(&mut self, app: &App) -> GDResult { let buf = self.get_request_data(&app, 0, Request::INFO)?; if let App::GoldSrc(force) = app { if *force { @@ -285,10 +265,10 @@ impl ValveProtocol { let mut pos = 0; let protocol = buffer::get_u8(&buf, &mut pos)?; - let name = buffer::get_string(&buf, &mut pos)?; - let map = buffer::get_string(&buf, &mut pos)?; - let folder = buffer::get_string(&buf, &mut pos)?; - let game = buffer::get_string(&buf, &mut pos)?; + let name = buffer::get_string_utf8_le(&buf, &mut pos)?; + let map = buffer::get_string_utf8_le(&buf, &mut pos)?; + let folder = buffer::get_string_utf8_le(&buf, &mut pos)?; + let game = buffer::get_string_utf8_le(&buf, &mut pos)?; let mut appid = buffer::get_u16_le(&buf, &mut pos)? as u32; let players = buffer::get_u8(&buf, &mut pos)?; let max_players = buffer::get_u8(&buf, &mut pos)?; @@ -315,7 +295,7 @@ impl ValveProtocol { duration: buffer::get_u8(&buf, &mut pos)? }) }; - let version = buffer::get_string(&buf, &mut pos)?; + let version = buffer::get_string_utf8_le(&buf, &mut pos)?; let extra_data = match buffer::get_u8(&buf, &mut pos) { Err(_) => None, Ok(value) => Some(ExtraData { @@ -333,11 +313,11 @@ impl ValveProtocol { }, tv_name: match (value & 0x40) > 0 { false => None, - true => Some(buffer::get_string(&buf, &mut pos)?) + true => Some(buffer::get_string_utf8_le(&buf, &mut pos)?) }, keywords: match (value & 0x20) > 0 { false => None, - true => Some(buffer::get_string(&buf, &mut pos)?) + true => Some(buffer::get_string_utf8_le(&buf, &mut pos)?) }, game_id: match (value & 0x01) > 0 { false => None, @@ -374,17 +354,17 @@ impl ValveProtocol { } /// Get the server player's. - fn get_server_players(&self, app: &App, protocol: u8) -> GDResult> { + fn get_server_players(&mut self, app: &App, protocol: u8) -> GDResult> { let buf = self.get_request_data(&app, protocol, Request::PLAYERS)?; let mut pos = 0; - let count = buffer::get_u8(&buf, &mut pos)?; - let mut players: Vec = Vec::new(); + let count = buffer::get_u8(&buf, &mut pos)? as usize; + let mut players: Vec = Vec::with_capacity(count); for _ in 0..count { pos += 1; //skip the index byte players.push(ServerPlayer { - name: buffer::get_string(&buf, &mut pos)?, + name: buffer::get_string_utf8_le(&buf, &mut pos)?, score: buffer::get_u32_le(&buf, &mut pos)?, duration: buffer::get_f32_le(&buf, &mut pos)?, deaths: match *app == SteamID::TS.as_app() { @@ -402,7 +382,7 @@ impl ValveProtocol { } /// Get the server rules's. - fn get_server_rules(&self, app: &App, protocol: u8) -> GDResult>> { + fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult>> { if *app == SteamID::CSGO.as_app() { //cause csgo wont respond to this since feb 21 2014 update return Ok(None); } @@ -410,13 +390,13 @@ impl ValveProtocol { let buf = self.get_request_data(&app, protocol, Request::RULES)?; let mut pos = 0; - let count = buffer::get_u16_le(&buf, &mut pos)?; - let mut rules: Vec = Vec::new(); + let count = buffer::get_u16_le(&buf, &mut pos)? as usize; + let mut rules: Vec = Vec::with_capacity(count); for _ in 0..count { rules.push(ServerRule { - name: buffer::get_string(&buf, &mut pos)?, - value: buffer::get_string(&buf, &mut pos)? + name: buffer::get_string_utf8_le(&buf, &mut pos)?, + value: buffer::get_string_utf8_le(&buf, &mut pos)? }) } @@ -428,12 +408,11 @@ impl ValveProtocol { /// Providing None to the settings results in using the default values for them (GatherSettings::[default](GatheringSettings::default), TimeoutSettings::[default](TimeoutSettings::default)). pub fn query(address: &str, port: u16, app: App, gather_settings: Option, timeout_settings: Option) -> GDResult { let response_gather_settings = gather_settings.unwrap_or(GatheringSettings::default()); - let response_timeout_settings = timeout_settings.unwrap_or(TimeoutSettings::default()); - get_response(address, port, app, response_gather_settings, response_timeout_settings) + get_response(address, port, app, response_gather_settings, timeout_settings) } -fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSettings, timeout_settings: TimeoutSettings) -> GDResult { - let client = ValveProtocol::new(address, port, timeout_settings)?; +fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSettings, timeout_settings: Option) -> GDResult { + let mut client = ValveProtocol::new(address, port, timeout_settings)?; let info = client.get_server_info(&app)?; let protocol = info.protocol; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 6767e27..158f3e6 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,5 +1,3 @@ -use std::time::Duration; -use crate::{GDError, GDResult}; /// The type of the server. #[derive(Debug)] diff --git a/src/socket.rs b/src/socket.rs new file mode 100644 index 0000000..ae25a44 --- /dev/null +++ b/src/socket.rs @@ -0,0 +1,88 @@ +use std::io::{Read, Write}; +use std::net; +use crate::{GDError, GDResult}; +use crate::protocols::types::TimeoutSettings; +use crate::utils::complete_address; + +static DEFAULT_PACKET_SIZE: usize = 1024; + +pub trait Socket { + fn new(address: &str, port: u16) -> GDResult where Self: Sized; + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()>; + + fn send(&mut self, data: &[u8]) -> GDResult<()>; + fn receive(&mut self, size: Option) -> GDResult>; +} + +pub struct TcpSocket { + socket: net::TcpStream +} + +impl Socket for TcpSocket { + fn new(address: &str, port: u16) -> GDResult { + let complete_address = complete_address(address, port)?; + let socket = net::TcpStream::connect(complete_address).map_err(|e| GDError::SocketConnect(e.to_string()))?; + + Ok(Self { + socket + }) + } + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { + let settings = timeout_settings.unwrap_or(TimeoutSettings::default()); + self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new + self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error + + Ok(()) + } + + fn send(&mut self, data: &[u8]) -> GDResult<()> { + self.socket.write(&data).map_err(|e| GDError::PacketSend(e.to_string()))?; + Ok(()) + } + + fn receive(&mut self, size: Option) -> GDResult> { + let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); + self.socket.read_to_end(&mut buf).map_err(|e| GDError::PacketReceive(e.to_string()))?; + + Ok(buf) + } +} + +pub struct UdpSocket { + socket: net::UdpSocket, + complete_address: String +} + +impl Socket for UdpSocket { + fn new(address: &str, port: u16) -> GDResult { + let complete_address = complete_address(address, port)?; + let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|e| GDError::SocketBind(e.to_string()))?; + + Ok(Self { + socket, + complete_address + }) + } + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { + let settings = timeout_settings.unwrap_or(TimeoutSettings::default()); + self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new + self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error + + Ok(()) + } + + fn send(&mut self, data: &[u8]) -> GDResult<()> { + self.socket.send_to(&data, &self.complete_address).map_err(|e| GDError::PacketSend(e.to_string()))?; + Ok(()) + } + + fn receive(&mut self, size: Option) -> GDResult> { + let mut buf: Vec = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; + let (number_of_bytes_received, _) = self.socket.recv_from(&mut buf).map_err(|e| GDError::PacketReceive(e.to_string()))?; + + Ok(buf[..number_of_bytes_received].to_vec()) + } +} diff --git a/src/utils.rs b/src/utils.rs index 6ca978b..a5c9772 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -43,6 +43,16 @@ pub mod buffer { Ok(value) } + pub fn get_u16_be(buf: &[u8], pos: &mut usize) -> GDResult { + if buf.len() <= *pos + 1 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + } + + let value = u16::from_be_bytes([buf[*pos], buf[*pos + 1]]); + *pos += 2; + Ok(value) + } + pub fn get_u32_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 3 { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); @@ -73,10 +83,25 @@ pub mod buffer { Ok(value) } - pub fn get_string(buf: &[u8], pos: &mut usize) -> GDResult { + pub fn get_string_utf8_le(buf: &[u8], pos: &mut usize) -> GDResult { let sub_buf = &buf[*pos..]; - let first_null_position = sub_buf.iter().position(|&x| x == 0).ok_or(GDError::PacketBad("Unexpectedly formatted packet.".to_string()))?; - let value = std::str::from_utf8(&sub_buf[..first_null_position]).unwrap().to_string(); + let first_null_position = sub_buf.iter().position(|&x| x == 0) + .ok_or(GDError::PacketBad("Unexpectedly formatted packet.".to_string()))?; + let value = std::str::from_utf8(&sub_buf[..first_null_position]) + .map_err(|_| GDError::PacketBad("Badly formatted string.".to_string()))?.to_string(); + + *pos += value.len() + 1; + Ok(value) + } + + pub fn get_string_utf16_be(buf: &[u8], pos: &mut usize) -> GDResult { + let sub_buf = &buf[*pos..]; + let paired_buf: Vec = sub_buf.chunks_exact(2) + .into_iter().map(|a| u16::from_be_bytes([a[0], a[1]])).collect(); + + let value = String::from_utf16(&paired_buf) + .map_err(|_| GDError::PacketBad("Badly formatted string.".to_string()))?.to_string(); + *pos += value.len() + 1; Ok(value) } @@ -143,12 +168,12 @@ mod tests { } #[test] - fn get_string_test() { + fn get_string_utf8_le_test() { let data = [72, 101, 108, 108, 111, 0, 72]; let mut pos = 0; - assert_eq!(buffer::get_string(&data, &mut pos).unwrap(), "Hello"); + assert_eq!(buffer::get_string_utf8_le(&data, &mut pos).unwrap(), "Hello"); assert_eq!(pos, 6); - assert!(buffer::get_string(&data, &mut pos).is_err()); + assert!(buffer::get_string_utf8_le(&data, &mut pos).is_err()); assert_eq!(pos, 6); } } From b988b51cff192cab0ef6caeea55e060db808d6bd Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 24 Nov 2022 22:58:34 +0200 Subject: [PATCH 057/597] Some reordering --- examples/master_querant.rs | 2 +- src/lib.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 8a69de3..a050a23 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -44,9 +44,9 @@ fn main() -> GDResult<()> { "dod" => println!("{:?}", dod::query(ip, port)?), "mc" => println!("{:?}", mc::query(ip, port)?), "mc_java" => println!("{:?}", mc::query_specific(Server::Java, ip, port)?), + "mc_legacy_vb1_8" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port)?), "mc_legacy_v1_4" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port)?), "mc_legacy_v1_6" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port)?), - "mc_legacy_vb1_8" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port)?), "_src" => println!("{:?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/lib.rs b/src/lib.rs index fc429d0..f2590ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,6 @@ //! } //! ``` -extern crate core; - pub mod errors; pub mod protocols; pub mod games; From 304b8340d2fa4f29bebd88ba5f9245b2ee11c01b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 24 Nov 2022 23:38:51 +0200 Subject: [PATCH 058/597] Better, faster and stronger errors. --- src/errors.rs | 31 ++++++++-------- src/protocols/minecraft/protocol/java.rs | 18 +++++----- .../minecraft/protocol/legacy_bv1_8.rs | 15 ++++---- .../minecraft/protocol/legacy_v1_4.rs | 15 ++++---- .../minecraft/protocol/legacy_v1_6.rs | 19 +++++----- src/protocols/valve/protocol.rs | 10 +++--- src/utils.rs | 36 ++++++++++++------- 7 files changed, 75 insertions(+), 69 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 6ed3bf9..23711a9 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -11,33 +11,35 @@ pub type GDResult = Result; #[derive(Debug, Clone)] pub enum GDError { /// The received packet was bigger than the buffer size. - PacketOverflow(String), + PacketOverflow(&'static str), /// The received packet was shorter than the expected one. - PacketUnderflow(String), + PacketUnderflow(&'static str), /// The received packet was badly formatted. - PacketBad(String), + PacketBad(&'static str), /// Couldn't send the packet. - PacketSend(String), + PacketSend(&'static str), /// Couldn't send the receive. - PacketReceive(String), + PacketReceive(&'static str), /// Couldn't decompress data. - Decompress(String), + Decompress(&'static str), /// Unknown cast while translating a value to an enum. UnknownEnumCast, /// The server queried is not from the queried game. - BadGame(String), + BadGame(&'static str), /// Problems occurred while dns resolving. - DnsResolve(String), + DnsResolve(&'static str), /// Couldn't bind a socket. - SocketBind(String), + SocketBind(&'static str), /// Invalid input. - InvalidInput(String), + InvalidInput(&'static str), /// Couldn't create a socket connection. - SocketConnect(String), + SocketConnect(&'static str), /// Couldn't parse a json string. - JsonParse(String), - /// Couldn't parse a json string. - AutoQuery(String), + JsonParse(&'static str), + /// Couldn't automatically query. + AutoQuery(&'static str), + /// A protocol-defined expected format was not met. + ProtocolRule(&'static str), } impl fmt::Display for GDError { @@ -57,6 +59,7 @@ impl fmt::Display for GDError { GDError::SocketConnect(details) => write!(f, "Socket connect: {details}"), GDError::JsonParse(details) => write!(f, "Json parse: {details}"), GDError::AutoQuery(details) => write!(f, "Auto query: {details}"), + GDError::ProtocolRule(details) => write!(f, "Protocol rule: {details}"), } } } diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index 06b87f6..3e491c1 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -73,33 +73,33 @@ impl Java { let mut pos = 0; if get_varint(&buf, &mut pos)? != 0 { //first var int is the packet id - return Err(GDError::PacketBad("Bad receive packet id.".to_string())); + return Err(GDError::PacketBad("Bad receive packet id.")); } let json_response = get_string(&buf, &mut pos)?; let value_response: Value = serde_json::from_str(&json_response) - .map_err(|e| GDError::JsonParse(e.to_string()))?; + .map_err(|e| GDError::JsonParse(e.to_string().as_str()))?; let version_name = value_response["version"]["name"].as_str() - .ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string(); + .ok_or(GDError::PacketBad("Couldn't get expected string."))?.to_string(); let version_protocol = value_response["version"]["protocol"].as_i64() - .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as i32; + .ok_or(GDError::PacketBad("Couldn't get expected number."))? as i32; let max_players = value_response["players"]["max"].as_u64() - .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as u32; + .ok_or(GDError::PacketBad("Couldn't get expected number."))? as u32; let online_players = value_response["players"]["online"].as_u64() - .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as u32; + .ok_or(GDError::PacketBad("Couldn't get expected number."))? as u32; let sample_players: Option> = match value_response["players"]["sample"].is_null() { true => None, false => Some({ let players_values = value_response["players"]["sample"].as_array() - .ok_or(GDError::PacketBad("Couldn't get expected array.".to_string()))?; + .ok_or(GDError::PacketBad("Couldn't get expected array."))?; let mut players = Vec::with_capacity(players_values.len()); for player in players_values { players.push(Player { - name: player["name"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string(), - id: player["id"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string() + name: player["name"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string."))?.to_string(), + id: player["id"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string."))?.to_string() }) } diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index abfac32..b935c9a 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -4,6 +4,7 @@ use crate::protocols::minecraft::{LegacyGroup, Response, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; +use crate::utils::error_by_expected_size; pub struct LegacyBV1_8 { socket: TcpSocket @@ -30,26 +31,22 @@ impl LegacyBV1_8 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::PacketBad("Expected 0xFF".to_string())); + return Err(GDError::ProtocolRule("Expected 0xFF at the begin of the packet.")); } let length = get_u16_be(&buf, &mut pos)? * 2; - if buf.len() != (length + 3) as usize { //+ 3 because of the first byte and the u16 - return Err(GDError::PacketBad("Not right size".to_string())); - } + error_by_expected_size((length + 3) as usize, buf.len())?; let packet_string = get_string_utf16_be(&buf, &mut pos)?; let split: Vec<&str> = packet_string.split("§").collect(); - if split.len() != 3 { - return Err(GDError::PacketBad("Not right size".to_string())); - } + error_by_expected_size(3, split.len())?; let description = split[0].to_string(); let online_players = split[1].parse() - .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; let max_players = split[2].parse() - .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; Ok(Response { version_name: "Beta 1.8+".to_string(), diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index 853a24a..29815be 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -5,6 +5,7 @@ use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; +use crate::utils::error_by_expected_size; pub struct LegacyV1_4 { socket: TcpSocket @@ -31,13 +32,11 @@ impl LegacyV1_4 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::PacketBad("Expected 0xFF".to_string())); + return Err(GDError::ProtocolRule("Expected 0xFF at the begin of the packet.")); } let length = get_u16_be(&buf, &mut pos)? * 2; - if buf.len() != (length + 3) as usize { //+ 3 because of the first byte and the u16 - return Err(GDError::PacketBad("Not right size".to_string())); - } + error_by_expected_size((length + 3) as usize, buf.len())?; if LegacyV1_6::is_protocol(&buf, &mut pos)? { return LegacyV1_6::get_response(&buf, &mut pos); @@ -46,15 +45,13 @@ impl LegacyV1_4 { let packet_string = get_string_utf16_be(&buf, &mut pos)?; let split: Vec<&str> = packet_string.split("§").collect(); - if split.len() != 3 { - return Err(GDError::PacketBad("Not right size".to_string())); - } + error_by_expected_size(3, split.len())?; let description = split[0].to_string(); let online_players = split[1].parse() - .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; let max_players = split[2].parse() - .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; Ok(Response { version_name: "1.4+".to_string(), diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index edbaa73..0659261 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -3,6 +3,7 @@ use crate::protocols::minecraft::{LegacyGroup, Response, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; +use crate::utils::error_by_expected_size; pub struct LegacyV1_6 { socket: TcpSocket @@ -48,18 +49,16 @@ impl LegacyV1_6 { let packet_string = get_string_utf16_be(&buf, pos)?; let split: Vec<&str> = packet_string.split("\x00").collect(); - if split.len() != 5 { - return Err(GDError::PacketBad("Not right split size".to_string())); - } + error_by_expected_size(5, split.len())?; let version_protocol = split[0].parse() - .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; let version_name = split[1].to_string(); let description = split[2].to_string(); let max_players = split[3].parse() - .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; let online_players = split[4].parse() - .map_err(|_| GDError::PacketBad("Expected int".to_string()))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; Ok(Response { version_name, @@ -82,16 +81,14 @@ impl LegacyV1_6 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::PacketBad("Expected 0xFF".to_string())); + return Err(GDError::ProtocolRule("Expected a certain byte (0xFF) at the begin of the packet.")); } let length = get_u16_be(&buf, &mut pos)? * 2; - if buf.len() != (length + 3) as usize { //+ 3 because of the first byte and the u16 - return Err(GDError::PacketBad("Not right size".to_string())); - } + error_by_expected_size((length + 3) as usize, buf.len())?; if !LegacyV1_6::is_protocol(&buf, &mut pos)? { - return Err(GDError::PacketBad("Not good".to_string())); + return Err(GDError::ProtocolRule("Expected certain bytes at the beginning of the packet.")); } LegacyV1_6::get_response(&buf, &mut pos) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 780ce81..cbdcac6 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -117,18 +117,18 @@ impl SplitPacket { fn get_payload(&self) -> GDResult> { if self.compressed { let mut decoder = Decoder::new(); - decoder.write(&self.payload).map_err(|e| GDError::Decompress(e.to_string()))?; + decoder.write(&self.payload).map_err(|e| GDError::Decompress(e.to_string().as_str()))?; let decompressed_size = self.decompressed_size.unwrap() as usize; let mut decompressed_payload = Vec::with_capacity(decompressed_size); - decoder.read(&mut decompressed_payload).map_err(|e| GDError::Decompress(e.to_string()))?; + decoder.read(&mut decompressed_payload).map_err(|e| GDError::Decompress(e.to_string().as_str()))?; if decompressed_payload.len() != decompressed_size { - Err(GDError::Decompress("Valve Protocol: The decompressed payload size doesn't match the expected one.".to_string())) + Err(GDError::Decompress("The decompressed payload size doesn't match the expected one.")) } else if crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { - Err(GDError::Decompress("Valve Protocol: The decompressed crc32 hash does not match the expected one.".to_string())) + Err(GDError::Decompress("The decompressed crc32 hash does not match the expected one.")) } else { Ok(decompressed_payload) @@ -420,7 +420,7 @@ fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSe if let App::Source(x) = &app { if let Some(appid) = x { if *appid != info.appid { - return Err(GDError::BadGame(format!("Expected {}, found {} instead!", *appid, info.appid))); + return Err(GDError::BadGame(format!("Expected {}, found {} instead!", *appid, info.appid).as_str())); } } } diff --git a/src/utils.rs b/src/utils.rs index a5c9772..6b334a9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,12 +4,24 @@ use crate::{GDResult, GDError}; fn resolve_dns(address: &str) -> GDResult { let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()) - .map_err(|e| GDError::DnsResolve(e.to_string()))?; + .map_err(|e| GDError::DnsResolve(e.to_string().as_str()))?; let response = resolver.lookup_ip(address) - .map_err(|e| GDError::DnsResolve(e.to_string()))?; + .map_err(|e| GDError::DnsResolve(e.to_string().as_str()))?; - Ok(response.iter().next().ok_or(GDError::DnsResolve("Couldn't resolve the DNS address.".to_string()))?.to_string()) + Ok(response.iter().next().ok_or(GDError::DnsResolve("Couldn't resolve the DNS address."))?.to_string()) +} + +pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { + if size < expected { + Err(GDError::PacketUnderflow("Unexpectedly short packet.")) + } + else if size > expected { + Err(GDError::PacketOverflow("Unexpectedly long packet.")) + } + else { + Ok(()) + } } pub fn complete_address(address: &str, port: u16) -> GDResult { @@ -25,7 +37,7 @@ pub mod buffer { pub fn get_u8(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); } let value = buf[*pos]; @@ -35,7 +47,7 @@ pub mod buffer { pub fn get_u16_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 1 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); } let value = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]); @@ -45,7 +57,7 @@ pub mod buffer { pub fn get_u16_be(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 1 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); } let value = u16::from_be_bytes([buf[*pos], buf[*pos + 1]]); @@ -55,7 +67,7 @@ pub mod buffer { pub fn get_u32_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 3 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); } let value = u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); @@ -65,7 +77,7 @@ pub mod buffer { pub fn get_f32_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 3 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); } let value = f32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); @@ -75,7 +87,7 @@ pub mod buffer { pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 7 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); } let value = u64::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3], buf[*pos + 4], buf[*pos + 5], buf[*pos + 6], buf[*pos + 7]]); @@ -86,9 +98,9 @@ pub mod buffer { pub fn get_string_utf8_le(buf: &[u8], pos: &mut usize) -> GDResult { let sub_buf = &buf[*pos..]; let first_null_position = sub_buf.iter().position(|&x| x == 0) - .ok_or(GDError::PacketBad("Unexpectedly formatted packet.".to_string()))?; + .ok_or(GDError::PacketBad("Unexpectedly formatted packet."))?; let value = std::str::from_utf8(&sub_buf[..first_null_position]) - .map_err(|_| GDError::PacketBad("Badly formatted string.".to_string()))?.to_string(); + .map_err(|_| GDError::PacketBad("Badly formatted string."))?.to_string(); *pos += value.len() + 1; Ok(value) @@ -100,7 +112,7 @@ pub mod buffer { .into_iter().map(|a| u16::from_be_bytes([a[0], a[1]])).collect(); let value = String::from_utf16(&paired_buf) - .map_err(|_| GDError::PacketBad("Badly formatted string.".to_string()))?.to_string(); + .map_err(|_| GDError::PacketBad("Badly formatted string."))?.to_string(); *pos += value.len() + 1; Ok(value) From dc0926bab7ca742bd1ab6d7d2f9183ff4f60a9d3 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 24 Nov 2022 23:41:23 +0200 Subject: [PATCH 059/597] Renamed ProtocolRule to ProtocolFormat --- src/errors.rs | 4 ++-- src/protocols/minecraft/protocol/legacy_bv1_8.rs | 2 +- src/protocols/minecraft/protocol/legacy_v1_4.rs | 2 +- src/protocols/minecraft/protocol/legacy_v1_6.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 23711a9..04fd2f5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -39,7 +39,7 @@ pub enum GDError { /// Couldn't automatically query. AutoQuery(&'static str), /// A protocol-defined expected format was not met. - ProtocolRule(&'static str), + ProtocolFormat(&'static str), } impl fmt::Display for GDError { @@ -59,7 +59,7 @@ impl fmt::Display for GDError { GDError::SocketConnect(details) => write!(f, "Socket connect: {details}"), GDError::JsonParse(details) => write!(f, "Json parse: {details}"), GDError::AutoQuery(details) => write!(f, "Auto query: {details}"), - GDError::ProtocolRule(details) => write!(f, "Protocol rule: {details}"), + GDError::ProtocolFormat(details) => write!(f, "Protocol rule: {details}"), } } } diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index b935c9a..aea38d9 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -31,7 +31,7 @@ impl LegacyBV1_8 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::ProtocolRule("Expected 0xFF at the begin of the packet.")); + return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.")); } let length = get_u16_be(&buf, &mut pos)? * 2; diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index 29815be..2fc3cd8 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -32,7 +32,7 @@ impl LegacyV1_4 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::ProtocolRule("Expected 0xFF at the begin of the packet.")); + return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.")); } let length = get_u16_be(&buf, &mut pos)? * 2; diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 0659261..4f203a7 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -81,14 +81,14 @@ impl LegacyV1_6 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::ProtocolRule("Expected a certain byte (0xFF) at the begin of the packet.")); + return Err(GDError::ProtocolFormat("Expected a certain byte (0xFF) at the begin of the packet.")); } let length = get_u16_be(&buf, &mut pos)? * 2; error_by_expected_size((length + 3) as usize, buf.len())?; if !LegacyV1_6::is_protocol(&buf, &mut pos)? { - return Err(GDError::ProtocolRule("Expected certain bytes at the beginning of the packet.")); + return Err(GDError::ProtocolFormat("Expected certain bytes at the beginning of the packet.")); } LegacyV1_6::get_response(&buf, &mut pos) From 7b44c5f7eb66076db247648561aae2b5b5ae40a9 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 18:42:10 +0200 Subject: [PATCH 060/597] Reverter errors from taking a &'static str to String --- src/errors.rs | 30 +++++++++---------- src/protocols/minecraft/protocol/java.rs | 18 +++++------ .../minecraft/protocol/legacy_bv1_8.rs | 6 ++-- .../minecraft/protocol/legacy_v1_4.rs | 6 ++-- .../minecraft/protocol/legacy_v1_6.rs | 10 +++---- src/protocols/minecraft/protocol/mod.rs | 2 +- src/protocols/minecraft/types.rs | 4 +-- src/protocols/types.rs | 4 +-- src/protocols/valve/protocol.rs | 10 +++---- src/utils.rs | 28 ++++++++--------- 10 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 04fd2f5..099bb0c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -11,35 +11,35 @@ pub type GDResult = Result; #[derive(Debug, Clone)] pub enum GDError { /// The received packet was bigger than the buffer size. - PacketOverflow(&'static str), + PacketOverflow(String), /// The received packet was shorter than the expected one. - PacketUnderflow(&'static str), + PacketUnderflow(String), /// The received packet was badly formatted. - PacketBad(&'static str), + PacketBad(String), /// Couldn't send the packet. - PacketSend(&'static str), + PacketSend(String), /// Couldn't send the receive. - PacketReceive(&'static str), + PacketReceive(String), /// Couldn't decompress data. - Decompress(&'static str), + Decompress(String), /// Unknown cast while translating a value to an enum. UnknownEnumCast, /// The server queried is not from the queried game. - BadGame(&'static str), + BadGame(String), /// Problems occurred while dns resolving. - DnsResolve(&'static str), + DnsResolve(String), /// Couldn't bind a socket. - SocketBind(&'static str), + SocketBind(String), /// Invalid input. - InvalidInput(&'static str), + InvalidInput(String), /// Couldn't create a socket connection. - SocketConnect(&'static str), + SocketConnect(String), /// Couldn't parse a json string. - JsonParse(&'static str), + JsonParse(String), /// Couldn't automatically query. - AutoQuery(&'static str), + AutoQuery, /// A protocol-defined expected format was not met. - ProtocolFormat(&'static str), + ProtocolFormat(String), } impl fmt::Display for GDError { @@ -58,7 +58,7 @@ impl fmt::Display for GDError { GDError::InvalidInput(details) => write!(f, "Invalid input: {details}"), GDError::SocketConnect(details) => write!(f, "Socket connect: {details}"), GDError::JsonParse(details) => write!(f, "Json parse: {details}"), - GDError::AutoQuery(details) => write!(f, "Auto query: {details}"), + GDError::AutoQuery => write!(f, "Auto query failed."), GDError::ProtocolFormat(details) => write!(f, "Protocol rule: {details}"), } } diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index 3e491c1..06b87f6 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -73,33 +73,33 @@ impl Java { let mut pos = 0; if get_varint(&buf, &mut pos)? != 0 { //first var int is the packet id - return Err(GDError::PacketBad("Bad receive packet id.")); + return Err(GDError::PacketBad("Bad receive packet id.".to_string())); } let json_response = get_string(&buf, &mut pos)?; let value_response: Value = serde_json::from_str(&json_response) - .map_err(|e| GDError::JsonParse(e.to_string().as_str()))?; + .map_err(|e| GDError::JsonParse(e.to_string()))?; let version_name = value_response["version"]["name"].as_str() - .ok_or(GDError::PacketBad("Couldn't get expected string."))?.to_string(); + .ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string(); let version_protocol = value_response["version"]["protocol"].as_i64() - .ok_or(GDError::PacketBad("Couldn't get expected number."))? as i32; + .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as i32; let max_players = value_response["players"]["max"].as_u64() - .ok_or(GDError::PacketBad("Couldn't get expected number."))? as u32; + .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as u32; let online_players = value_response["players"]["online"].as_u64() - .ok_or(GDError::PacketBad("Couldn't get expected number."))? as u32; + .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as u32; let sample_players: Option> = match value_response["players"]["sample"].is_null() { true => None, false => Some({ let players_values = value_response["players"]["sample"].as_array() - .ok_or(GDError::PacketBad("Couldn't get expected array."))?; + .ok_or(GDError::PacketBad("Couldn't get expected array.".to_string()))?; let mut players = Vec::with_capacity(players_values.len()); for player in players_values { players.push(Player { - name: player["name"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string."))?.to_string(), - id: player["id"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string."))?.to_string() + name: player["name"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string(), + id: player["id"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string() }) } diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index aea38d9..430f38e 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -31,7 +31,7 @@ impl LegacyBV1_8 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.")); + return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.".to_string())); } let length = get_u16_be(&buf, &mut pos)? * 2; @@ -44,9 +44,9 @@ impl LegacyBV1_8 { let description = split[0].to_string(); let online_players = split[1].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; let max_players = split[2].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; Ok(Response { version_name: "Beta 1.8+".to_string(), diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index 2fc3cd8..4c2ab08 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -32,7 +32,7 @@ impl LegacyV1_4 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.")); + return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.".to_string())); } let length = get_u16_be(&buf, &mut pos)? * 2; @@ -49,9 +49,9 @@ impl LegacyV1_4 { let description = split[0].to_string(); let online_players = split[1].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; let max_players = split[2].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; Ok(Response { version_name: "1.4+".to_string(), diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 4f203a7..0034a39 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -52,13 +52,13 @@ impl LegacyV1_6 { error_by_expected_size(5, split.len())?; let version_protocol = split[0].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; let version_name = split[1].to_string(); let description = split[2].to_string(); let max_players = split[3].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; let online_players = split[4].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int."))?; + .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; Ok(Response { version_name, @@ -81,14 +81,14 @@ impl LegacyV1_6 { let mut pos = 0; if get_u8(&buf, &mut pos)? != 0xFF { - return Err(GDError::ProtocolFormat("Expected a certain byte (0xFF) at the begin of the packet.")); + return Err(GDError::ProtocolFormat("Expected a certain byte (0xFF) at the begin of the packet.".to_string())); } let length = get_u16_be(&buf, &mut pos)? * 2; error_by_expected_size((length + 3) as usize, buf.len())?; if !LegacyV1_6::is_protocol(&buf, &mut pos)? { - return Err(GDError::ProtocolFormat("Expected certain bytes at the beginning of the packet.")); + return Err(GDError::ProtocolFormat("Expected certain bytes at the beginning of the packet.".to_string())); } LegacyV1_6::get_response(&buf, &mut pos) diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 5e575a8..79f539f 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -29,7 +29,7 @@ pub fn query(address: &str, port: u16, timeout_settings: Option return Ok(response); } - Err(GDError::AutoQuery("No protocol returned a response.".to_string())) + Err(GDError::AutoQuery) } /// Queries a specific Minecraft Server type. diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index c56bdb9..60d17b0 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -96,7 +96,7 @@ pub fn get_varint(buf: &[u8], pos: &mut usize) -> GDResult { // The 5th byte is only allowed to have the 4 smallest bits set if i == 4 && (current_byte & 0xf0 != 0) { - return Err(GDError::PacketBad("VarInt Overflow".to_string())) + return Err(GDError::PacketBad("Couldn't parse to VarInt: Overflow.".to_string())) } if (current_byte & msb) == 0 { @@ -140,7 +140,7 @@ pub fn get_string(buf: &[u8], pos: &mut usize) -> GDResult { } Ok(String::from_utf8(text) - .map_err(|_| GDError::PacketBad("Minecraft bad String".to_string()))?) + .map_err(|_| GDError::PacketBad("Couldn't parse to a Minecraft String.".to_string()))?) } pub fn as_string(value: String) -> Vec { diff --git a/src/protocols/types.rs b/src/protocols/types.rs index c274541..bb2719d 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -13,13 +13,13 @@ impl TimeoutSettings { pub fn new(read: Option, write: Option) -> GDResult { if let Some(read_duration) = read { if read_duration == Duration::new(0, 0) { - return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_string())) + return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings.".to_string())) } } if let Some(write_duration) = write { if write_duration == Duration::new(0, 0) { - return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings".to_string())) + return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings.".to_string())) } } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index cbdcac6..a3990fc 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -117,18 +117,18 @@ impl SplitPacket { fn get_payload(&self) -> GDResult> { if self.compressed { let mut decoder = Decoder::new(); - decoder.write(&self.payload).map_err(|e| GDError::Decompress(e.to_string().as_str()))?; + decoder.write(&self.payload).map_err(|e| GDError::Decompress(e.to_string()))?; let decompressed_size = self.decompressed_size.unwrap() as usize; let mut decompressed_payload = Vec::with_capacity(decompressed_size); - decoder.read(&mut decompressed_payload).map_err(|e| GDError::Decompress(e.to_string().as_str()))?; + decoder.read(&mut decompressed_payload).map_err(|e| GDError::Decompress(e.to_string()))?; if decompressed_payload.len() != decompressed_size { - Err(GDError::Decompress("The decompressed payload size doesn't match the expected one.")) + Err(GDError::Decompress("The decompressed payload size doesn't match the expected one.".to_string())) } else if crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { - Err(GDError::Decompress("The decompressed crc32 hash does not match the expected one.")) + Err(GDError::Decompress("The decompressed crc32 hash does not match the expected one.".to_string())) } else { Ok(decompressed_payload) @@ -420,7 +420,7 @@ fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSe if let App::Source(x) = &app { if let Some(appid) = x { if *appid != info.appid { - return Err(GDError::BadGame(format!("Expected {}, found {} instead!", *appid, info.appid).as_str())); + return Err(GDError::BadGame(format!("Expected {}, found {} instead!", *appid, info.appid))); } } } diff --git a/src/utils.rs b/src/utils.rs index 6b334a9..c84b038 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,20 +4,20 @@ use crate::{GDResult, GDError}; fn resolve_dns(address: &str) -> GDResult { let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()) - .map_err(|e| GDError::DnsResolve(e.to_string().as_str()))?; + .map_err(|e| GDError::DnsResolve(e.to_string()))?; let response = resolver.lookup_ip(address) - .map_err(|e| GDError::DnsResolve(e.to_string().as_str()))?; + .map_err(|e| GDError::DnsResolve(e.to_string()))?; - Ok(response.iter().next().ok_or(GDError::DnsResolve("Couldn't resolve the DNS address."))?.to_string()) + Ok(response.iter().next().ok_or(GDError::DnsResolve("Couldn't resolve the DNS address.".to_string()))?.to_string()) } pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { if size < expected { - Err(GDError::PacketUnderflow("Unexpectedly short packet.")) + Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())) } else if size > expected { - Err(GDError::PacketOverflow("Unexpectedly long packet.")) + Err(GDError::PacketOverflow("Unexpectedly long packet.".to_string())) } else { Ok(()) @@ -37,7 +37,7 @@ pub mod buffer { pub fn get_u8(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u8.".to_string())); } let value = buf[*pos]; @@ -47,7 +47,7 @@ pub mod buffer { pub fn get_u16_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 1 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u16.".to_string())); } let value = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]); @@ -57,7 +57,7 @@ pub mod buffer { pub fn get_u16_be(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 1 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u16.".to_string())); } let value = u16::from_be_bytes([buf[*pos], buf[*pos + 1]]); @@ -67,7 +67,7 @@ pub mod buffer { pub fn get_u32_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 3 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u32.".to_string())); } let value = u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); @@ -77,7 +77,7 @@ pub mod buffer { pub fn get_f32_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 3 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an f32.".to_string())); } let value = f32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); @@ -87,7 +87,7 @@ pub mod buffer { pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> GDResult { if buf.len() <= *pos + 7 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet.")); + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u64.".to_string())); } let value = u64::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3], buf[*pos + 4], buf[*pos + 5], buf[*pos + 6], buf[*pos + 7]]); @@ -98,9 +98,9 @@ pub mod buffer { pub fn get_string_utf8_le(buf: &[u8], pos: &mut usize) -> GDResult { let sub_buf = &buf[*pos..]; let first_null_position = sub_buf.iter().position(|&x| x == 0) - .ok_or(GDError::PacketBad("Unexpectedly formatted packet."))?; + .ok_or(GDError::PacketBad("Unexpectedly formatted packet for getting a string.".to_string()))?; let value = std::str::from_utf8(&sub_buf[..first_null_position]) - .map_err(|_| GDError::PacketBad("Badly formatted string."))?.to_string(); + .map_err(|_| GDError::PacketBad("Badly formatted string.".to_string()))?.to_string(); *pos += value.len() + 1; Ok(value) @@ -112,7 +112,7 @@ pub mod buffer { .into_iter().map(|a| u16::from_be_bytes([a[0], a[1]])).collect(); let value = String::from_utf16(&paired_buf) - .map_err(|_| GDError::PacketBad("Badly formatted string."))?.to_string(); + .map_err(|_| GDError::PacketBad("Badly formatted string.".to_string()))?.to_string(); *pos += value.len() + 1; Ok(value) From e36161ce5ac6299514c13ce964e9f511eb00eac3 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 18:59:25 +0200 Subject: [PATCH 061/597] Edited README.md --- README.md | 41 ++++++++++++++++++++++++++++---- examples/master_querant.rs | 48 +++++++++++++++++++------------------- examples/minecraft.rs | 4 ++-- examples/tf2.rs | 4 ++-- 4 files changed, 64 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index a7aa841..b0fb664 100644 --- a/README.md +++ b/README.md @@ -7,23 +7,54 @@ MSRV is `1.58.1` and the code is cross-platform. To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. ## Usage -Just pick a game/service/protocol, provide the ip and the port (can be optional) then query on it. +Just pick a game/service/protocol, provide the ip and the port (can be optional) then query on it. +Team Fortress 2 query example: ```rust use gamedig::games::tf2; fn main() { - let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port + let response = tf2::query("localhost", None); //or Some(27015), None is the default protocol port match response { Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) + Ok(r) => println!("{:#?}", r) } } ``` +Response: +```json5 +{ + protocol: 17, + name: "Team Fortress 2 Dedicated Server!", + map: "ctf_turbine", + game: "tf2", + players: 0, + players_details: [], + max_players: 69, + bots: 0, + server_type: Dedicated, + has_password: false, + vac_secured: true, + version: "7638371", + port: Some(27015), + steam_id: Some(69753253289735296), + tv_port: None, + tv_name: None, + keywords: Some("alltalk,arena,nocrits"), + rules: [ + ServerRule { + name: "mp_autoteambalance", + value: "1", + } + //.... + ] +} +``` + To see more examples, see the [examples](examples) folder. ## Documentation The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). -Curious about the history and what changed between versions? you can see just that in the [CHANGELOG](CHANGELOG.md) file. +Curious about the history and what changed between versions? Check out the [CHANGELOG](CHANGELOG.md) file. ## Contributing -If you want see your favorite game/service being supported here, open an issue, and I'll prioritize it! (or do a pull request if you want to implement it yourself) +If you want see your favorite game/service being supported here, open an issue, and I'll prioritize it (or do a pull request if you want to implement it yourself)! diff --git a/examples/master_querant.rs b/examples/master_querant.rs index a050a23..0992fa1 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -26,30 +26,30 @@ fn main() -> GDResult<()> { }; match args[1].as_str() { - "aliens" => println!("{:?}", aliens::query(ip, port)?), - "asrd" => println!("{:?}", asrd::query(ip, port)?), - "csgo" => println!("{:?}", csgo::query(ip, port)?), - "css" => println!("{:?}", css::query(ip, port)?), - "dods" => println!("{:?}", dods::query(ip, port)?), - "gm" => println!("{:?}", gm::query(ip, port)?), - "hl2dm" => println!("{:?}", hl2dm::query(ip, port)?), - "tf2" => println!("{:?}", tf2::query(ip, port)?), - "insmic" => println!("{:?}", insmic::query(ip, port)?), - "ins" => println!("{:?}", ins::query(ip, port)?), - "inss" => println!("{:?}", inss::query(ip, port)?), - "l4d" => println!("{:?}", l4d::query(ip, port)?), - "l4d2" => println!("{:?}", l4d2::query(ip, port)?), - "ts" => println!("{:?}", ts::query(ip, port)?), - "cscz" => println!("{:?}", cscz::query(ip, port)?), - "dod" => println!("{:?}", dod::query(ip, port)?), - "mc" => println!("{:?}", mc::query(ip, port)?), - "mc_java" => println!("{:?}", mc::query_specific(Server::Java, ip, port)?), - "mc_legacy_vb1_8" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port)?), - "mc_legacy_v1_4" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port)?), - "mc_legacy_v1_6" => println!("{:?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port)?), - "_src" => println!("{:?}", valve::query(ip, 27015, App::Source(None), None, None)?), - "_gld" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), - "_gld_f" => println!("{:?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), + "aliens" => println!("{:#?}", aliens::query(ip, port)?), + "asrd" => println!("{:#?}", asrd::query(ip, port)?), + "csgo" => println!("{:#?}", csgo::query(ip, port)?), + "css" => println!("{:#?}", css::query(ip, port)?), + "dods" => println!("{:#?}", dods::query(ip, port)?), + "gm" => println!("{:#?}", gm::query(ip, port)?), + "hl2dm" => println!("{:#?}", hl2dm::query(ip, port)?), + "tf2" => println!("{:#?}", tf2::query(ip, port)?), + "insmic" => println!("{:#?}", insmic::query(ip, port)?), + "ins" => println!("{:#?}", ins::query(ip, port)?), + "inss" => println!("{:#?}", inss::query(ip, port)?), + "l4d" => println!("{:#?}", l4d::query(ip, port)?), + "l4d2" => println!("{:#?}", l4d2::query(ip, port)?), + "ts" => println!("{:#?}", ts::query(ip, port)?), + "cscz" => println!("{:#?}", cscz::query(ip, port)?), + "dod" => println!("{:#?}", dod::query(ip, port)?), + "mc" => println!("{:#?}", mc::query(ip, port)?), + "mc_java" => println!("{:#?}", mc::query_specific(Server::Java, ip, port)?), + "mc_legacy_vb1_8" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port)?), + "mc_legacy_v1_4" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port)?), + "mc_legacy_v1_6" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port)?), + "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), + "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), + "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/examples/minecraft.rs b/examples/minecraft.rs index 791f958..6b9ec6e 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -2,9 +2,9 @@ use gamedig::games::mc; fn main() { - let response = mc::query("localhost", None); //or Some(25565), None is the default protocol port (which is 25565) + let response = mc::query("cosminperram.com", Some(26062)); //or Some(25565), None is the default protocol port (which is 25565) match response { Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) + Ok(r) => println!("{:#?}", r) } } diff --git a/examples/tf2.rs b/examples/tf2.rs index eb8c70a..e79cb7f 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -2,9 +2,9 @@ use gamedig::games::tf2; fn main() { - let response = tf2::query("cosminperram.com", None); //or Some(27015), None is the default protocol port (which is 27015) + let response = tf2::query("localhost", None); //or Some(27015), None is the default protocol port (which is 27015) match response { Err(error) => println!("Couldn't query, error: {error}"), - Ok(r) => println!("{:?}", r) + Ok(r) => println!("{:#?}", r) } } From 0e1ca4304b89eeb029cc1a14c47415acbb526f51 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 19:00:32 +0200 Subject: [PATCH 062/597] Create rust.yml --- .github/workflows/rust.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..ab271fc --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,21 @@ +name: Rust + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose From 4c7cecb5c37098bb50a7d2ccd650843702a5afab Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 19:25:47 +0200 Subject: [PATCH 063/597] Renamed workflow file and edited README --- .github/workflows/{rust.yml => build_and_test.yml} | 2 +- README.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) rename .github/workflows/{rust.yml => build_and_test.yml} (93%) diff --git a/.github/workflows/rust.yml b/.github/workflows/build_and_test.yml similarity index 93% rename from .github/workflows/rust.yml rename to .github/workflows/build_and_test.yml index ab271fc..411f540 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/build_and_test.yml @@ -1,4 +1,4 @@ -name: Rust +name: Build and Test on: push: diff --git a/README.md b/README.md index b0fb664..0fd708c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # rust-gamedig +![Build and Test](https://github.com/cosminperram/rust-gamedig/actions/workflows/build_and_test.yml/badge.svg) + **rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! MSRV is `1.58.1` and the code is cross-platform. From 462014c8ac8ad9184631d0e6a33a89bedd2fc867 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 19:37:12 +0200 Subject: [PATCH 064/597] Renamed workflow file and edited README --- .github/workflows/{build_and_test.yml => ci.yml} | 0 README.md | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) rename .github/workflows/{build_and_test.yml => ci.yml} (100%) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/workflows/build_and_test.yml rename to .github/workflows/ci.yml diff --git a/README.md b/README.md index 0fd708c..5b337be 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # rust-gamedig -![Build and Test](https://github.com/cosminperram/rust-gamedig/actions/workflows/build_and_test.yml/badge.svg) +[![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) +[![Latest Version](https://img.shields.io/crates/v/gamedig.svg)](https://crates.io/crates/gamedig) +[![License:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md) +![Lines of code](https://img.shields.io/tokei/lines/github/cosminperram/rust-gamedig) **rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! From 7498b68e8140d350b680d89a9aa9cc8751155ef0 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 19:44:08 +0200 Subject: [PATCH 065/597] Edited README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5b337be..fa612a1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # rust-gamedig [![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) -[![Latest Version](https://img.shields.io/crates/v/gamedig.svg)](https://crates.io/crates/gamedig) -[![License:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md) -![Lines of code](https://img.shields.io/tokei/lines/github/cosminperram/rust-gamedig) +[![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) +![Lines of code](https://img.shields.io/tokei/lines/github/cosminperram/rust-gamedig?color=blue) +[![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) **rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! From 06b9ef0013ee100549b5e81b47a77d75fafdf2a8 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 19:47:26 +0200 Subject: [PATCH 066/597] Changed example ip to localhost --- examples/minecraft.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/minecraft.rs b/examples/minecraft.rs index 6b9ec6e..59d34e1 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -2,7 +2,7 @@ use gamedig::games::mc; fn main() { - let response = mc::query("cosminperram.com", Some(26062)); //or Some(25565), None is the default protocol port (which is 25565) + let response = mc::query("localhost", None); //or Some(25565), None is the default protocol port (which is 25565) match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:#?}", r) From f683c17c80b4aa99d25daf0ca3a281649c6ee4f7 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 19:56:43 +0200 Subject: [PATCH 067/597] Changed name of the CI file --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 411f540..118bf86 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Build and Test +name: CI on: push: From 2f640e93d53b12f32163e63b3eb2e32ac958c3a3 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 19:57:41 +0200 Subject: [PATCH 068/597] Modified README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fa612a1..e260a9a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Response: ```json5 { protocol: 17, - name: "Team Fortress 2 Dedicated Server!", + name: "Team Fortress 2 Dedicated Server.", map: "ctf_turbine", game: "tf2", players: 0, @@ -44,7 +44,7 @@ Response: steam_id: Some(69753253289735296), tv_port: None, tv_name: None, - keywords: Some("alltalk,arena,nocrits"), + keywords: Some("alltalk,nocrits"), rules: [ ServerRule { name: "mp_autoteambalance", From e689bc766e4a5544adbe37426cdeb17792220266 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 20:10:16 +0200 Subject: [PATCH 069/597] 7 Days To Die support. --- CHANGELOG.md | 3 ++- SERVICES.md | 2 +- examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/sdtd.rs | 12 ++++++++++++ src/protocols/minecraft/types.rs | 32 ++++++++++++++++---------------- src/protocols/valve/protocol.rs | 2 +- src/protocols/valve/types.rs | 2 ++ 8 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 src/games/sdtd.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c1fe3c3..9d98442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ Who knows what the future holds... # 0.0.6 - ??/??/2022 -[Minecraft](https://www.minecraft.com) implementation (bedrock not supported yet). +[Minecraft](https://www.minecraft.com) implementation (bedrock not supported yet). +[7 Days To Die](https://store.steampowered.com/app/251570/7_Days_to_Die/) implementation. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/SERVICES.md b/SERVICES.md index 38f4669..c1033d2 100644 --- a/SERVICES.md +++ b/SERVICES.md @@ -1,5 +1,5 @@ -# Supported games: +# Supported services: | ID | Name | Notes | |-----|------|-------| | --- | ---- | ----- | diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 0992fa1..cb6bd4e 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, tf2, ts}; +use gamedig::{aliens, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf2, ts}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -47,6 +47,7 @@ fn main() -> GDResult<()> { "mc_legacy_vb1_8" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port)?), "mc_legacy_v1_4" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port)?), "mc_legacy_v1_6" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port)?), + "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/mod.rs b/src/games/mod.rs index 6bee154..5fe9863 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -35,3 +35,5 @@ pub mod cscz; pub mod dod; /// Minecraft pub mod mc; +/// 7 Days To Die +pub mod sdtd; diff --git a/src/games/sdtd.rs b/src/games/sdtd.rs new file mode 100644 index 0000000..7ca1ec6 --- /dev/null +++ b/src/games/sdtd.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 26900, + Some(port) => port + }, SteamID::SDTD.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index 60d17b0..46cfa0c 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -31,16 +31,16 @@ SOFTWARE. use crate::{GDError, GDResult}; use crate::utils::buffer::get_u8; -/// The type of Minecraft Server you want to query +/// The type of Minecraft Server you want to query. #[derive(Debug)] pub enum Server { - /// Java Edition + /// Java Edition. Java, - /// Legacy Java + /// Legacy Java. Legacy(LegacyGroup) } -/// Legacy Java (Versions) Groups +/// Legacy Java (Versions) Groups. #[derive(Debug)] pub enum LegacyGroup { /// 1.6 @@ -51,35 +51,35 @@ pub enum LegacyGroup { VB1_8 } -/// Information about a player +/// Information about a player. #[derive(Debug)] pub struct Player { pub name: String, pub id: String } -/// A query response +/// A query response. #[derive(Debug)] pub struct Response { - /// Version name, example: "1.19.2" + /// Version name, example: "1.19.2". pub version_name: String, - /// Version protocol, example: 760 (for 1.19.2) + /// Version protocol, example: 760 (for 1.19.2). pub version_protocol: i32, - /// Number of server capacity + /// Number of server capacity. pub max_players: u32, - /// Number of online players + /// Number of online players. pub online_players: u32, - /// Some online players (can be missing) + /// Some online players (can be missing). pub sample_players: Option>, - /// Server's description or MOTD + /// Server's description or MOTD. pub description: String, - /// The favicon (can be missing) + /// The favicon (can be missing). pub favicon: Option, - /// Tells if the chat preview is enabled (can be missing) + /// Tells if the chat preview is enabled (can be missing). pub previews_chat: Option, - /// Tells if secure chat is enforced (can be missing) + /// Tells if secure chat is enforced (can be missing). pub enforces_secure_chat: Option, - /// Tell's the server type + /// Tell's the server type. pub server_type: Server } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index a3990fc..238098f 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -143,7 +143,7 @@ struct ValveProtocol { socket: UdpSocket } -static PACKET_SIZE: usize = 1400; +static PACKET_SIZE: usize = 1600; impl ValveProtocol { fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 158f3e6..b9fb839 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -173,6 +173,8 @@ pub enum SteamID { INSMIC = 17700, /// Insurgency INS = 222880, + /// 7 Days To Die + SDTD = 251570, /// Insurgency: Sandstorm INSS = 581320, /// Alien Swarm: Reactive Drop From 0a48b0e8eb49d7144ab2747b02ac00c9c3c91454 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 25 Nov 2022 20:27:31 +0200 Subject: [PATCH 070/597] Ark: Survival Evolved support --- CHANGELOG.md | 3 ++- examples/master_querant.rs | 3 ++- src/games/ase.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 5 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/games/ase.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d98442..3e16784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ Who knows what the future holds... # 0.0.6 - ??/??/2022 [Minecraft](https://www.minecraft.com) implementation (bedrock not supported yet). -[7 Days To Die](https://store.steampowered.com/app/251570/7_Days_to_Die/) implementation. +[7 Days To Die](https://store.steampowered.com/app/251570/7_Days_to_Die/) support. +[ARK: Survival Evolved](https://store.steampowered.com/app/346110/ARK_Survival_Evolved/) support. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/examples/master_querant.rs b/examples/master_querant.rs index cb6bd4e..1190f6c 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf2, ts}; +use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf2, ts}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -48,6 +48,7 @@ fn main() -> GDResult<()> { "mc_legacy_v1_4" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port)?), "mc_legacy_v1_6" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port)?), "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), + "ase" => println!("{:#?}", ase::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/ase.rs b/src/games/ase.rs new file mode 100644 index 0000000..30d2092 --- /dev/null +++ b/src/games/ase.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::ASE.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 5fe9863..63189ee 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -37,3 +37,5 @@ pub mod dod; pub mod mc; /// 7 Days To Die pub mod sdtd; +/// ARK: Survival Evolved +pub mod ase; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index b9fb839..42c50ad 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -175,6 +175,8 @@ pub enum SteamID { INS = 222880, /// 7 Days To Die SDTD = 251570, + /// ARK: Survival Evolved + ASE = 346110, /// Insurgency: Sandstorm INSS = 581320, /// Alien Swarm: Reactive Drop From 999998f309f53a5ca7b34b4e015b9a13bc0446dc Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 15:22:07 +0200 Subject: [PATCH 071/597] Modified README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e260a9a..6f67b44 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # rust-gamedig [![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) [![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) +[![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) ![Lines of code](https://img.shields.io/tokei/lines/github/cosminperram/rust-gamedig?color=blue) [![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) From 7b2cad22ec267547786cd2554aae056f9c359e12 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 15:38:13 +0200 Subject: [PATCH 072/597] Unturned support. --- README.md | 4 ++-- examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/unturned.rs | 12 ++++++++++++ src/protocols/valve/protocol.rs | 2 +- src/protocols/valve/types.rs | 2 ++ 6 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 src/games/unturned.rs diff --git a/README.md b/README.md index 6f67b44..ecbf8a3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ MSRV is `1.58.1` and the code is cross-platform. To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. ## Usage -Just pick a game/service/protocol, provide the ip and the port (can be optional) then query on it. +Just pick a game/service/protocol, provide the ip and the port (can be optional) (some use a special query port) then query on it. Team Fortress 2 query example: ```rust use gamedig::games::tf2; @@ -26,7 +26,7 @@ fn main() { } } ``` -Response: +Response (note that some games have a different structure): ```json5 { protocol: 17, diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 1190f6c..64d3d9b 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf2, ts}; +use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf2, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -49,6 +49,7 @@ fn main() -> GDResult<()> { "mc_legacy_v1_6" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port)?), "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), "ase" => println!("{:#?}", ase::query(ip, port)?), + "unturned" => println!("{:#?}", unturned::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/mod.rs b/src/games/mod.rs index 63189ee..31db2ab 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -39,3 +39,5 @@ pub mod mc; pub mod sdtd; /// ARK: Survival Evolved pub mod ase; +/// Unturned +pub mod unturned; diff --git a/src/games/unturned.rs b/src/games/unturned.rs new file mode 100644 index 0000000..4d9c9b9 --- /dev/null +++ b/src/games/unturned.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::UNTURNED.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 238098f..dcae027 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -143,7 +143,7 @@ struct ValveProtocol { socket: UdpSocket } -static PACKET_SIZE: usize = 1600; +static PACKET_SIZE: usize = 4096; impl ValveProtocol { fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 42c50ad..408c728 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -175,6 +175,8 @@ pub enum SteamID { INS = 222880, /// 7 Days To Die SDTD = 251570, + /// Unturned + UNTURNED = 304930, /// ARK: Survival Evolved ASE = 346110, /// Insurgency: Sandstorm From ed2161a6da36d2cb16a5fd8a6b88b25bf01daca3 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 15:40:34 +0200 Subject: [PATCH 073/597] Updated CHANGELOG and GAMES --- CHANGELOG.md | 3 ++- GAMES.md | 39 +++++++++++++++++++++------------------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e16784..dfa95a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ Who knows what the future holds... # 0.0.6 - ??/??/2022 [Minecraft](https://www.minecraft.com) implementation (bedrock not supported yet). [7 Days To Die](https://store.steampowered.com/app/251570/7_Days_to_Die/) support. -[ARK: Survival Evolved](https://store.steampowered.com/app/346110/ARK_Survival_Evolved/) support. +[ARK: Survival Evolved](https://store.steampowered.com/app/346110/ARK_Survival_Evolved/) support. +[Unturned](https://store.steampowered.com/app/304930/Unturned/) support. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index 208c05e..742049d 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,23 +1,26 @@ # Supported games: -| ID | Name | Protocol | Notes | -|--------|------------------------------------|----------------|------------------------------------------------------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| TS | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | -| CSS | Counter-Strike: Source | Valve Protocol | | -| DODS | Day of Defeat: Source | Valve Protocol | | -| L4D | Left 4 Dead | Valve Protocol | | -| L4D2 | Left 4 Dead 2 | Valve Protocol | | -| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | -| ALIENS | Alien Swarm | Valve Protocol | Not tested. | -| ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | -| INS | Insurgency | Valve Protocol | | -| INSS | Insurgency: Sandstorm | Valve Protocol | Use the query port, not the server port. | -| INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | Not tested. | -| CSCZ | Counter-Strike: Condition Zero | Valve Protocol | | -| DOD | Day of Defeat | Valve Protocol | | -| MC | Minecraft | Proprietary | Bedrock not supported yet. | +| ID | Name | Protocol | Notes | +|----------|------------------------------------|----------------|------------------------------------------------------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| TS | The Ship | Valve Protocol | | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | +| CSS | Counter-Strike: Source | Valve Protocol | | +| DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | +| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | +| ALIENS | Alien Swarm | Valve Protocol | Not tested. | +| ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | +| INS | Insurgency | Valve Protocol | | +| INSS | Insurgency: Sandstorm | Valve Protocol | Use the query port, not the server port. | +| INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | Not tested. | +| CSCZ | Counter-Strike: Condition Zero | Valve Protocol | | +| DOD | Day of Defeat | Valve Protocol | | +| MC | Minecraft | Proprietary | Bedrock not supported yet. | +| SDTD | 7 Days To Die | Valve Protocol | | +| ASE | ARK: Survival Evolved | Valve Protocol | | +| UNTURNED | Unturned | Valve Protocol | | ## Planned to add support: _ From 21b7d91ee6b012e594b5b22e5200ad550978310b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 16:08:47 +0200 Subject: [PATCH 074/597] The Forest support. --- CHANGELOG.md | 3 ++- GAMES.md | 3 ++- examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/tf.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/games/tf.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index dfa95a3..b94f5e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ Who knows what the future holds... [Minecraft](https://www.minecraft.com) implementation (bedrock not supported yet). [7 Days To Die](https://store.steampowered.com/app/251570/7_Days_to_Die/) support. [ARK: Survival Evolved](https://store.steampowered.com/app/346110/ARK_Survival_Evolved/) support. -[Unturned](https://store.steampowered.com/app/304930/Unturned/) support. +[Unturned](https://store.steampowered.com/app/304930/Unturned/) support. +[The Forest](https://store.steampowered.com/app/242760/The_Forest/) support. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index 742049d..73a222e 100644 --- a/GAMES.md +++ b/GAMES.md @@ -13,7 +13,7 @@ | ALIENS | Alien Swarm | Valve Protocol | Not tested. | | ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | | INS | Insurgency | Valve Protocol | | -| INSS | Insurgency: Sandstorm | Valve Protocol | Use the query port, not the server port. | +| INSS | Insurgency: Sandstorm | Valve Protocol | Use the query port. | | INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | Not tested. | | CSCZ | Counter-Strike: Condition Zero | Valve Protocol | | | DOD | Day of Defeat | Valve Protocol | | @@ -21,6 +21,7 @@ | SDTD | 7 Days To Die | Valve Protocol | | | ASE | ARK: Survival Evolved | Valve Protocol | | | UNTURNED | Unturned | Valve Protocol | | +| TF | The Forest | Valve Protocol | Use the query port. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 64d3d9b..62b4d57 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf2, ts, unturned}; +use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf, tf2, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -50,6 +50,7 @@ fn main() -> GDResult<()> { "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), "ase" => println!("{:#?}", ase::query(ip, port)?), "unturned" => println!("{:#?}", unturned::query(ip, port)?), + "tf" => println!("{:#?}", tf::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/mod.rs b/src/games/mod.rs index 31db2ab..7f60cc8 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -41,3 +41,5 @@ pub mod sdtd; pub mod ase; /// Unturned pub mod unturned; +/// The Forest +pub mod tf; diff --git a/src/games/tf.rs b/src/games/tf.rs new file mode 100644 index 0000000..e5088e4 --- /dev/null +++ b/src/games/tf.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::TF.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 408c728..76e3367 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -173,6 +173,8 @@ pub enum SteamID { INSMIC = 17700, /// Insurgency INS = 222880, + /// The Forrest + TF = 556450, //this is the id for the dedicated server, for the game its 242760 /// 7 Days To Die SDTD = 251570, /// Unturned From d086d49cdc488e316945dab85ca0e98d1ab42654 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 16:15:01 +0200 Subject: [PATCH 075/597] Team Fortress Classic support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/tfc.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 4 +++- 6 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/games/tfc.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b94f5e7..a238bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Who knows what the future holds... [ARK: Survival Evolved](https://store.steampowered.com/app/346110/ARK_Survival_Evolved/) support. [Unturned](https://store.steampowered.com/app/304930/Unturned/) support. [The Forest](https://store.steampowered.com/app/242760/The_Forest/) support. +[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/) support. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index 73a222e..52cfdb4 100644 --- a/GAMES.md +++ b/GAMES.md @@ -22,6 +22,7 @@ | ASE | ARK: Survival Evolved | Valve Protocol | | | UNTURNED | Unturned | Valve Protocol | | | TF | The Forest | Valve Protocol | Use the query port. | +| TFC | Team Fortress Classic | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 62b4d57..c1a5c9d 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf, tf2, ts, unturned}; +use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -51,6 +51,7 @@ fn main() -> GDResult<()> { "ase" => println!("{:#?}", ase::query(ip, port)?), "unturned" => println!("{:#?}", unturned::query(ip, port)?), "tf" => println!("{:#?}", tf::query(ip, port)?), + "tfc" => println!("{:#?}", tfc::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/mod.rs b/src/games/mod.rs index 7f60cc8..3e41830 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -43,3 +43,5 @@ pub mod ase; pub mod unturned; /// The Forest pub mod tf; +/// Team Fortress Classic +pub mod tfc; diff --git a/src/games/tfc.rs b/src/games/tfc.rs new file mode 100644 index 0000000..aa8eef2 --- /dev/null +++ b/src/games/tfc.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::TFC.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 76e3367..4757fbb 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -145,6 +145,8 @@ pub enum Request { #[repr(u32)] #[derive(PartialEq, Clone)] pub enum SteamID { + /// Team Fortress Classic + TFC = 20, /// Day of Defeat DOD = 30, /// Counter-Strike: Condition Zero @@ -191,7 +193,7 @@ impl SteamID { /// Get ID as App (the engine is specified). pub fn as_app(&self) -> App { match self { - SteamID::CSCZ | SteamID::DOD => App::GoldSrc(false), + SteamID::TFC | SteamID::DOD | SteamID::CSCZ => App::GoldSrc(false), x => App::Source(Some(x.clone() as u32)) } } From 8c5ac24468ede985c353d0cdb6396e8f02f6a46a Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 16:15:26 +0200 Subject: [PATCH 076/597] Changed The Forest default port to 27016. --- src/games/tf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/games/tf.rs b/src/games/tf.rs index e5088e4..730573b 100644 --- a/src/games/tf.rs +++ b/src/games/tf.rs @@ -4,7 +4,7 @@ use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { - None => 27015, + None => 27016, Some(port) => port }, SteamID::TF.as_app(), None, None)?; From de3ac9aad52c5c2a4b87ce89210e670bbc993ba4 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 16:21:58 +0200 Subject: [PATCH 077/597] Sven Co-op support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/sc.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 4 +++- 6 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/games/sc.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a238bf0..ea515cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Who knows what the future holds... [Unturned](https://store.steampowered.com/app/304930/Unturned/) support. [The Forest](https://store.steampowered.com/app/242760/The_Forest/) support. [Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/) support. +[Sven Co-op](https://store.steampowered.com/app/225840/Sven_Coop/) support. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index 52cfdb4..648796f 100644 --- a/GAMES.md +++ b/GAMES.md @@ -23,6 +23,7 @@ | UNTURNED | Unturned | Valve Protocol | | | TF | The Forest | Valve Protocol | Use the query port. | | TFC | Team Fortress Classic | Valve Protocol | | +| SC | Sven Co-op | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index c1a5c9d..d230e69 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -52,6 +52,7 @@ fn main() -> GDResult<()> { "unturned" => println!("{:#?}", unturned::query(ip, port)?), "tf" => println!("{:#?}", tf::query(ip, port)?), "tfc" => println!("{:#?}", tfc::query(ip, port)?), + "sc" => println!("{:#?}", sc::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/mod.rs b/src/games/mod.rs index 3e41830..9023672 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -45,3 +45,5 @@ pub mod unturned; pub mod tf; /// Team Fortress Classic pub mod tfc; +/// Sven Co-op +pub mod sc; diff --git a/src/games/sc.rs b/src/games/sc.rs new file mode 100644 index 0000000..7ab933d --- /dev/null +++ b/src/games/sc.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::SC.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 4757fbb..cb55026 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -175,6 +175,8 @@ pub enum SteamID { INSMIC = 17700, /// Insurgency INS = 222880, + /// Sven Co-op + SC = 225840, /// The Forrest TF = 556450, //this is the id for the dedicated server, for the game its 242760 /// 7 Days To Die @@ -193,7 +195,7 @@ impl SteamID { /// Get ID as App (the engine is specified). pub fn as_app(&self) -> App { match self { - SteamID::TFC | SteamID::DOD | SteamID::CSCZ => App::GoldSrc(false), + SteamID::TFC | SteamID::DOD | SteamID::CSCZ | SteamID::SC => App::GoldSrc(false), x => App::Source(Some(x.clone() as u32)) } } From 3f58e99c284411904fb454e64d579bc551a3da00 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 16:27:03 +0200 Subject: [PATCH 078/597] Rust support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/rust.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/rust.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ea515cd..07cbdb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Who knows what the future holds... [The Forest](https://store.steampowered.com/app/242760/The_Forest/) support. [Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/) support. [Sven Co-op](https://store.steampowered.com/app/225840/Sven_Coop/) support. +[Rust](https://store.steampowered.com/app/252490/Rust/) support. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index 648796f..0542526 100644 --- a/GAMES.md +++ b/GAMES.md @@ -24,6 +24,7 @@ | TF | The Forest | Valve Protocol | Use the query port. | | TFC | Team Fortress Classic | Valve Protocol | | | SC | Sven Co-op | Valve Protocol | | +| RUST | Rust | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index d230e69..03a268b 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -53,6 +53,7 @@ fn main() -> GDResult<()> { "tf" => println!("{:#?}", tf::query(ip, port)?), "tfc" => println!("{:#?}", tfc::query(ip, port)?), "sc" => println!("{:#?}", sc::query(ip, port)?), + "rust" => println!("{:#?}", rust::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/mod.rs b/src/games/mod.rs index 9023672..44fdf8c 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -47,3 +47,5 @@ pub mod tf; pub mod tfc; /// Sven Co-op pub mod sc; +/// Rust +pub mod rust; diff --git a/src/games/rust.rs b/src/games/rust.rs new file mode 100644 index 0000000..8f0e5a9 --- /dev/null +++ b/src/games/rust.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::RUST.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index cb55026..c38d76b 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -177,6 +177,8 @@ pub enum SteamID { INS = 222880, /// Sven Co-op SC = 225840, + /// Rust + RUST = 252490, /// The Forrest TF = 556450, //this is the id for the dedicated server, for the game its 242760 /// 7 Days To Die From 645582868d02c7768b86379a513ca17594738d9d Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 17:09:34 +0200 Subject: [PATCH 079/597] More tests and fixed (?) utf16 be string reading --- src/utils.rs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index c84b038..0e0a591 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -97,10 +97,14 @@ pub mod buffer { pub fn get_string_utf8_le(buf: &[u8], pos: &mut usize) -> GDResult { let sub_buf = &buf[*pos..]; + if sub_buf.len() == 0 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 LE string.".to_string())); + } + let first_null_position = sub_buf.iter().position(|&x| x == 0) - .ok_or(GDError::PacketBad("Unexpectedly formatted packet for getting a string.".to_string()))?; + .ok_or(GDError::PacketBad("Unexpectedly formatted packet for getting a utf8 LE string.".to_string()))?; let value = std::str::from_utf8(&sub_buf[..first_null_position]) - .map_err(|_| GDError::PacketBad("Badly formatted string.".to_string()))?.to_string(); + .map_err(|_| GDError::PacketBad("Badly formatted utf8 LE string.".to_string()))?.to_string(); *pos += value.len() + 1; Ok(value) @@ -108,13 +112,17 @@ pub mod buffer { pub fn get_string_utf16_be(buf: &[u8], pos: &mut usize) -> GDResult { let sub_buf = &buf[*pos..]; + if sub_buf.len() == 0 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf16 BE string.".to_string())); + } + let paired_buf: Vec = sub_buf.chunks_exact(2) .into_iter().map(|a| u16::from_be_bytes([a[0], a[1]])).collect(); let value = String::from_utf16(&paired_buf) - .map_err(|_| GDError::PacketBad("Badly formatted string.".to_string()))?.to_string(); + .map_err(|_| GDError::PacketBad("Badly formatted utf16 BE string.".to_string()))?.to_string(); - *pos += value.len() + 1; + *pos += value.len() * 2; Ok(value) } } @@ -149,6 +157,16 @@ mod tests { assert_eq!(pos, 2); } + #[test] + fn get_u16_be_test() { + let data = [29, 72]; + let mut pos = 0; + assert_eq!(buffer::get_u16_be(&data, &mut pos).unwrap(), 7496); + assert_eq!(pos, 2); + assert!(buffer::get_u16_be(&data, &mut pos).is_err()); + assert_eq!(pos, 2); + } + #[test] fn get_u32_le_test() { let data = [72, 29, 128, 100]; @@ -188,4 +206,14 @@ mod tests { assert!(buffer::get_string_utf8_le(&data, &mut pos).is_err()); assert_eq!(pos, 6); } + + #[test] + fn get_string_utf16_be_test() { + let data = [0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f]; + let mut pos = 0; + assert_eq!(buffer::get_string_utf16_be(&data, &mut pos).unwrap(), "Hello"); + assert_eq!(pos, 10); + assert!(buffer::get_string_utf16_be(&data, &mut pos).is_err()); + assert_eq!(pos, 10); + } } From aec145a8470ebee2e0f070e703705aba1a2d5c47 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 17:17:09 +0200 Subject: [PATCH 080/597] Counter-Strike support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/cs.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 4 +++- 6 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/games/cs.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 07cbdb5..7634a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Who knows what the future holds... [Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/) support. [Sven Co-op](https://store.steampowered.com/app/225840/Sven_Coop/) support. [Rust](https://store.steampowered.com/app/252490/Rust/) support. +[Counter-Strike](https://store.steampowered.com/app/10/CounterStrike/) support. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index 0542526..1362b2a 100644 --- a/GAMES.md +++ b/GAMES.md @@ -25,6 +25,7 @@ | TFC | Team Fortress Classic | Valve Protocol | | | SC | Sven Co-op | Valve Protocol | | | RUST | Rust | Valve Protocol | | +| CS | Counter-Strike | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 03a268b..66626a1 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, ase, asrd, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, ase, asrd, cs, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -54,6 +54,7 @@ fn main() -> GDResult<()> { "tfc" => println!("{:#?}", tfc::query(ip, port)?), "sc" => println!("{:#?}", sc::query(ip, port)?), "rust" => println!("{:#?}", rust::query(ip, port)?), + "cs" => println!("{:#?}", cs::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/cs.rs b/src/games/cs.rs new file mode 100644 index 0000000..3e662cb --- /dev/null +++ b/src/games/cs.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::CS.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 44fdf8c..0c80b16 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -49,3 +49,5 @@ pub mod tfc; pub mod sc; /// Rust pub mod rust; +/// Counter-Strike +pub mod cs; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index c38d76b..04b42ec 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -145,6 +145,8 @@ pub enum Request { #[repr(u32)] #[derive(PartialEq, Clone)] pub enum SteamID { + /// Counter-Strike + CS = 10, /// Team Fortress Classic TFC = 20, /// Day of Defeat @@ -197,7 +199,7 @@ impl SteamID { /// Get ID as App (the engine is specified). pub fn as_app(&self) -> App { match self { - SteamID::TFC | SteamID::DOD | SteamID::CSCZ | SteamID::SC => App::GoldSrc(false), + SteamID::CS | SteamID::TFC | SteamID::DOD | SteamID::CSCZ | SteamID::SC => App::GoldSrc(false), x => App::Source(Some(x.clone() as u32)) } } From 9f861df96b14286f7e63018969e531e943046886 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 26 Nov 2022 18:08:32 +0200 Subject: [PATCH 081/597] Arma 2: Operation Arrowhead support. --- CHANGELOG.md | 5 +++-- GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/arma2oa.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/games/arma2oa.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7634a57..a658251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,9 @@ Who knows what the future holds... [The Forest](https://store.steampowered.com/app/242760/The_Forest/) support. [Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/) support. [Sven Co-op](https://store.steampowered.com/app/225840/Sven_Coop/) support. -[Rust](https://store.steampowered.com/app/252490/Rust/) support. -[Counter-Strike](https://store.steampowered.com/app/10/CounterStrike/) support. +[Rust](https://store.steampowered.com/app/252490/Rust/) support. +[Counter-Strike](https://store.steampowered.com/app/10/CounterStrike/) support. +[Arma 2: Operation Arrowhead](https://store.steampowered.com/app/33930/Arma_2_Operation_Arrowhead/) support. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index 1362b2a..9abbd26 100644 --- a/GAMES.md +++ b/GAMES.md @@ -26,6 +26,7 @@ | SC | Sven Co-op | Valve Protocol | | | RUST | Rust | Valve Protocol | | | CS | Counter-Strike | Valve Protocol | | +| ARMA2OA | Arma 2: Operation Arrowhead | Valve Protocol | Use the query port. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 66626a1..0d6295a 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, ase, asrd, cs, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -55,6 +55,7 @@ fn main() -> GDResult<()> { "sc" => println!("{:#?}", sc::query(ip, port)?), "rust" => println!("{:#?}", rust::query(ip, port)?), "cs" => println!("{:#?}", cs::query(ip, port)?), + "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/arma2oa.rs b/src/games/arma2oa.rs new file mode 100644 index 0000000..5ea6f86 --- /dev/null +++ b/src/games/arma2oa.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 2304, + Some(port) => port + }, SteamID::ARMA2OA.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 0c80b16..e998e16 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -51,3 +51,5 @@ pub mod sc; pub mod rust; /// Counter-Strike pub mod cs; +/// ARMA 2: Operation Arrowhead +pub mod arma2oa; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 04b42ec..45355b2 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -175,6 +175,8 @@ pub enum SteamID { GM = 4000, /// Insurgency: Modern Infantry Combat INSMIC = 17700, + /// ARMA 2: Operation Arrowhead + ARMA2OA = 33930, /// Insurgency INS = 222880, /// Sven Co-op From 84d05bd95824f6cb28d6f26daa80067aea5b67ff Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 27 Nov 2022 23:40:07 +0200 Subject: [PATCH 082/597] Tested Alien Swarm and Insurgency: Modern Infantry Combat. --- CHANGELOG.md | 2 ++ GAMES.md | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a658251..1d3fcdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ Who knows what the future holds... [Rust](https://store.steampowered.com/app/252490/Rust/) support. [Counter-Strike](https://store.steampowered.com/app/10/CounterStrike/) support. [Arma 2: Operation Arrowhead](https://store.steampowered.com/app/33930/Arma_2_Operation_Arrowhead/) support. +Tested `Alien Swarm` and `Insurgency: Modern Infantry Combat` and they work. +Increased Valve Protocol `PACKET_SIZE` from 1400 to 4096, some games send larger packets than the specified protocol size. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index 9abbd26..61cb137 100644 --- a/GAMES.md +++ b/GAMES.md @@ -10,11 +10,11 @@ | L4D | Left 4 Dead | Valve Protocol | | | L4D2 | Left 4 Dead 2 | Valve Protocol | | | HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | -| ALIENS | Alien Swarm | Valve Protocol | Not tested. | +| ALIENS | Alien Swarm | Valve Protocol | | | ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | | INS | Insurgency | Valve Protocol | | | INSS | Insurgency: Sandstorm | Valve Protocol | Use the query port. | -| INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | Not tested. | +| INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | | | CSCZ | Counter-Strike: Condition Zero | Valve Protocol | | | DOD | Day of Defeat | Valve Protocol | | | MC | Minecraft | Proprietary | Bedrock not supported yet. | From ed681025b46e7350c2cf8ea07a3bb948bde46941 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 27 Nov 2022 23:45:48 +0200 Subject: [PATCH 083/597] Added error_by_expected_size test. --- src/utils.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index 0e0a591..5af8cde 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -216,4 +216,11 @@ mod tests { assert!(buffer::get_string_utf16_be(&data, &mut pos).is_err()); assert_eq!(pos, 10); } + + #[test] + fn error_by_expected_size_test() { + assert!(error_by_expected_size(69, 69).is_ok()); + assert!(error_by_expected_size(69, 68).is_err()); + assert!(error_by_expected_size(69, 70).is_err()); + } } From e709cb3ce566fdecdb64f3de98b34cd7fbe27285 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 00:23:19 +0200 Subject: [PATCH 084/597] Edited GAMES.md, PROTOCOLS.md and changed 'verify' keyword to 'query' in Cargo.toml --- Cargo.toml | 2 +- GAMES.md | 55 ++++++++++++++++++++++++++-------------------------- PROTOCOLS.md | 9 +++++---- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5000cf0..944f04f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ homepage = "https://github.com/CosminPerRam/rust-gamedig" documentation = "https://docs.rs/gamedig/latest/gamedig/" repository = "https://github.com/CosminPerRam/rust-gamedig" readme = "README.md" -keywords = ["server", "verify", "game", "check", "status"] +keywords = ["server", "query", "game", "check", "status"] [package.metadata] msrv = "1.58.1" diff --git a/GAMES.md b/GAMES.md index 61cb137..d95782b 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,32 +1,33 @@ +A supported game is defined as a game that has been successfully tested, other games that use the implemented protocols might work too, but it isn't guaranteed. # Supported games: -| ID | Name | Protocol | Notes | -|----------|------------------------------------|----------------|------------------------------------------------------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| TS | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | -| CSS | Counter-Strike: Source | Valve Protocol | | -| DODS | Day of Defeat: Source | Valve Protocol | | -| L4D | Left 4 Dead | Valve Protocol | | -| L4D2 | Left 4 Dead 2 | Valve Protocol | | -| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | -| ALIENS | Alien Swarm | Valve Protocol | | -| ASRD | Alien Swarm: Reactive Drop | Valve Protocol | | -| INS | Insurgency | Valve Protocol | | -| INSS | Insurgency: Sandstorm | Valve Protocol | Use the query port. | -| INSMIC | Insurgency: Modern Infantry Combat | Valve Protocol | | -| CSCZ | Counter-Strike: Condition Zero | Valve Protocol | | -| DOD | Day of Defeat | Valve Protocol | | -| MC | Minecraft | Proprietary | Bedrock not supported yet. | -| SDTD | 7 Days To Die | Valve Protocol | | -| ASE | ARK: Survival Evolved | Valve Protocol | | -| UNTURNED | Unturned | Valve Protocol | | -| TF | The Forest | Valve Protocol | Use the query port. | -| TFC | Team Fortress Classic | Valve Protocol | | -| SC | Sven Co-op | Valve Protocol | | -| RUST | Rust | Valve Protocol | | -| CS | Counter-Strike | Valve Protocol | | -| ARMA2OA | Arma 2: Operation Arrowhead | Valve Protocol | Use the query port. | +| Game | Use name | Protocol | Notes | +|------------------------------------|----------|---------------------------|------------------------------------------------------------------------------| +| Team Fortress 2 | TF2 | Valve Protocol | | +| The Ship | TS | Valve Protocol (*Altered) | | +| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | +| Counter-Strike: Source | CSS | Valve Protocol | | +| Day of Defeat: Source | DODS | Valve Protocol | | +| Left 4 Dead | L4D | Valve Protocol | | +| Left 4 Dead 2 | L4D2 | Valve Protocol | | +| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | +| Alien Swarm | ALIENS | Valve Protocol | | +| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | +| Insurgency | INS | Valve Protocol | | +| Insurgency: Sandstorm | INSS | Valve Protocol | Use the query port. | +| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | +| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | +| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | +| Minecraft | MC | Proprietary | Bedrock not supported yet. | +| 7 Days To Die | SDTD | Valve Protocol | | +| ARK: Survival Evolved | ASE | Valve Protocol | | +| Unturned | UNTURNED | Valve Protocol | | +| The Forest | TF | Valve Protocol (GoldSrc) | Use the query port. | +| Team Fortress Classic | TFC | Valve Protocol | | +| Sven Co-op | SC | Valve Protocol (GoldSrc) | | +| Rust | RUST | Valve Protocol | | +| Counter-Strike | CS | Valve Protocol (GoldSrc) | | +| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | ## Planned to add support: _ diff --git a/PROTOCOLS.md b/PROTOCOLS.md index bc2dc85..06f3d26 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,9 +1,10 @@ +A protocol is defined as proprietary if it is being used only for a single scope. # Supported protocols: -| Name | Documentation reference | Notes | -|----------------|---------------------------------------------------------------------------|----------------------------------------| -| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | -| Minecraft | [List Server Protocol](https://wiki.vg/Server_List_Ping) | Bedrock not yet supported. | +| Name | For | Proprietary? | Documentation reference | Notes | +|----------------|-------|--------------|---------------------------------------------------------------------------|----------------------------------------| +| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | +| Minecraft | Games | Yes | [List Server Protocol](https://wiki.vg/Server_List_Ping) | Bedrock not yet supported. | ## Planned to add support: _ From 259e21a4abe20c1f2e65f95efdfa3e5cc69c7526 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 00:51:09 +0200 Subject: [PATCH 085/597] Restored full CSGO query capabilities --- CHANGELOG.md | 2 +- GAMES.md | 54 +++++++++++++++--------------- src/games/csgo.rs | 59 +++------------------------------ src/protocols/valve/protocol.rs | 6 +--- src/protocols/valve/types.rs | 4 +-- 5 files changed, 35 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d3fcdc..5b2a72a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ Who knows what the future holds... [Counter-Strike](https://store.steampowered.com/app/10/CounterStrike/) support. [Arma 2: Operation Arrowhead](https://store.steampowered.com/app/33930/Arma_2_Operation_Arrowhead/) support. Tested `Alien Swarm` and `Insurgency: Modern Infantry Combat` and they work. -Increased Valve Protocol `PACKET_SIZE` from 1400 to 4096, some games send larger packets than the specified protocol size. +Increased Valve Protocol `PACKET_SIZE` from 1400 to 6144, some games send larger packets than the specified protocol size. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/GAMES.md b/GAMES.md index d95782b..ab3131c 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,33 +1,33 @@ A supported game is defined as a game that has been successfully tested, other games that use the implemented protocols might work too, but it isn't guaranteed. # Supported games: -| Game | Use name | Protocol | Notes | -|------------------------------------|----------|---------------------------|------------------------------------------------------------------------------| -| Team Fortress 2 | TF2 | Valve Protocol | | -| The Ship | TS | Valve Protocol (*Altered) | | -| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | -| Counter-Strike: Source | CSS | Valve Protocol | | -| Day of Defeat: Source | DODS | Valve Protocol | | -| Left 4 Dead | L4D | Valve Protocol | | -| Left 4 Dead 2 | L4D2 | Valve Protocol | | -| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | -| Alien Swarm | ALIENS | Valve Protocol | | -| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | -| Insurgency | INS | Valve Protocol | | -| Insurgency: Sandstorm | INSS | Valve Protocol | Use the query port. | -| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | -| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | -| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | -| Minecraft | MC | Proprietary | Bedrock not supported yet. | -| 7 Days To Die | SDTD | Valve Protocol | | -| ARK: Survival Evolved | ASE | Valve Protocol | | -| Unturned | UNTURNED | Valve Protocol | | -| The Forest | TF | Valve Protocol (GoldSrc) | Use the query port. | -| Team Fortress Classic | TFC | Valve Protocol | | -| Sven Co-op | SC | Valve Protocol (GoldSrc) | | -| Rust | RUST | Valve Protocol | | -| Counter-Strike | CS | Valve Protocol (GoldSrc) | | -| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | +| Game | Use name | Protocol | Notes | +|------------------------------------|----------|---------------------------|-------------------------------------------------------------------------------------------| +| Team Fortress 2 | TF2 | Valve Protocol | | +| The Ship | TS | Valve Protocol (*Altered) | | +| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server must have the cvar `host_players_show` set to `2` to get the full player list. | +| Counter-Strike: Source | CSS | Valve Protocol | | +| Day of Defeat: Source | DODS | Valve Protocol | | +| Left 4 Dead | L4D | Valve Protocol | | +| Left 4 Dead 2 | L4D2 | Valve Protocol | | +| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | +| Alien Swarm | ALIENS | Valve Protocol | | +| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | +| Insurgency | INS | Valve Protocol | | +| Insurgency: Sandstorm | INSS | Valve Protocol | Use the query port. | +| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | +| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | +| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | +| Minecraft | MC | Proprietary | Bedrock not supported yet. | +| 7 Days To Die | SDTD | Valve Protocol | | +| ARK: Survival Evolved | ASE | Valve Protocol | | +| Unturned | UNTURNED | Valve Protocol | | +| The Forest | TF | Valve Protocol (GoldSrc) | Use the query port. | +| Team Fortress Classic | TFC | Valve Protocol | | +| Sven Co-op | SC | Valve Protocol (GoldSrc) | | +| Rust | RUST | Valve Protocol | | +| Counter-Strike | CS | Valve Protocol (GoldSrc) | | +| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | ## Planned to add support: _ diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 2ad965e..3786606 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,63 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{Server, GatheringSettings, get_optional_extracted_data, SteamID}; -use crate::protocols::valve::game::Player; +use crate::protocols::valve::{game, SteamID}; -#[derive(Debug)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), - max_players: response.info.max_players, - bots: response.info.bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CSGO.as_app(), Some(GatheringSettings { - players: true, - rules: false // cause csgo doesnt reply with rules anymore - }), None)?; + }, SteamID::CSGO.as_app(), None, None)?; - Ok(Response::new_from_valve_response(valve_response)) + Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index dcae027..c1985ba 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -143,7 +143,7 @@ struct ValveProtocol { socket: UdpSocket } -static PACKET_SIZE: usize = 4096; +static PACKET_SIZE: usize = 6144; impl ValveProtocol { fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { @@ -383,10 +383,6 @@ impl ValveProtocol { /// Get the server rules's. fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult>> { - if *app == SteamID::CSGO.as_app() { //cause csgo wont respond to this since feb 21 2014 update - return Ok(None); - } - let buf = self.get_request_data(&app, protocol, Request::RULES)?; let mut pos = 0; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 45355b2..cca0341 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -290,7 +290,7 @@ pub mod game { map: response.info.map, game: response.info.game, players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + players_details: response.players.unwrap_or(vec![]).iter().map(|p| Player::from_valve_response(p)).collect(), max_players: response.info.max_players, bots: response.info.bots, server_type: response.info.server_type, @@ -302,7 +302,7 @@ pub mod game { tv_port, tv_name, keywords, - rules: response.rules.unwrap() + rules: response.rules.unwrap_or(vec![]) } } } From 77a68e4a0cae37846df75990144c73d547bacd48 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 00:56:11 +0200 Subject: [PATCH 086/597] Modified CHANGELOG.md to tell the comeback of the csgo rules response and increased valve protocol PACKET_SIZE from 1400 to 6144 --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b2a72a..226885d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ Who knows what the future holds... [Rust](https://store.steampowered.com/app/252490/Rust/) support. [Counter-Strike](https://store.steampowered.com/app/10/CounterStrike/) support. [Arma 2: Operation Arrowhead](https://store.steampowered.com/app/33930/Arma_2_Operation_Arrowhead/) support. -Tested `Alien Swarm` and `Insurgency: Modern Infantry Combat` and they work. -Increased Valve Protocol `PACKET_SIZE` from 1400 to 6144, some games send larger packets than the specified protocol size. +Successfully tested `Alien Swarm` and `Insurgency: Modern Infantry Combat`. +Restored rules response for `Counter-Strike: Global Offensive` (note: for a full player list response, the cvar `host_players_show` must be set to `2`). +Increased Valve Protocol `PACKET_SIZE` from 1400 to 6144 (because some games send larger packets than the specified protocol size). # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. From 3d95f08ef461b043dace6123f3b6e3cf54da8984 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 01:13:08 +0200 Subject: [PATCH 087/597] Removed DNS resolving as it was not needed --- CHANGELOG.md | 3 ++- Cargo.toml | 2 -- src/socket.rs | 6 +++--- src/utils.rs | 22 +++++----------------- 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 226885d..7b867f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ Who knows what the future holds... [Arma 2: Operation Arrowhead](https://store.steampowered.com/app/33930/Arma_2_Operation_Arrowhead/) support. Successfully tested `Alien Swarm` and `Insurgency: Modern Infantry Combat`. Restored rules response for `Counter-Strike: Global Offensive` (note: for a full player list response, the cvar `host_players_show` must be set to `2`). -Increased Valve Protocol `PACKET_SIZE` from 1400 to 6144 (because some games send larger packets than the specified protocol size). +Increased Valve Protocol `PACKET_SIZE` from 1400 to 6144 (because some games send larger packets than the specified protocol size). +Removed DNS resolving as it was not needed. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/Cargo.toml b/Cargo.toml index 944f04f..0ec3c7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,4 @@ msrv = "1.58.1" bzip2-rs = "0.1.2" # for compression crc32fast = "1.3.2" -trust-dns-resolver = "0.22.0" # dns resolving - serde_json = "1.0.87" # json to structs diff --git a/src/socket.rs b/src/socket.rs index ae25a44..b573eda 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -2,7 +2,7 @@ use std::io::{Read, Write}; use std::net; use crate::{GDError, GDResult}; use crate::protocols::types::TimeoutSettings; -use crate::utils::complete_address; +use crate::utils::address_and_port_as_string; static DEFAULT_PACKET_SIZE: usize = 1024; @@ -21,7 +21,7 @@ pub struct TcpSocket { impl Socket for TcpSocket { fn new(address: &str, port: u16) -> GDResult { - let complete_address = complete_address(address, port)?; + let complete_address = address_and_port_as_string(address, port)?; let socket = net::TcpStream::connect(complete_address).map_err(|e| GDError::SocketConnect(e.to_string()))?; Ok(Self { @@ -57,7 +57,7 @@ pub struct UdpSocket { impl Socket for UdpSocket { fn new(address: &str, port: u16) -> GDResult { - let complete_address = complete_address(address, port)?; + let complete_address = address_and_port_as_string(address, port)?; let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|e| GDError::SocketBind(e.to_string()))?; Ok(Self { diff --git a/src/utils.rs b/src/utils.rs index 5af8cde..178bc07 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,17 +1,5 @@ -use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; -use trust_dns_resolver::Resolver; use crate::{GDResult, GDError}; -fn resolve_dns(address: &str) -> GDResult { - let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()) - .map_err(|e| GDError::DnsResolve(e.to_string()))?; - - let response = resolver.lookup_ip(address) - .map_err(|e| GDError::DnsResolve(e.to_string()))?; - - Ok(response.iter().next().ok_or(GDError::DnsResolve("Couldn't resolve the DNS address.".to_string()))?.to_string()) -} - pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { if size < expected { Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())) @@ -24,8 +12,8 @@ pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { } } -pub fn complete_address(address: &str, port: u16) -> GDResult { - Ok(resolve_dns(address)? + ":" + &*port.to_string()) +pub fn address_and_port_as_string(address: &str, port: u16) -> GDResult { + Ok(address.to_string() + ":" + &*port.to_string()) } pub fn u8_lower_upper(n: u8) -> (u8, u8) { @@ -132,9 +120,9 @@ mod tests { use super::*; #[test] - fn complete_address_test() { - assert_eq!(complete_address("192.168.0.1", 27015).unwrap(), "192.168.0.1:27015"); - assert!(complete_address("not_existent_address", 9999).is_err()); + fn address_and_port_as_string_test() { + assert_eq!(address_and_port_as_string("192.168.0.1", 27015).unwrap(), "192.168.0.1:27015"); + assert!(address_and_port_as_string("not_existent_address", 9999).is_err()); } #[test] From a1d42af2dfc68be632a100d49b833c54bea08de6 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 01:16:30 +0200 Subject: [PATCH 088/597] Simplified some map uses --- src/games/ts.rs | 2 +- src/protocols/valve/types.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/games/ts.rs b/src/games/ts.rs index a59c4ae..95c4d49 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -60,7 +60,7 @@ impl Response { map: response.info.map, game: response.info.game, players: response.info.players, - players_details: response.players.unwrap().iter().map(|p| TheShipPlayer::new_from_valve_player(p)).collect(), + players_details: response.players.unwrap().iter().map(TheShipPlayer::new_from_valve_player).collect(), max_players: response.info.max_players, bots: response.info.bots, server_type: response.info.server_type, diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index cca0341..e83a3e9 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -290,7 +290,7 @@ pub mod game { map: response.info.map, game: response.info.game, players: response.info.players, - players_details: response.players.unwrap_or(vec![]).iter().map(|p| Player::from_valve_response(p)).collect(), + players_details: response.players.unwrap_or(vec![]).iter().map(Player::from_valve_response).collect(), max_players: response.info.max_players, bots: response.info.bots, server_type: response.info.server_type, From 2e8aa50c3ae7d1a5d8cfbaa29b51f1671cc425fb Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 01:22:59 +0200 Subject: [PATCH 089/597] Fixed tests and removed dns error --- CHANGELOG.md | 2 +- src/errors.rs | 3 --- src/utils.rs | 7 +++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b867f7..c878ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Who knows what the future holds... # 0.0.6 - ??/??/2022 -[Minecraft](https://www.minecraft.com) implementation (bedrock not supported yet). +[Minecraft](https://www.minecraft.com) support (bedrock not supported yet). [7 Days To Die](https://store.steampowered.com/app/251570/7_Days_to_Die/) support. [ARK: Survival Evolved](https://store.steampowered.com/app/346110/ARK_Survival_Evolved/) support. [Unturned](https://store.steampowered.com/app/304930/Unturned/) support. diff --git a/src/errors.rs b/src/errors.rs index 099bb0c..148aa7d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -26,8 +26,6 @@ pub enum GDError { UnknownEnumCast, /// The server queried is not from the queried game. BadGame(String), - /// Problems occurred while dns resolving. - DnsResolve(String), /// Couldn't bind a socket. SocketBind(String), /// Invalid input. @@ -53,7 +51,6 @@ impl fmt::Display for GDError { GDError::Decompress(details) => write!(f, "Couldn't decompress data: {details}"), GDError::UnknownEnumCast => write!(f, "Unknown enum cast encountered."), GDError::BadGame(details) => write!(f, "Queried another game that the supposed one: {details}"), - GDError::DnsResolve(details) => write!(f, "DNS Resolve: {details}"), GDError::SocketBind(details) => write!(f, "Socket bind: {details}"), GDError::InvalidInput(details) => write!(f, "Invalid input: {details}"), GDError::SocketConnect(details) => write!(f, "Socket connect: {details}"), diff --git a/src/utils.rs b/src/utils.rs index 178bc07..3fbaa7f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -12,8 +12,8 @@ pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { } } -pub fn address_and_port_as_string(address: &str, port: u16) -> GDResult { - Ok(address.to_string() + ":" + &*port.to_string()) +pub fn address_and_port_as_string(address: &str, port: u16) -> String { + address.to_string() + ":" + &*port.to_string() } pub fn u8_lower_upper(n: u8) -> (u8, u8) { @@ -121,8 +121,7 @@ mod tests { #[test] fn address_and_port_as_string_test() { - assert_eq!(address_and_port_as_string("192.168.0.1", 27015).unwrap(), "192.168.0.1:27015"); - assert!(address_and_port_as_string("not_existent_address", 9999).is_err()); + assert_eq!(address_and_port_as_string("192.168.0.1", 27015), "192.168.0.1:27015"); } #[test] From 013da5f0c4219433b2769f876de87d7ad339d43e Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 01:28:07 +0200 Subject: [PATCH 090/597] Forgot to change address_and_port_as_string in the socket.rs --- src/socket.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socket.rs b/src/socket.rs index b573eda..65765a5 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -21,7 +21,7 @@ pub struct TcpSocket { impl Socket for TcpSocket { fn new(address: &str, port: u16) -> GDResult { - let complete_address = address_and_port_as_string(address, port)?; + let complete_address = address_and_port_as_string(address, port); let socket = net::TcpStream::connect(complete_address).map_err(|e| GDError::SocketConnect(e.to_string()))?; Ok(Self { @@ -57,7 +57,7 @@ pub struct UdpSocket { impl Socket for UdpSocket { fn new(address: &str, port: u16) -> GDResult { - let complete_address = address_and_port_as_string(address, port)?; + let complete_address = address_and_port_as_string(address, port); let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|e| GDError::SocketBind(e.to_string()))?; Ok(Self { From 1ad5031c6f1f411aaf9191e3a4e47b436edb7664 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 01:56:46 +0200 Subject: [PATCH 091/597] Added u8_lower_upper test --- src/utils.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index 3fbaa7f..1f90729 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -124,6 +124,11 @@ mod tests { assert_eq!(address_and_port_as_string("192.168.0.1", 27015), "192.168.0.1:27015"); } + #[test] + fn u8_lower_upper_test() { + assert_eq!(u8_lower_upper(171), (11, 10)); + } + #[test] fn get_u8_test() { let data = [72]; From 1c173b76ca703c201ff0182e416ef8c578344915 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 20:53:12 +0200 Subject: [PATCH 092/597] Day of Infamy support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/doi.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/doi.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c878ae7..55518dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Who knows what the future holds... [Rust](https://store.steampowered.com/app/252490/Rust/) support. [Counter-Strike](https://store.steampowered.com/app/10/CounterStrike/) support. [Arma 2: Operation Arrowhead](https://store.steampowered.com/app/33930/Arma_2_Operation_Arrowhead/) support. +[Day of Infamy](https://store.steampowered.com/app/447820/Day_of_Infamy/) support. Successfully tested `Alien Swarm` and `Insurgency: Modern Infantry Combat`. Restored rules response for `Counter-Strike: Global Offensive` (note: for a full player list response, the cvar `host_players_show` must be set to `2`). Increased Valve Protocol `PACKET_SIZE` from 1400 to 6144 (because some games send larger packets than the specified protocol size). diff --git a/GAMES.md b/GAMES.md index ab3131c..a5a3c39 100644 --- a/GAMES.md +++ b/GAMES.md @@ -28,6 +28,7 @@ A supported game is defined as a game that has been successfully tested, other g | Rust | RUST | Valve Protocol | | | Counter-Strike | CS | Valve Protocol (GoldSrc) | | | Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | +| Day of Infamy | DOI | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 0d6295a..27a5a17 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -56,6 +56,7 @@ fn main() -> GDResult<()> { "rust" => println!("{:#?}", rust::query(ip, port)?), "cs" => println!("{:#?}", cs::query(ip, port)?), "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), + "doi" => println!("{:#?}", doi::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), diff --git a/src/games/doi.rs b/src/games/doi.rs new file mode 100644 index 0000000..f5f8d69 --- /dev/null +++ b/src/games/doi.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::DOI.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index e998e16..d5f7a97 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -53,3 +53,5 @@ pub mod rust; pub mod cs; /// ARMA 2: Operation Arrowhead pub mod arma2oa; +/// Day of Infamy +pub mod doi; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index e83a3e9..c75c8ff 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -183,6 +183,8 @@ pub enum SteamID { SC = 225840, /// Rust RUST = 252490, + /// Day of Infamy + DOI = 447820, /// The Forrest TF = 556450, //this is the id for the dedicated server, for the game its 242760 /// 7 Days To Die From 663fb6a66ebf8788ec14f437f81c35f5383ca63b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 21:20:24 +0200 Subject: [PATCH 093/597] Modified the master_querant example --- examples/master_querant.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 27a5a17..2d77f51 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -21,7 +21,13 @@ fn main() -> GDResult<()> { let ip = args[2].as_str(); let port = match args.len() == 4 { - false => None, + false => { + if args[1].starts_with("_") { + panic!("The port must be specified with an anonymous query.") + } + + None + }, true => Some(args[3].parse::().expect("Invalid port!")) }; @@ -57,9 +63,9 @@ fn main() -> GDResult<()> { "cs" => println!("{:#?}", cs::query(ip, port)?), "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), "doi" => println!("{:#?}", doi::query(ip, port)?), - "_src" => println!("{:#?}", valve::query(ip, 27015, App::Source(None), None, None)?), - "_gld" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(false), None, None)?), - "_gld_f" => println!("{:#?}", valve::query(ip, 27015, App::GoldSrc(true), None, None)?), + "_src" => println!("{:#?}", valve::query(ip, port.unwrap(), App::Source(None), None, None)?), + "_gld" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(false), None, None)?), + "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(true), None, None)?), _ => panic!("Undefined game: {}", args[1]) }; From d671bb0310cd8c6b1d776433c53eb300444b04fe Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 21:33:09 +0200 Subject: [PATCH 094/597] Half-Life Deathmatch: Source support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/hldms.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/hldms.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 55518dd..020a8af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Who knows what the future holds... [Counter-Strike](https://store.steampowered.com/app/10/CounterStrike/) support. [Arma 2: Operation Arrowhead](https://store.steampowered.com/app/33930/Arma_2_Operation_Arrowhead/) support. [Day of Infamy](https://store.steampowered.com/app/447820/Day_of_Infamy/) support. +[Half-Life Deathmatch: Source](https://store.steampowered.com/app/360/HalfLife_Deathmatch_Source/) support. Successfully tested `Alien Swarm` and `Insurgency: Modern Infantry Combat`. Restored rules response for `Counter-Strike: Global Offensive` (note: for a full player list response, the cvar `host_players_show` must be set to `2`). Increased Valve Protocol `PACKET_SIZE` from 1400 to 6144 (because some games send larger packets than the specified protocol size). diff --git a/GAMES.md b/GAMES.md index a5a3c39..223f9c0 100644 --- a/GAMES.md +++ b/GAMES.md @@ -29,6 +29,7 @@ A supported game is defined as a game that has been successfully tested, other g | Counter-Strike | CS | Valve Protocol (GoldSrc) | | | Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | | Day of Infamy | DOI | Valve Protocol | | +| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 2d77f51..2493df4 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -63,6 +63,7 @@ fn main() -> GDResult<()> { "cs" => println!("{:#?}", cs::query(ip, port)?), "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), "doi" => println!("{:#?}", doi::query(ip, port)?), + "hldms" => println!("{:#?}", hldms::query(ip, port)?), "_src" => println!("{:#?}", valve::query(ip, port.unwrap(), App::Source(None), None, None)?), "_gld" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(false), None, None)?), "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(true), None, None)?), diff --git a/src/games/hldms.rs b/src/games/hldms.rs new file mode 100644 index 0000000..862c5e3 --- /dev/null +++ b/src/games/hldms.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::HLDMS.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index d5f7a97..18ca089 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -55,3 +55,5 @@ pub mod cs; pub mod arma2oa; /// Day of Infamy pub mod doi; +/// Half-Life Deathmatch: Source +pub mod hldms; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index c75c8ff..c4e5bbc 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -159,6 +159,8 @@ pub enum SteamID { DODS = 300, /// Half-Life 2 Deathmatch HL2DM = 320, + /// Half-Life Deathmatch: Source + HLDMS = 360, /// Team Fortress 2 TF2 = 440, /// Left 4 Dead From 7828bb943395d48b2750315f3079da782f9d149f Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 22:08:28 +0200 Subject: [PATCH 095/597] Minecraft rework and some docs --- CHANGELOG.md | 3 ++- examples/master_querant.rs | 17 ++++++++------- src/games/mc.rs | 29 +++++++++++++++++++++---- src/protocols/minecraft/protocol/mod.rs | 2 +- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 020a8af..75f4f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ Who knows what the future holds... Successfully tested `Alien Swarm` and `Insurgency: Modern Infantry Combat`. Restored rules response for `Counter-Strike: Global Offensive` (note: for a full player list response, the cvar `host_players_show` must be set to `2`). Increased Valve Protocol `PACKET_SIZE` from 1400 to 6144 (because some games send larger packets than the specified protocol size). -Removed DNS resolving as it was not needed. +Removed DNS resolving as it was not needed. +Valve Protocol minor optimizations. # 0.0.5 - 15/11/2022 Added `SocketBind` error, regarding failing to bind a socket. diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 2493df4..4ab5de0 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, protocols, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::{LegacyGroup, Server}; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -48,11 +48,15 @@ fn main() -> GDResult<()> { "ts" => println!("{:#?}", ts::query(ip, port)?), "cscz" => println!("{:#?}", cscz::query(ip, port)?), "dod" => println!("{:#?}", dod::query(ip, port)?), + "_src" => println!("{:#?}", valve::query(ip, port.unwrap(), App::Source(None), None, None)?), + "_gld" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(false), None, None)?), + "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(true), None, None)?), "mc" => println!("{:#?}", mc::query(ip, port)?), - "mc_java" => println!("{:#?}", mc::query_specific(Server::Java, ip, port)?), - "mc_legacy_vb1_8" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port)?), - "mc_legacy_v1_4" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port)?), - "mc_legacy_v1_6" => println!("{:#?}", mc::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port)?), + "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), + "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), + "_mc_legacy_vb1_8" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port.unwrap(), None)?), + "_mc_legacy_v1_4" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port.unwrap(), None)?), + "_mc_legacy_v1_6" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port.unwrap(), None)?), "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), "ase" => println!("{:#?}", ase::query(ip, port)?), "unturned" => println!("{:#?}", unturned::query(ip, port)?), @@ -64,9 +68,6 @@ fn main() -> GDResult<()> { "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), "doi" => println!("{:#?}", doi::query(ip, port)?), "hldms" => println!("{:#?}", hldms::query(ip, port)?), - "_src" => println!("{:#?}", valve::query(ip, port.unwrap(), App::Source(None), None, None)?), - "_gld" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(false), None, None)?), - "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(true), None, None)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/mc.rs b/src/games/mc.rs index 125150f..e9b68d8 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,13 +1,34 @@ -use crate::GDResult; +use crate::{GDError, GDResult}; use crate::protocols::minecraft; -use crate::protocols::minecraft::{Server, Response}; +use crate::protocols::minecraft::{Server, Response, LegacyGroup}; +/// Query with all the protocol variants one by one (Java -> Legacy (1.6 -> 1.4 -> Beta 1.8)). pub fn query(address: &str, port: Option) -> GDResult { minecraft::query(address, port_or_default(port), None) } -pub fn query_specific(mc_type: Server, address: &str, port: Option) -> GDResult { - minecraft::query_specific(mc_type, address, port_or_default(port), None) +/// Query a Java Server. +pub fn query_java(address: &str, port: Option) -> GDResult { + minecraft::query_specific(Server::Java, address, port_or_default(port), None) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: Option) -> GDResult { + let unwrapped_port = port_or_default(port); + + if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::V1_6), address, unwrapped_port, None) { + return Ok(response); + } + + if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::V1_4), address, unwrapped_port, None) { + return Ok(response); + } + + if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::VB1_8), address, unwrapped_port, None) { + return Ok(response); + } + + Err(GDError::AutoQuery) } fn port_or_default(port: Option) -> u16 { diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 79f539f..bcd7c87 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -11,7 +11,7 @@ mod legacy_v1_4; mod legacy_v1_6; mod legacy_bv1_8; -/// Queries a Minecraft server. +/// Queries a Minecraft server with all the protocol variants one by one (Java -> Legacy (1.6 -> 1.4 -> Beta 1.8)). pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { if let Ok(response) = query_specific(Server::Java, address, port, timeout_settings.clone()) { return Ok(response); From e66fa014ca2eb243c1c91e32ce234a26c49f915d Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 28 Nov 2022 22:10:35 +0200 Subject: [PATCH 096/597] Bumped version to 0.0.6! --- CHANGELOG.md | 4 +++- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f4f5c..a1dafc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ Who knows what the future holds... -# 0.0.6 - ??/??/2022 +# 0.0.7 - ??/??/2022 + +# 0.0.6 - 28/11/2022 [Minecraft](https://www.minecraft.com) support (bedrock not supported yet). [7 Days To Die](https://store.steampowered.com/app/251570/7_Days_to_Die/) support. [ARK: Survival Evolved](https://store.steampowered.com/app/346110/ARK_Survival_Evolved/) support. diff --git a/Cargo.toml b/Cargo.toml index 0ec3c7e..c5b0ab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.0.5" +version = "0.0.6" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" From ae14e37e60e0c898dea1266faaed4967fca7eaa8 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Wed, 30 Nov 2022 23:13:51 +0200 Subject: [PATCH 097/597] Modified README.md: Removed lines of code badge (as it was broken). --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ecbf8a3..49bd483 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) [![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) [![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) -![Lines of code](https://img.shields.io/tokei/lines/github/cosminperram/rust-gamedig?color=blue) [![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) **rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! From 91f8bbb9feb8c33354c21f8b795c8fcf2d4da5f9 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 5 Dec 2022 18:47:35 +0200 Subject: [PATCH 098/597] Minecraft bedrock support (#7) * Added needed ground stuff * Minecraft bedrock support! * Documentation acknowledgements! * Added utf8_le_undended test, some docs and modified master_querant * Modified query function to comply with the others Before: game query -> protocol query (get port or default port) After: game query (get port or default port) -> protocol query * Modified md files --- CHANGELOG.md | 6 ++ GAMES.md | 58 ++++++------ PROTOCOLS.md | 10 +- README.md | 2 +- examples/master_querant.rs | 11 ++- examples/minecraft.rs | 4 +- examples/tf2.rs | 2 +- src/errors.rs | 3 + src/games/mc.rs | 53 +++++++---- src/protocols/minecraft/protocol/bedrock.rs | 100 ++++++++++++++++++++ src/protocols/minecraft/protocol/mod.rs | 60 ++++++++---- src/protocols/minecraft/types.rs | 94 ++++++++++++------ src/utils.rs | 23 +++++ 13 files changed, 319 insertions(+), 107 deletions(-) create mode 100644 src/protocols/minecraft/protocol/bedrock.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a1dafc3..4970d55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Who knows what the future holds... # 0.0.7 - ??/??/2022 +### Changes: +[Minecraft](https://www.minecraft.com) bedrock edition support. +Also added a `query_legacy_specific` method to the mc game queries. + +### Breaking: +Removed `query_specific` from the mc protocol in favor of `query_java`, `query_legacy` and `query_legacy_specific`. # 0.0.6 - 28/11/2022 [Minecraft](https://www.minecraft.com) support (bedrock not supported yet). diff --git a/GAMES.md b/GAMES.md index 223f9c0..712cd02 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,35 +1,35 @@ A supported game is defined as a game that has been successfully tested, other games that use the implemented protocols might work too, but it isn't guaranteed. # Supported games: -| Game | Use name | Protocol | Notes | -|------------------------------------|----------|---------------------------|-------------------------------------------------------------------------------------------| -| Team Fortress 2 | TF2 | Valve Protocol | | -| The Ship | TS | Valve Protocol (*Altered) | | -| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server must have the cvar `host_players_show` set to `2` to get the full player list. | -| Counter-Strike: Source | CSS | Valve Protocol | | -| Day of Defeat: Source | DODS | Valve Protocol | | -| Left 4 Dead | L4D | Valve Protocol | | -| Left 4 Dead 2 | L4D2 | Valve Protocol | | -| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | -| Alien Swarm | ALIENS | Valve Protocol | | -| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | -| Insurgency | INS | Valve Protocol | | -| Insurgency: Sandstorm | INSS | Valve Protocol | Use the query port. | -| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | -| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | -| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | -| Minecraft | MC | Proprietary | Bedrock not supported yet. | -| 7 Days To Die | SDTD | Valve Protocol | | -| ARK: Survival Evolved | ASE | Valve Protocol | | -| Unturned | UNTURNED | Valve Protocol | | -| The Forest | TF | Valve Protocol (GoldSrc) | Use the query port. | -| Team Fortress Classic | TFC | Valve Protocol | | -| Sven Co-op | SC | Valve Protocol (GoldSrc) | | -| Rust | RUST | Valve Protocol | | -| Counter-Strike | CS | Valve Protocol (GoldSrc) | | -| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | -| Day of Infamy | DOI | Valve Protocol | | -| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | +| Game | Use name | Protocol | Notes | +|------------------------------------|----------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Team Fortress 2 | TF2 | Valve Protocol | | +| The Ship | TS | Valve Protocol (*Altered) | | +| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server must have the cvar `host_players_show` set to `2` to get the full player list. | +| Counter-Strike: Source | CSS | Valve Protocol | | +| Day of Defeat: Source | DODS | Valve Protocol | | +| Left 4 Dead | L4D | Valve Protocol | | +| Left 4 Dead 2 | L4D2 | Valve Protocol | | +| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | +| Alien Swarm | ALIENS | Valve Protocol | | +| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | +| Insurgency | INS | Valve Protocol | | +| Insurgency: Sandstorm | INSS | Valve Protocol | Use the query port. | +| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | +| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | +| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | +| Minecraft | MC | Proprietary | Bedrock edition provides a different response compared to the Java edition, query specifically for bedrock to get them, otherwise, only matching fields will be provided. | +| 7 Days To Die | SDTD | Valve Protocol | | +| ARK: Survival Evolved | ASE | Valve Protocol | | +| Unturned | UNTURNED | Valve Protocol | | +| The Forest | TF | Valve Protocol (GoldSrc) | Use the query port. | +| Team Fortress Classic | TFC | Valve Protocol | | +| Sven Co-op | SC | Valve Protocol (GoldSrc) | | +| Rust | RUST | Valve Protocol | | +| Counter-Strike | CS | Valve Protocol (GoldSrc) | | +| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | +| Day of Infamy | DOI | Valve Protocol | | +| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | ## Planned to add support: _ diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 06f3d26..0422ebd 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,10 +1,10 @@ -A protocol is defined as proprietary if it is being used only for a single scope. +A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft). # Supported protocols: -| Name | For | Proprietary? | Documentation reference | Notes | -|----------------|-------|--------------|---------------------------------------------------------------------------|----------------------------------------| -| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | -| Minecraft | Games | Yes | [List Server Protocol](https://wiki.vg/Server_List_Ping) | Bedrock not yet supported. | +| Name | For | Proprietary? | Documentation reference | Notes | +|----------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------| +| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | +| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | ## Planned to add support: _ diff --git a/README.md b/README.md index 49bd483..61760c7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Team Fortress 2 query example: use gamedig::games::tf2; fn main() { - let response = tf2::query("localhost", None); //or Some(27015), None is the default protocol port + let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:#?}", r) diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 4ab5de0..dd181ba 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,7 +1,7 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, protocols, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; -use gamedig::protocols::minecraft::{LegacyGroup, Server}; +use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -53,10 +53,11 @@ fn main() -> GDResult<()> { "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(true), None, None)?), "mc" => println!("{:#?}", mc::query(ip, port)?), "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), + "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), - "_mc_legacy_vb1_8" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port.unwrap(), None)?), - "_mc_legacy_v1_4" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port.unwrap(), None)?), - "_mc_legacy_v1_6" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port.unwrap(), None)?), + "mc_legacy_vb1_8" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::VB1_8, ip, port)?), + "mc_legacy_v1_4" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::V1_4, ip, port)?), + "mc_legacy_v1_6" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::V1_6, ip, port)?), "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), "ase" => println!("{:#?}", ase::query(ip, port)?), "unturned" => println!("{:#?}", unturned::query(ip, port)?), diff --git a/examples/minecraft.rs b/examples/minecraft.rs index 59d34e1..f675508 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -2,7 +2,9 @@ use gamedig::games::mc; fn main() { - let response = mc::query("localhost", None); //or Some(25565), None is the default protocol port (which is 25565) + //or Some(), None is the default protocol port (which is 25565 for java and 19132 for bedrock) + let response = mc::query("127.0.0.1", None); + match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:#?}", r) diff --git a/examples/tf2.rs b/examples/tf2.rs index e79cb7f..f901d01 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -2,7 +2,7 @@ use gamedig::games::tf2; fn main() { - let response = tf2::query("localhost", None); //or Some(27015), None is the default protocol port (which is 27015) + let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port (which is 27015) match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:#?}", r) diff --git a/src/errors.rs b/src/errors.rs index 148aa7d..5f5cae4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -38,6 +38,8 @@ pub enum GDError { AutoQuery, /// A protocol-defined expected format was not met. ProtocolFormat(String), + /// Couldn't parse a value. + TypeParse(String), } impl fmt::Display for GDError { @@ -57,6 +59,7 @@ impl fmt::Display for GDError { GDError::JsonParse(details) => write!(f, "Json parse: {details}"), GDError::AutoQuery => write!(f, "Auto query failed."), GDError::ProtocolFormat(details) => write!(f, "Protocol rule: {details}"), + GDError::TypeParse(details) => write!(f, "Type parse: {details}"), } } } diff --git a/src/games/mc.rs b/src/games/mc.rs index e9b68d8..32443e7 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,39 +1,54 @@ use crate::{GDError, GDResult}; use crate::protocols::minecraft; -use crate::protocols::minecraft::{Server, Response, LegacyGroup}; +use crate::protocols::minecraft::{Response, LegacyGroup, BedrockResponse}; -/// Query with all the protocol variants one by one (Java -> Legacy (1.6 -> 1.4 -> Beta 1.8)). +/// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). pub fn query(address: &str, port: Option) -> GDResult { - minecraft::query(address, port_or_default(port), None) -} - -/// Query a Java Server. -pub fn query_java(address: &str, port: Option) -> GDResult { - minecraft::query_specific(Server::Java, address, port_or_default(port), None) -} - -/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: Option) -> GDResult { - let unwrapped_port = port_or_default(port); - - if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::V1_6), address, unwrapped_port, None) { + if let Ok(response) = query_java(address, port) { return Ok(response); } - if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::V1_4), address, unwrapped_port, None) { - return Ok(response); + if let Ok(response) = query_bedrock(address, port) { + return Ok(Response::from_bedrock_response(response)); } - if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::VB1_8), address, unwrapped_port, None) { + if let Ok(response) = query_legacy(address, port) { return Ok(response); } Err(GDError::AutoQuery) } -fn port_or_default(port: Option) -> u16 { +/// Query a Java Server. +pub fn query_java(address: &str, port: Option) -> GDResult { + minecraft::query_java(address, port_or_java_default(port), None) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: Option) -> GDResult { + minecraft::query_legacy(address, port_or_java_default(port), None) +} + +/// Query a specific (Java) Legacy Server. +pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { + minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) +} + +/// Query a Bedrock Server. +pub fn query_bedrock(address: &str, port: Option) -> GDResult { + minecraft::query_bedrock(address, port_or_bedrock_default(port), None) +} + +fn port_or_java_default(port: Option) -> u16 { match port { None => 25565, Some(port) => port } } + +fn port_or_bedrock_default(port: Option) -> u16 { + match port { + None => 19132, + Some(port) => port + } +} diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs new file mode 100644 index 0000000..f406f4f --- /dev/null +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -0,0 +1,100 @@ + +/* +This file has code that has been documented by the NodeJS GameDig library (MIT) from +https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js +*/ + +use crate::{GDError, GDResult}; +use crate::protocols::minecraft::{BedrockResponse, GameMode, Server}; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, UdpSocket}; +use crate::utils::buffer::{get_string_utf8_le_unended, get_u16_be, get_u64_le, get_u8}; +use crate::utils::error_by_expected_size; + +pub struct Bedrock { + socket: UdpSocket +} + +impl Bedrock { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { + socket + }) + } + + fn send_status_request(&mut self) -> GDResult<()> { + self.socket.send(&[ + // Message ID, ID_UNCONNECTED_PING + 0x01, + // Nonce / timestamp + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + // Magic + 0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78, + // Client GUID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])?; + + Ok(()) + } + + fn get_info(&mut self) -> GDResult { + self.send_status_request()?; + + let buf = self.socket.receive(None)?; + let mut pos = 0; + + if get_u8(&buf, &mut pos)? != 0x1c { + return Err(GDError::PacketBad("Invalid message id.".to_string())); + } + + // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). + if get_u64_le(&buf, &mut pos)? != 9833440827789222417 { + return Err(GDError::PacketBad("Invalid nonce.".to_string())); + } + + // These 8 bytes are identical to the serverId string we receive in decimal below + pos += 8; + + // Verifying the magic value (as we need 16 bytes, cast to two u64 values) + if get_u64_le(&buf, &mut pos)? != 18374403896610127616 { + return Err(GDError::PacketBad("Invalid magic (part 1).".to_string())); + } + + if get_u64_le(&buf, &mut pos)? != 8671175388723805693 { + return Err(GDError::PacketBad("Invalid magic (part 2).".to_string())); + } + + let remaining_length = get_u16_be(&buf, &mut pos)? as usize; + error_by_expected_size(remaining_length, buf.len() - pos)?; + + let binding = get_string_utf8_le_unended(&buf, &mut pos)?; + let status: Vec<&str> = binding.split(";").collect(); + + // We must have at least 6 values + if status.len() < 6 { + return Err(GDError::PacketBad("Not enough status parts.".to_string())); + } + + Ok(BedrockResponse { + edition: status[0].to_string(), + name: status[1].to_string(), + version_name: status[3].to_string(), + version_protocol: status[2].to_string(), + max_players: status[5].parse().map_err(|_| GDError::TypeParse("couldn't parse.".to_string()))?, + online_players: status[4].parse().map_err(|_| GDError::TypeParse("couldn't parse.".to_string()))?, + id: status.get(6).and_then(|v| Some(v.to_string())), + map: status.get(7).and_then(|v| Some(v.to_string())), + game_mode: match status.get(8) { + None => None, + Some(v) => Some(GameMode::from_bedrock(v)?) + }, + server_type: Server::Bedrock + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Bedrock::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index bcd7c87..a3ae26a 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,5 +1,6 @@ use crate::{GDError, GDResult}; -use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::minecraft::{BedrockResponse, LegacyGroup, Response}; +use crate::protocols::minecraft::protocol::bedrock::Bedrock; use crate::protocols::minecraft::protocol::java::Java; use crate::protocols::minecraft::protocol::legacy_v1_4::LegacyV1_4; use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; @@ -10,36 +11,57 @@ mod java; mod legacy_v1_4; mod legacy_v1_6; mod legacy_bv1_8; +mod bedrock; -/// Queries a Minecraft server with all the protocol variants one by one (Java -> Legacy (1.6 -> 1.4 -> Beta 1.8)). +/// Queries a Minecraft server with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - if let Ok(response) = query_specific(Server::Java, address, port, timeout_settings.clone()) { + if let Ok(response) = query_java(address, port, timeout_settings.clone()) { return Ok(response); } - if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::V1_6), address, port, timeout_settings.clone()) { - return Ok(response); + if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) { + return Ok(Response::from_bedrock_response(response)); } - if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::V1_4), address, port, timeout_settings.clone()) { - return Ok(response); - } - - if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::VB1_8), address, port, timeout_settings.clone()) { + if let Ok(response) = query_legacy(address, port, timeout_settings) { return Ok(response); } Err(GDError::AutoQuery) } -/// Queries a specific Minecraft Server type. -pub fn query_specific(mc_type: Server, address: &str, port: u16, timeout_settings: Option) -> GDResult { - match mc_type { - Server::Java => Java::query(address, port, timeout_settings), - Server::Legacy(category) => match category { - LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), - LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), - LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings), - } +/// Query a Java Server. +pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Java::query(address, port, timeout_settings) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { + if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_legacy_specific(LegacyGroup::V1_4, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_legacy_specific(LegacyGroup::VB1_8, address, port, timeout_settings) { + return Ok(response); + } + + Err(GDError::AutoQuery) +} + +/// Query a specific (Java) Legacy Server. +pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: u16, timeout_settings: Option) -> GDResult { + match group { + LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), + LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), + LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings) } } + +/// Query a Bedrock Server. +pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Bedrock::query(address, port, timeout_settings) +} diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index 46cfa0c..05999a4 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -1,31 +1,8 @@ /* - -This file contains lightly modified versions of the original code. (using only the varint parts) -Code reference: https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs - -MIT License - -Copyright (c) 2021-2022 Jaiden Bernard - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - +Although its a lightly modified version, this file contains code +by Jaiden Bernard (2021-2022 - MIT) from +https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs */ use crate::{GDError, GDResult}; @@ -37,7 +14,9 @@ pub enum Server { /// Java Edition. Java, /// Legacy Java. - Legacy(LegacyGroup) + Legacy(LegacyGroup), + /// Bedrock Edition. + Bedrock } /// Legacy Java (Versions) Groups. @@ -83,6 +62,67 @@ pub struct Response { pub server_type: Server } +/// A Bedrock Edition query response. +#[derive(Debug)] +pub struct BedrockResponse { + /// Server edition. + pub edition: String, + /// Server name. + pub name: String, + /// Version name, example: "1.19.40". + pub version_name: String, + /// Version protocol, example: 760 (for 1.19.2). + pub version_protocol: String, + /// Number of server capacity. + pub max_players: u32, + /// Number of online players. + pub online_players: u32, + /// Server id. + pub id: Option, + /// The map. + pub map: Option, + /// Game mode. + pub game_mode: Option, + /// Tell's the server type. + pub server_type: Server +} + +impl Response { + pub fn from_bedrock_response(response: BedrockResponse) -> Self { + Self { + version_name: response.version_name, + version_protocol: 0, + max_players: response.max_players, + online_players: response.online_players, + sample_players: None, + description: response.name, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Bedrock + } + } +} + +/// A server's game mode (used only by Bedrock servers). +#[derive(Debug)] +pub enum GameMode { + Survival, Creative, Hardcore, Spectator, Adventure +} + +impl GameMode { + pub fn from_bedrock(value: &&str) -> GDResult { + match *value { + "Survival" => Ok(GameMode::Survival), + "Creative" => Ok(GameMode::Creative), + "Hardcore" => Ok(GameMode::Hardcore), + "Spectator" => Ok(GameMode::Spectator), + "Adventure" => Ok(GameMode::Adventure), + _ => Err(GDError::UnknownEnumCast) + } + } +} + pub fn get_varint(buf: &[u8], pos: &mut usize) -> GDResult { let mut result = 0; diff --git a/src/utils.rs b/src/utils.rs index 1f90729..8f4c7f5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -98,6 +98,19 @@ pub mod buffer { Ok(value) } + pub fn get_string_utf8_le_unended(buf: &[u8], pos: &mut usize) -> GDResult { + let sub_buf = &buf[*pos..]; + if sub_buf.len() == 0 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 LE string.".to_string())); + } + + let value = std::str::from_utf8(&sub_buf) + .map_err(|_| GDError::PacketBad("Badly formatted utf8 LE string.".to_string()))?.to_string(); + + *pos += value.len(); + Ok(value) + } + pub fn get_string_utf16_be(buf: &[u8], pos: &mut usize) -> GDResult { let sub_buf = &buf[*pos..]; if sub_buf.len() == 0 { @@ -199,6 +212,16 @@ mod tests { assert_eq!(pos, 6); } + #[test] + fn get_string_utf8_le_unended_test() { + let data = [72, 101, 108, 108, 111]; + let mut pos = 0; + assert_eq!(buffer::get_string_utf8_le_unended(&data, &mut pos).unwrap(), "Hello"); + assert_eq!(pos, 5); + assert!(buffer::get_string_utf8_le_unended(&data, &mut pos).is_err()); + assert_eq!(pos, 5); + } + #[test] fn get_string_utf16_be_test() { let data = [0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f]; From b09fa4ada5cf044f54d55ae8a7d70e7e948953a6 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 29 Dec 2022 16:30:24 +0200 Subject: [PATCH 099/597] Change buffer reading implementation (#8) * Add new implementation an valve protocol refactor * Refactor minecraft protocol with new bufferer --- src/bufferer.rs | 304 ++++++++++++++++++ src/lib.rs | 1 + src/protocols/minecraft/protocol/bedrock.rs | 22 +- src/protocols/minecraft/protocol/java.rs | 17 +- .../minecraft/protocol/legacy_bv1_8.rs | 13 +- .../minecraft/protocol/legacy_v1_4.rs | 17 +- .../minecraft/protocol/legacy_v1_6.rs | 25 +- src/protocols/minecraft/types.rs | 12 +- src/protocols/valve/protocol.rs | 181 +++++------ src/utils.rs | 216 +------------ 10 files changed, 455 insertions(+), 353 deletions(-) create mode 100644 src/bufferer.rs diff --git a/src/bufferer.rs b/src/bufferer.rs new file mode 100644 index 0000000..20feb0d --- /dev/null +++ b/src/bufferer.rs @@ -0,0 +1,304 @@ +use crate::{GDError, GDResult}; + +pub enum Endianess { + Little, Big +} + +pub struct Bufferer { + data: Vec, + endianess: Endianess, + position: usize +} + +impl Bufferer { + pub fn new(endianess: Endianess) -> Self { + Bufferer::new_with_data(endianess, &[]) + } + + pub fn new_with_data(endianess: Endianess, data: &[u8]) -> Self { + Bufferer { + data: data.to_vec(), + endianess, + position: 0 + } + } + + fn check_size(&self, by: usize) -> bool { + by > self.remaining_length() + } + + pub fn get_u8(&mut self) -> GDResult { + if self.check_size(1) { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u8.".to_string())); + } + + let value = self.data[self.position]; + self.position += 1; + Ok(value) + } + + pub fn get_u16(&mut self) -> GDResult { + if self.check_size(2) { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u16.".to_string())); + } + + let source_data: [u8; 2] = (&self.data[self.position..self.position + 2]).try_into().unwrap(); + + let value = match self.endianess { + Endianess::Little => u16::from_le_bytes(source_data), + Endianess::Big => u16::from_be_bytes(source_data) + }; + + self.position += 2; + Ok(value) + } + + pub fn get_u32(&mut self) -> GDResult { + if self.check_size(4) { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u32.".to_string())); + } + + let source_data: [u8; 4] = (&self.data[self.position..self.position + 4]).try_into().unwrap(); + + let value = match self.endianess { + Endianess::Little => u32::from_le_bytes(source_data), + Endianess::Big => u32::from_be_bytes(source_data) + }; + + self.position += 4; + Ok(value) + } + + pub fn get_f32(&mut self) -> GDResult { + if self.check_size(4) { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an f32.".to_string())); + } + + let source_data: [u8; 4] = (&self.data[self.position..self.position + 4]).try_into().unwrap(); + + let value = match self.endianess { + Endianess::Little => f32::from_le_bytes(source_data), + Endianess::Big => f32::from_be_bytes(source_data) + }; + + self.position += 4; + Ok(value) + } + + pub fn get_u64(&mut self) -> GDResult { + if self.check_size(8) { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u64.".to_string())); + } + + let source_data: [u8; 8] = (&self.data[self.position..self.position + 8]).try_into().unwrap(); + + let value = match self.endianess { + Endianess::Little => u64::from_le_bytes(source_data), + Endianess::Big => u64::from_be_bytes(source_data) + }; + + self.position += 8; + Ok(value) + } + + pub fn get_string_utf8(&mut self) -> GDResult { + let sub_buf = &self.data[self.position..]; + if sub_buf.len() == 0 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 string.".to_string())); + } + + let first_null_position = sub_buf.iter().position(|&x| x == 0) + .ok_or(GDError::PacketBad("Unexpectedly formatted packet for getting an utf8 string.".to_string()))?; + let value = std::str::from_utf8(&sub_buf[..first_null_position]) + .map_err(|_| GDError::PacketBad("Badly formatted utf8 string.".to_string()))?.to_string(); + + self.position += value.len() + 1; + Ok(value) + } + + pub fn get_string_utf8_unended(&mut self) -> GDResult { + let sub_buf = &self.data[self.position..]; + if sub_buf.len() == 0 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 unended string.".to_string())); + } + + let value = std::str::from_utf8(&sub_buf) + .map_err(|_| GDError::PacketBad("Badly formatted utf8 unended string.".to_string()))?.to_string(); + + self.position += value.len(); + Ok(value) + } + + pub fn get_string_utf16(&mut self) -> GDResult { + let sub_buf = &self.data[self.position..]; + if sub_buf.len() == 0 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf16 string.".to_string())); + } + + let paired_buf: Vec = sub_buf.chunks_exact(2) + .into_iter().map(|a| match self.endianess { + Endianess::Little => u16::from_le_bytes([a[0], a[1]]), + Endianess::Big => u16::from_be_bytes([a[0], a[1]]) + }).collect(); + + let value = String::from_utf16(&paired_buf) + .map_err(|_| GDError::PacketBad("Badly formatted utf16 string.".to_string()))?.to_string(); + + self.position += value.len() * 2; + Ok(value) + } + + pub fn move_position_ahead(&mut self, by: usize) { + self.position += by; + } + + pub fn move_position_backward(&mut self, by: usize) { + self.position -= by; + } + + pub fn get_data_in_front_of_position(&self) -> Vec { + self.data[self.position..].to_vec() + } + + pub fn data_length(&self) -> usize { + self.data.len() + } + + pub fn remaining_data(&self) -> &[u8] { + &self.data[self.position..] + } + + pub fn remaining_length(&self) -> usize { + self.data.len() - self.position + } + + pub fn as_endianess(&self, endianess: Endianess) -> Self { + Bufferer { + data: self.data.clone(), + endianess, + position: self.position, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_u8() { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72]); + + assert_eq!(buffer.get_u8().unwrap(), 72); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_u8().is_err()); + } + + #[test] + fn get_u16_le() { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 79]); + + assert_eq!(buffer.get_u16().unwrap(), 20296); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_u16().is_err()); + } + + #[test] + fn get_u16_be() { + let mut buffer = Bufferer::new_with_data(Endianess::Big, &[29, 72]); + + assert_eq!(buffer.get_u16().unwrap(), 7496); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_u16().is_err()); + } + + #[test] + fn get_u32_le() { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 100]); + + assert_eq!(buffer.get_u32().unwrap(), 1686117704); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_u32().is_err()); + } + + #[test] + fn get_u32_be() { + let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 100]); + + assert_eq!(buffer.get_u32().unwrap(), 1209892964); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_u32().is_err()); + } + + #[test] + fn get_f32_le() { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 100]); + + assert_eq!(buffer.get_f32().unwrap(), 1.8906345e22); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_f32().is_err()); + } + + #[test] + fn get_f32_be() { + let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 100]); + + assert_eq!(buffer.get_f32().unwrap(), 161281.56); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_f32().is_err()); + } + + #[test] + fn get_u64_le() { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 99, 69, 4, 2, 0]); + + assert_eq!(buffer.get_u64().unwrap(), 567646022016328); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_u64().is_err()); + } + + #[test] + fn get_u64_be() { + let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 99, 69, 4, 2, 0]); + + assert_eq!(buffer.get_u64().unwrap(), 5196450708903428608); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_u64().is_err()); + } + + #[test] + fn get_string_utf8() { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 101, 108, 108, 111, 0, 72]); + + assert_eq!(buffer.get_string_utf8().unwrap(), "Hello"); + assert_eq!(buffer.remaining_length(), 1); + assert!(buffer.get_string_utf8().is_err()); + } + + #[test] + fn get_string_utf8_unended() { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 101, 108, 108, 111]); + + assert_eq!(buffer.get_string_utf8_unended().unwrap(), "Hello"); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_string_utf8_unended().is_err()); + } + + #[test] + fn get_string_utf16_le() { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &[0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00]); + + assert_eq!(buffer.get_string_utf16().unwrap(), "Hello"); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_string_utf16().is_err()); + } + + #[test] + fn get_string_utf16_be() { + let mut buffer = Bufferer::new_with_data(Endianess::Big, &[0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f]); + + assert_eq!(buffer.get_string_utf16().unwrap(), "Hello"); + assert_eq!(buffer.remaining_length(), 0); + assert!(buffer.get_string_utf16().is_err()); + } +} diff --git a/src/lib.rs b/src/lib.rs index f2590ab..b892c3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ pub mod protocols; pub mod games; mod utils; mod socket; +mod bufferer; pub use errors::*; pub use games::*; diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index f406f4f..50ff0ca 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -5,10 +5,10 @@ https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.j */ use crate::{GDError, GDResult}; +use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::minecraft::{BedrockResponse, GameMode, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; -use crate::utils::buffer::{get_string_utf8_le_unended, get_u16_be, get_u64_le, get_u8}; use crate::utils::error_by_expected_size; pub struct Bedrock { @@ -42,34 +42,34 @@ impl Bedrock { fn get_info(&mut self) -> GDResult { self.send_status_request()?; - let buf = self.socket.receive(None)?; - let mut pos = 0; + let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); - if get_u8(&buf, &mut pos)? != 0x1c { + if buffer.get_u8()? != 0x1c { return Err(GDError::PacketBad("Invalid message id.".to_string())); } // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). - if get_u64_le(&buf, &mut pos)? != 9833440827789222417 { + if buffer.get_u64()? != 9833440827789222417 { return Err(GDError::PacketBad("Invalid nonce.".to_string())); } // These 8 bytes are identical to the serverId string we receive in decimal below - pos += 8; + buffer.move_position_ahead(8); // Verifying the magic value (as we need 16 bytes, cast to two u64 values) - if get_u64_le(&buf, &mut pos)? != 18374403896610127616 { + if buffer.get_u64()? != 18374403896610127616 { return Err(GDError::PacketBad("Invalid magic (part 1).".to_string())); } - if get_u64_le(&buf, &mut pos)? != 8671175388723805693 { + if buffer.get_u64()? != 8671175388723805693 { return Err(GDError::PacketBad("Invalid magic (part 2).".to_string())); } - let remaining_length = get_u16_be(&buf, &mut pos)? as usize; - error_by_expected_size(remaining_length, buf.len() - pos)?; + let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize; + buffer.move_position_ahead(2); + error_by_expected_size(remaining_length, buffer.remaining_length())?; - let binding = get_string_utf8_le_unended(&buf, &mut pos)?; + let binding = buffer.get_string_utf8_unended()?; let status: Vec<&str> = binding.split(";").collect(); // We must have at least 6 values diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index 06b87f6..c042241 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -1,5 +1,6 @@ use serde_json::Value; use crate::{GDError, GDResult}; +use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::minecraft::{as_varint, get_string, get_varint, Player, Response, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; @@ -22,14 +23,13 @@ impl Java { self.socket.send(&[as_varint(data.len() as i32), data].concat()) } - fn receive(&mut self) -> GDResult> { - let buf = self.socket.receive(None)?; - let mut pos = 0; + fn receive(&mut self) -> GDResult { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); - let _packet_length = get_varint(&buf, &mut pos)? as usize; + let _packet_length = get_varint(&mut buffer)? as usize; //this declared 'packet length' from within the packet might be wrong (?), not checking with it... - Ok(buf[pos..].to_vec()) + Ok(buffer) } fn send_handshake(&mut self) -> GDResult<()> { @@ -69,14 +69,13 @@ impl Java { self.send_status_request()?; self.send_ping_request()?; - let buf = self.receive()?; - let mut pos = 0; + let mut buffer = self.receive()?; - if get_varint(&buf, &mut pos)? != 0 { //first var int is the packet id + if get_varint(&mut buffer)? != 0 { //first var int is the packet id return Err(GDError::PacketBad("Bad receive packet id.".to_string())); } - let json_response = get_string(&buf, &mut pos)?; + let json_response = get_string(&mut buffer)?; let value_response: Value = serde_json::from_str(&json_response) .map_err(|e| GDError::JsonParse(e.to_string()))?; diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index 430f38e..0da7d6e 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -1,9 +1,9 @@ use crate::{GDError, GDResult}; +use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::minecraft::{LegacyGroup, Response, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; -use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; use crate::utils::error_by_expected_size; pub struct LegacyBV1_8 { @@ -27,17 +27,16 @@ impl LegacyBV1_8 { fn get_info(&mut self) -> GDResult { self.send_initial_request()?; - let buf = self.socket.receive(None)?; - let mut pos = 0; + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - if get_u8(&buf, &mut pos)? != 0xFF { + if buffer.get_u8()? != 0xFF { return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.".to_string())); } - let length = get_u16_be(&buf, &mut pos)? * 2; - error_by_expected_size((length + 3) as usize, buf.len())?; + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; - let packet_string = get_string_utf16_be(&buf, &mut pos)?; + let packet_string = buffer.get_string_utf16()?; let split: Vec<&str> = packet_string.split("§").collect(); error_by_expected_size(3, split.len())?; diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index 4c2ab08..108b53d 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -1,10 +1,10 @@ use crate::{GDError, GDResult}; +use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::minecraft::{LegacyGroup, Response, Server}; use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; -use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; use crate::utils::error_by_expected_size; pub struct LegacyV1_4 { @@ -28,21 +28,20 @@ impl LegacyV1_4 { fn get_info(&mut self) -> GDResult { self.send_initial_request()?; - let buf = self.socket.receive(None)?; - let mut pos = 0; + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - if get_u8(&buf, &mut pos)? != 0xFF { + if buffer.get_u8()? != 0xFF { return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.".to_string())); } - let length = get_u16_be(&buf, &mut pos)? * 2; - error_by_expected_size((length + 3) as usize, buf.len())?; + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; - if LegacyV1_6::is_protocol(&buf, &mut pos)? { - return LegacyV1_6::get_response(&buf, &mut pos); + if LegacyV1_6::is_protocol(&mut buffer)? { + return LegacyV1_6::get_response(&mut buffer); } - let packet_string = get_string_utf16_be(&buf, &mut pos)?; + let packet_string = buffer.get_string_utf16()?; let split: Vec<&str> = packet_string.split("§").collect(); error_by_expected_size(3, split.len())?; diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 0034a39..9bd14a4 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -1,8 +1,8 @@ use crate::{GDError, GDResult}; +use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::minecraft::{LegacyGroup, Response, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; -use crate::utils::buffer::{get_string_utf16_be, get_u16_be, get_u8}; use crate::utils::error_by_expected_size; pub struct LegacyV1_6 { @@ -35,18 +35,18 @@ impl LegacyV1_6 { Ok(()) } - pub fn is_protocol(buf: &[u8], pos: &mut usize) -> GDResult { - let state = buf[*pos..].starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]); + pub fn is_protocol(buffer: &mut Bufferer) -> GDResult { + let state = buffer.remaining_data().starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]); if state { - *pos += 6; + buffer.move_position_ahead(6); } Ok(state) } - pub fn get_response(buf: &[u8], pos: &mut usize) -> GDResult { - let packet_string = get_string_utf16_be(&buf, pos)?; + pub fn get_response(buffer: &mut Bufferer) -> GDResult { + let packet_string = buffer.get_string_utf16()?; let split: Vec<&str> = packet_string.split("\x00").collect(); error_by_expected_size(5, split.len())?; @@ -77,21 +77,20 @@ impl LegacyV1_6 { fn get_info(&mut self) -> GDResult { self.send_initial_request()?; - let buf = self.socket.receive(None)?; - let mut pos = 0; + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - if get_u8(&buf, &mut pos)? != 0xFF { + if buffer.get_u8()? != 0xFF { return Err(GDError::ProtocolFormat("Expected a certain byte (0xFF) at the begin of the packet.".to_string())); } - let length = get_u16_be(&buf, &mut pos)? * 2; - error_by_expected_size((length + 3) as usize, buf.len())?; + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; - if !LegacyV1_6::is_protocol(&buf, &mut pos)? { + if !LegacyV1_6::is_protocol(&mut buffer)? { return Err(GDError::ProtocolFormat("Expected certain bytes at the beginning of the packet.".to_string())); } - LegacyV1_6::get_response(&buf, &mut pos) + LegacyV1_6::get_response(&mut buffer) } pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index 05999a4..74976c3 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -6,7 +6,7 @@ https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs */ use crate::{GDError, GDResult}; -use crate::utils::buffer::get_u8; +use crate::bufferer::Bufferer; /// The type of Minecraft Server you want to query. #[derive(Debug)] @@ -123,14 +123,14 @@ impl GameMode { } } -pub fn get_varint(buf: &[u8], pos: &mut usize) -> GDResult { +pub fn get_varint(buffer: &mut Bufferer) -> GDResult { let mut result = 0; let msb: u8 = 0b10000000; let mask: u8 = !msb; for i in 0..5 { - let current_byte = get_u8(buf, pos)?; + let current_byte = buffer.get_u8()?; result |= ((current_byte & mask) as i32) << (7 * i); @@ -171,12 +171,12 @@ pub fn as_varint(value: i32) -> Vec { bytes } -pub fn get_string(buf: &[u8], pos: &mut usize) -> GDResult { - let length = get_varint(buf, pos)? as usize; +pub fn get_string(buffer: &mut Bufferer) -> GDResult { + let length = get_varint(buffer)? as usize; let mut text = vec![0; length]; for i in 0..length { - text[i] = get_u8(buf, pos)?; + text[i] = buffer.get_u8()?; } Ok(String::from_utf8(text) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index c1985ba..e4fc464 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,10 +1,11 @@ use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; +use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::types::TimeoutSettings; use crate::protocols::valve::{App, ModData, SteamID}; use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip}; use crate::socket::{Socket, UdpSocket}; -use crate::utils::{buffer, u8_lower_upper}; +use crate::utils::u8_lower_upper; #[derive(Debug, Clone)] struct Packet { @@ -14,12 +15,11 @@ struct Packet { } impl Packet { - fn new(buf: &[u8]) -> GDResult { - let mut pos = 0; + fn new(buffer: &mut Bufferer) -> GDResult { Ok(Self { - header: buffer::get_u32_le(&buf, &mut pos)?, - kind: buffer::get_u8(&buf, &mut pos)?, - payload: buf[pos..].to_vec() + header: buffer.get_u32()?, + kind: buffer.get_u8()?, + payload: buffer.get_data_in_front_of_position() }) } @@ -75,27 +75,25 @@ struct SplitPacket { } impl SplitPacket { - fn new(app: &App, protocol: u8, buf: &[u8]) -> GDResult { - let mut pos = 0; - - let header = buffer::get_u32_le(&buf, &mut pos)?; - let id = buffer::get_u32_le(&buf, &mut pos)?; + fn new(app: &App, protocol: u8, buffer: &mut Bufferer) -> GDResult { + let header = buffer.get_u32()?; + let id = buffer.get_u32()?; let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match app { App::GoldSrc(_) => { - let (lower, upper) = u8_lower_upper(buffer::get_u8(&buf, &mut pos)?); + let (lower, upper) = u8_lower_upper(buffer.get_u8()?); (lower, upper, 0, false, None, None) } App::Source(_) => { - let total = buffer::get_u8(&buf, &mut pos)?; - let number = buffer::get_u8(&buf, &mut pos)?; + let total = buffer.get_u8()?; + let number = buffer.get_u8()?; let size = match protocol == 7 && (*app == SteamID::CSS.as_app()) { //certain apps with protocol = 7 doesnt have this field - false => buffer::get_u16_le(&buf, &mut pos)?, + false => buffer.get_u16()?, true => 1248 }; let compressed = ((id >> 31) & 1) == 1; let (decompressed_size, uncompressed_crc32) = match compressed { false => (None, None), - true => (Some(buffer::get_u32_le(&buf, &mut pos)?), Some(buffer::get_u32_le(&buf, &mut pos)?)) + true => (Some(buffer.get_u32()?), Some(buffer.get_u32()?)) }; (total, number, size, compressed, decompressed_size, uncompressed_crc32) } @@ -110,7 +108,7 @@ impl SplitPacket { compressed, decompressed_size, uncompressed_crc32, - payload: buf[pos..].to_vec() + payload: buffer.get_data_in_front_of_position() }) } @@ -156,21 +154,26 @@ impl ValveProtocol { } fn receive(&mut self, app: &App, protocol: u8, buffer_size: usize) -> GDResult { - let mut buf = self.socket.receive(Some(buffer_size))?; + let data = self.socket.receive(Some(buffer_size))?; + let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); - if buf[0] == 0xFE { //the packet is split - let mut main_packet = SplitPacket::new(&app, protocol, &buf)?; + let header = buffer.get_u8()?; + buffer.move_position_backward(1); + if header == 0xFE { //the packet is split + let mut main_packet = SplitPacket::new(&app, protocol, &mut buffer)?; for _ in 1..main_packet.total { - buf = self.socket.receive(Some(buffer_size))?; - let chunk_packet = SplitPacket::new(&app, protocol, &buf)?; + let new_data = self.socket.receive(Some(buffer_size))?; + buffer = Bufferer::new_with_data(Endianess::Little, &new_data); + let chunk_packet = SplitPacket::new(&app, protocol, &mut buffer)?; main_packet.payload.extend(chunk_packet.payload); } - Ok(Packet::new(&main_packet.get_payload()?)?) + let mut new_packet_buffer = Bufferer::new_with_data(Endianess::Little, &main_packet.get_payload()?); + Ok(Packet::new(&mut new_packet_buffer)?) } else { - Packet::new(&buf) + Packet::new(&mut buffer) } } @@ -192,44 +195,42 @@ impl ValveProtocol { Ok(self.receive(app, protocol, PACKET_SIZE)?.payload) } - fn get_goldsrc_server_info(buf: &[u8]) -> GDResult { - let mut pos = 0; - - buffer::get_u8(&buf, &mut pos)?; //get the header (useless info) - buffer::get_string_utf8_le(&buf, &mut pos)?; //get the server address (useless info) - let name = buffer::get_string_utf8_le(&buf, &mut pos)?; - let map = buffer::get_string_utf8_le(&buf, &mut pos)?; - let folder = buffer::get_string_utf8_le(&buf, &mut pos)?; - let game = buffer::get_string_utf8_le(&buf, &mut pos)?; - let players = buffer::get_u8(&buf, &mut pos)?; - let max_players = buffer::get_u8(&buf, &mut pos)?; - let protocol = buffer::get_u8(&buf, &mut pos)?; - let server_type = match buffer::get_u8(&buf, &mut pos)? { + fn get_goldsrc_server_info(buffer: &mut Bufferer) -> GDResult { + buffer.get_u8()?; //get the header (useless info) + buffer.get_string_utf8()?; //get the server address (useless info) + let name = buffer.get_string_utf8()?; + let map = buffer.get_string_utf8()?; + let folder = buffer.get_string_utf8()?; + let game = buffer.get_string_utf8()?; + let players = buffer.get_u8()?; + let max_players = buffer.get_u8()?; + let protocol = buffer.get_u8()?; + let server_type = match buffer.get_u8()? { 68 => Server::Dedicated, //'D' 76 => Server::NonDedicated, //'L' 80 => Server::TV, //'P' _ => Err(GDError::UnknownEnumCast)? }; - let environment_type = match buffer::get_u8(&buf, &mut pos)? { + let environment_type = match buffer.get_u8()? { 76 => Environment::Linux, //'L' 87 => Environment::Windows, //'W' _ => Err(GDError::UnknownEnumCast)? }; - let has_password = buffer::get_u8(&buf, &mut pos)? == 1; - let is_mod = buffer::get_u8(&buf, &mut pos)? == 1; + let has_password = buffer.get_u8()? == 1; + let is_mod = buffer.get_u8()? == 1; let mod_data = match is_mod { false => None, true => Some(ModData { - link: buffer::get_string_utf8_le(&buf, &mut pos)?, - download_link: buffer::get_string_utf8_le(&buf, &mut pos)?, - version: buffer::get_u32_le(&buf, &mut pos)?, - size: buffer::get_u32_le(&buf, &mut pos)?, - multiplayer_only: buffer::get_u8(&buf, &mut pos)? == 1, - has_own_dll: buffer::get_u8(&buf, &mut pos)? == 1 + link: buffer.get_string_utf8()?, + download_link: buffer.get_string_utf8()?, + version: buffer.get_u32()?, + size: buffer.get_u32()?, + multiplayer_only: buffer.get_u8()? == 1, + has_own_dll: buffer.get_u8()? == 1 }) }; - let vac_secured = buffer::get_u8(&buf, &mut pos)? == 1; - let bots = buffer::get_u8(&buf, &mut pos)?; + let vac_secured = buffer.get_u8()? == 1; + let bots = buffer.get_u8()?; Ok(ServerInfo { protocol, @@ -255,74 +256,74 @@ impl ValveProtocol { /// Get the server information's. fn get_server_info(&mut self, app: &App) -> GDResult { - let buf = self.get_request_data(&app, 0, Request::INFO)?; + let data = self.get_request_data(&app, 0, Request::INFO)?; + let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); + if let App::GoldSrc(force) = app { if *force { - return ValveProtocol::get_goldsrc_server_info(&buf); + return ValveProtocol::get_goldsrc_server_info(&mut buffer); } } - let mut pos = 0; - - let protocol = buffer::get_u8(&buf, &mut pos)?; - let name = buffer::get_string_utf8_le(&buf, &mut pos)?; - let map = buffer::get_string_utf8_le(&buf, &mut pos)?; - let folder = buffer::get_string_utf8_le(&buf, &mut pos)?; - let game = buffer::get_string_utf8_le(&buf, &mut pos)?; - let mut appid = buffer::get_u16_le(&buf, &mut pos)? as u32; - let players = buffer::get_u8(&buf, &mut pos)?; - let max_players = buffer::get_u8(&buf, &mut pos)?; - let bots = buffer::get_u8(&buf, &mut pos)?; - let server_type = match buffer::get_u8(&buf, &mut pos)? { + let protocol = buffer.get_u8()?; + let name = buffer.get_string_utf8()?; + let map = buffer.get_string_utf8()?; + let folder = buffer.get_string_utf8()?; + let game = buffer.get_string_utf8()?; + let mut appid = buffer.get_u16()? as u32; + let players = buffer.get_u8()?; + let max_players = buffer.get_u8()?; + let bots = buffer.get_u8()?; + let server_type = match buffer.get_u8()? { 100 => Server::Dedicated, //'d' 108 => Server::NonDedicated, //'l' 112 => Server::TV, //'p' _ => Err(GDError::UnknownEnumCast)? }; - let environment_type = match buffer::get_u8(&buf, &mut pos)? { + let environment_type = match buffer.get_u8()? { 108 => Environment::Linux, //'l' 119 => Environment::Windows, //'w' 109 | 111 => Environment::Mac, //'m' or 'o' _ => Err(GDError::UnknownEnumCast)? }; - let has_password = buffer::get_u8(&buf, &mut pos)? == 1; - let vac_secured = buffer::get_u8(&buf, &mut pos)? == 1; + let has_password = buffer.get_u8()? == 1; + let vac_secured = buffer.get_u8()? == 1; let the_ship = match *app == SteamID::TS.as_app() { false => None, true => Some(TheShip { - mode: buffer::get_u8(&buf, &mut pos)?, - witnesses: buffer::get_u8(&buf, &mut pos)?, - duration: buffer::get_u8(&buf, &mut pos)? + mode: buffer.get_u8()?, + witnesses: buffer.get_u8()?, + duration: buffer.get_u8()? }) }; - let version = buffer::get_string_utf8_le(&buf, &mut pos)?; - let extra_data = match buffer::get_u8(&buf, &mut pos) { + let version = buffer.get_string_utf8()?; + let extra_data = match buffer.get_u8() { Err(_) => None, Ok(value) => Some(ExtraData { port: match (value & 0x80) > 0 { false => None, - true => Some(buffer::get_u16_le(&buf, &mut pos)?) + true => Some(buffer.get_u16()?) }, steam_id: match (value & 0x10) > 0 { false => None, - true => Some(buffer::get_u64_le(&buf, &mut pos)?) + true => Some(buffer.get_u64()?) }, tv_port: match (value & 0x40) > 0 { false => None, - true => Some(buffer::get_u16_le(&buf, &mut pos)?) + true => Some(buffer.get_u16()?) }, tv_name: match (value & 0x40) > 0 { false => None, - true => Some(buffer::get_string_utf8_le(&buf, &mut pos)?) + true => Some(buffer.get_string_utf8()?) }, keywords: match (value & 0x20) > 0 { false => None, - true => Some(buffer::get_string_utf8_le(&buf, &mut pos)?) + true => Some(buffer.get_string_utf8()?) }, game_id: match (value & 0x01) > 0 { false => None, true => { - let gid = buffer::get_u64_le(&buf, &mut pos)?; + let gid = buffer.get_u64()?; appid = (gid & ((1 << 24) - 1)) as u32; Some(gid) @@ -355,25 +356,25 @@ impl ValveProtocol { /// Get the server player's. fn get_server_players(&mut self, app: &App, protocol: u8) -> GDResult> { - let buf = self.get_request_data(&app, protocol, Request::PLAYERS)?; - let mut pos = 0; + let data = self.get_request_data(&app, protocol, Request::PLAYERS)?; + let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); - let count = buffer::get_u8(&buf, &mut pos)? as usize; + let count = buffer.get_u8()? as usize; let mut players: Vec = Vec::with_capacity(count); for _ in 0..count { - pos += 1; //skip the index byte + buffer.move_position_ahead(1); //skip the index byte players.push(ServerPlayer { - name: buffer::get_string_utf8_le(&buf, &mut pos)?, - score: buffer::get_u32_le(&buf, &mut pos)?, - duration: buffer::get_f32_le(&buf, &mut pos)?, + name: buffer.get_string_utf8()?, + score: buffer.get_u32()?, + duration: buffer.get_f32()?, deaths: match *app == SteamID::TS.as_app() { false => None, - true => Some(buffer::get_u32_le(&buf, &mut pos)?) + true => Some(buffer.get_u32()?) }, money: match *app == SteamID::TS.as_app() { false => None, - true => Some(buffer::get_u32_le(&buf, &mut pos)?) + true => Some(buffer.get_u32()?) } }); } @@ -383,16 +384,16 @@ impl ValveProtocol { /// Get the server rules's. fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult>> { - let buf = self.get_request_data(&app, protocol, Request::RULES)?; - let mut pos = 0; + let data = self.get_request_data(&app, protocol, Request::RULES)?; + let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); - let count = buffer::get_u16_le(&buf, &mut pos)? as usize; + let count = buffer.get_u16()? as usize; let mut rules: Vec = Vec::with_capacity(count); for _ in 0..count { rules.push(ServerRule { - name: buffer::get_string_utf8_le(&buf, &mut pos)?, - value: buffer::get_string_utf8_le(&buf, &mut pos)? + name: buffer.get_string_utf8()?, + value: buffer.get_string_utf8()? }) } diff --git a/src/utils.rs b/src/utils.rs index 8f4c7f5..0043843 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -20,222 +20,22 @@ pub fn u8_lower_upper(n: u8) -> (u8, u8) { (n & 15, n >> 4) } -pub mod buffer { - use super::*; - - pub fn get_u8(buf: &[u8], pos: &mut usize) -> GDResult { - if buf.len() <= *pos { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u8.".to_string())); - } - - let value = buf[*pos]; - *pos += 1; - Ok(value) - } - - pub fn get_u16_le(buf: &[u8], pos: &mut usize) -> GDResult { - if buf.len() <= *pos + 1 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u16.".to_string())); - } - - let value = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]); - *pos += 2; - Ok(value) - } - - pub fn get_u16_be(buf: &[u8], pos: &mut usize) -> GDResult { - if buf.len() <= *pos + 1 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u16.".to_string())); - } - - let value = u16::from_be_bytes([buf[*pos], buf[*pos + 1]]); - *pos += 2; - Ok(value) - } - - pub fn get_u32_le(buf: &[u8], pos: &mut usize) -> GDResult { - if buf.len() <= *pos + 3 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u32.".to_string())); - } - - let value = u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); - *pos += 4; - Ok(value) - } - - pub fn get_f32_le(buf: &[u8], pos: &mut usize) -> GDResult { - if buf.len() <= *pos + 3 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an f32.".to_string())); - } - - let value = f32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); - *pos += 4; - Ok(value) - } - - pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> GDResult { - if buf.len() <= *pos + 7 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u64.".to_string())); - } - - let value = u64::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3], buf[*pos + 4], buf[*pos + 5], buf[*pos + 6], buf[*pos + 7]]); - *pos += 8; - Ok(value) - } - - pub fn get_string_utf8_le(buf: &[u8], pos: &mut usize) -> GDResult { - let sub_buf = &buf[*pos..]; - if sub_buf.len() == 0 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 LE string.".to_string())); - } - - let first_null_position = sub_buf.iter().position(|&x| x == 0) - .ok_or(GDError::PacketBad("Unexpectedly formatted packet for getting a utf8 LE string.".to_string()))?; - let value = std::str::from_utf8(&sub_buf[..first_null_position]) - .map_err(|_| GDError::PacketBad("Badly formatted utf8 LE string.".to_string()))?.to_string(); - - *pos += value.len() + 1; - Ok(value) - } - - pub fn get_string_utf8_le_unended(buf: &[u8], pos: &mut usize) -> GDResult { - let sub_buf = &buf[*pos..]; - if sub_buf.len() == 0 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 LE string.".to_string())); - } - - let value = std::str::from_utf8(&sub_buf) - .map_err(|_| GDError::PacketBad("Badly formatted utf8 LE string.".to_string()))?.to_string(); - - *pos += value.len(); - Ok(value) - } - - pub fn get_string_utf16_be(buf: &[u8], pos: &mut usize) -> GDResult { - let sub_buf = &buf[*pos..]; - if sub_buf.len() == 0 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf16 BE string.".to_string())); - } - - let paired_buf: Vec = sub_buf.chunks_exact(2) - .into_iter().map(|a| u16::from_be_bytes([a[0], a[1]])).collect(); - - let value = String::from_utf16(&paired_buf) - .map_err(|_| GDError::PacketBad("Badly formatted utf16 BE string.".to_string()))?.to_string(); - - *pos += value.len() * 2; - Ok(value) - } -} - #[cfg(test)] mod tests { - use super::*; - #[test] - fn address_and_port_as_string_test() { - assert_eq!(address_and_port_as_string("192.168.0.1", 27015), "192.168.0.1:27015"); + fn address_and_port_as_string() { + assert_eq!(super::address_and_port_as_string("192.168.0.1", 27015), "192.168.0.1:27015"); } #[test] - fn u8_lower_upper_test() { - assert_eq!(u8_lower_upper(171), (11, 10)); + fn u8_lower_upper() { + assert_eq!(super::u8_lower_upper(171), (11, 10)); } #[test] - fn get_u8_test() { - let data = [72]; - let mut pos = 0; - assert_eq!(buffer::get_u8(&data, &mut pos).unwrap(), 72); - assert_eq!(pos, 1); - assert!(buffer::get_u8(&data, &mut pos).is_err()); - assert_eq!(pos, 1); - } - - #[test] - fn get_u16_le_test() { - let data = [72, 29]; - let mut pos = 0; - assert_eq!(buffer::get_u16_le(&data, &mut pos).unwrap(), 7496); - assert_eq!(pos, 2); - assert!(buffer::get_u16_le(&data, &mut pos).is_err()); - assert_eq!(pos, 2); - } - - #[test] - fn get_u16_be_test() { - let data = [29, 72]; - let mut pos = 0; - assert_eq!(buffer::get_u16_be(&data, &mut pos).unwrap(), 7496); - assert_eq!(pos, 2); - assert!(buffer::get_u16_be(&data, &mut pos).is_err()); - assert_eq!(pos, 2); - } - - #[test] - fn get_u32_le_test() { - let data = [72, 29, 128, 100]; - let mut pos = 0; - assert_eq!(buffer::get_u32_le(&data, &mut pos).unwrap(), 1686117704); - assert_eq!(pos, 4); - assert!(buffer::get_u32_le(&data, &mut pos).is_err()); - assert_eq!(pos, 4); - } - - #[test] - fn get_f32_le_test() { - let data = [72, 29, 128, 100]; - let mut pos = 0; - assert_eq!(buffer::get_f32_le(&data, &mut pos).unwrap(), 1.8906345e22); - assert_eq!(pos, 4); - assert!(buffer::get_f32_le(&data, &mut pos).is_err()); - assert_eq!(pos, 4); - } - - #[test] - fn get_u64_le_test() { - let data = [72, 29, 128, 99, 69, 4, 2, 0]; - let mut pos = 0; - assert_eq!(buffer::get_u64_le(&data, &mut pos).unwrap(), 567646022016328); - assert_eq!(pos, 8); - assert!(buffer::get_u64_le(&data, &mut pos).is_err()); - assert_eq!(pos, 8); - } - - #[test] - fn get_string_utf8_le_test() { - let data = [72, 101, 108, 108, 111, 0, 72]; - let mut pos = 0; - assert_eq!(buffer::get_string_utf8_le(&data, &mut pos).unwrap(), "Hello"); - assert_eq!(pos, 6); - assert!(buffer::get_string_utf8_le(&data, &mut pos).is_err()); - assert_eq!(pos, 6); - } - - #[test] - fn get_string_utf8_le_unended_test() { - let data = [72, 101, 108, 108, 111]; - let mut pos = 0; - assert_eq!(buffer::get_string_utf8_le_unended(&data, &mut pos).unwrap(), "Hello"); - assert_eq!(pos, 5); - assert!(buffer::get_string_utf8_le_unended(&data, &mut pos).is_err()); - assert_eq!(pos, 5); - } - - #[test] - fn get_string_utf16_be_test() { - let data = [0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f]; - let mut pos = 0; - assert_eq!(buffer::get_string_utf16_be(&data, &mut pos).unwrap(), "Hello"); - assert_eq!(pos, 10); - assert!(buffer::get_string_utf16_be(&data, &mut pos).is_err()); - assert_eq!(pos, 10); - } - - #[test] - fn error_by_expected_size_test() { - assert!(error_by_expected_size(69, 69).is_ok()); - assert!(error_by_expected_size(69, 68).is_err()); - assert!(error_by_expected_size(69, 70).is_err()); + fn error_by_expected_size() { + assert!(super::error_by_expected_size(69, 69).is_ok()); + assert!(super::error_by_expected_size(69, 68).is_err()); + assert!(super::error_by_expected_size(69, 70).is_err()); } } From 8c98433da9cb8f0eef9a823352d07398f59c59af Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 29 Dec 2022 16:37:42 +0200 Subject: [PATCH 100/597] Fix online/max players being reversed on minecraft legacy 1.6 --- CHANGELOG.md | 3 ++- src/protocols/minecraft/protocol/legacy_v1_6.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4970d55..e5df8a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ Who knows what the future holds... # 0.0.7 - ??/??/2022 ### Changes: [Minecraft](https://www.minecraft.com) bedrock edition support. -Also added a `query_legacy_specific` method to the mc game queries. +Fix Minecraft legacy v1.6 max/online players count being reversed. +Added `query_legacy_specific` method to the Minecraft protocol. ### Breaking: Removed `query_specific` from the mc protocol in favor of `query_java`, `query_legacy` and `query_legacy_specific`. diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 9bd14a4..cd92380 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -55,9 +55,9 @@ impl LegacyV1_6 { .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; let version_name = split[1].to_string(); let description = split[2].to_string(); - let max_players = split[3].parse() + let online_players = split[3].parse() .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; - let online_players = split[4].parse() + let max_players = split[4].parse() .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; Ok(Response { From 0e68f8c83065732cae47bd4478c98b8ec079d357 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 29 Dec 2022 16:59:51 +0200 Subject: [PATCH 101/597] Make public functions that are meant to be used internally private. --- CHANGELOG.md | 3 ++- src/bufferer.rs | 4 ---- src/protocols/minecraft/types.rs | 9 +++++---- src/protocols/valve/types.rs | 8 ++++---- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5df8a6..4eaac5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ Fix Minecraft legacy v1.6 max/online players count being reversed. Added `query_legacy_specific` method to the Minecraft protocol. ### Breaking: -Removed `query_specific` from the mc protocol in favor of `query_java`, `query_legacy` and `query_legacy_specific`. +Removed `query_specific` from the mc protocol in favor of `query_java`, `query_legacy` and `query_legacy_specific`. +Some public functions that are meant to be used only internally were made private. # 0.0.6 - 28/11/2022 [Minecraft](https://www.minecraft.com) support (bedrock not supported yet). diff --git a/src/bufferer.rs b/src/bufferer.rs index 20feb0d..5db3f60 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -11,10 +11,6 @@ pub struct Bufferer { } impl Bufferer { - pub fn new(endianess: Endianess) -> Self { - Bufferer::new_with_data(endianess, &[]) - } - pub fn new_with_data(endianess: Endianess, data: &[u8]) -> Self { Bufferer { data: data.to_vec(), diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index 74976c3..58116b2 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -123,7 +123,7 @@ impl GameMode { } } -pub fn get_varint(buffer: &mut Bufferer) -> GDResult { +pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult { let mut result = 0; let msb: u8 = 0b10000000; @@ -147,7 +147,7 @@ pub fn get_varint(buffer: &mut Bufferer) -> GDResult { Ok(result) } -pub fn as_varint(value: i32) -> Vec { +pub(crate) fn as_varint(value: i32) -> Vec { let mut bytes = vec![]; let mut reading_value = value; @@ -171,7 +171,7 @@ pub fn as_varint(value: i32) -> Vec { bytes } -pub fn get_string(buffer: &mut Bufferer) -> GDResult { +pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult { let length = get_varint(buffer)? as usize; let mut text = vec![0; length]; @@ -183,7 +183,8 @@ pub fn get_string(buffer: &mut Bufferer) -> GDResult { .map_err(|_| GDError::PacketBad("Couldn't parse to a Minecraft String.".to_string()))?) } -pub fn as_string(value: String) -> Vec { +#[allow(dead_code)] +pub(crate) fn as_string(value: String) -> Vec { let mut buf = as_varint(value.len() as i32); buf.extend(value.as_bytes().to_vec()); diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index c4e5bbc..f126ad0 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -122,7 +122,7 @@ pub struct ModData { pub has_own_dll: bool } -pub fn get_optional_extracted_data(data: Option) -> (Option, Option, Option, Option, Option) { +pub(crate) fn get_optional_extracted_data(data: Option) -> (Option, Option, Option, Option, Option) { match data { None => (None, None, None, None, None), Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) @@ -132,7 +132,7 @@ pub fn get_optional_extracted_data(data: Option) -> (Option, Opt /// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). #[derive(PartialEq, Clone)] #[repr(u8)] -pub enum Request { +pub(crate) enum Request { /// Known as `A2S_INFO` INFO = 0x54, /// Known as `A2S_PLAYERS` @@ -211,7 +211,7 @@ impl SteamID { } } -/// App type +/// App type. #[derive(PartialEq, Clone)] pub enum App { /// A Source game, the argument represents the wanted response steam app id, if its **None**, @@ -240,7 +240,7 @@ impl Default for GatheringSettings { } /// Generic response types that are used by many games, they are the protocol ones, but without the -/// unnecessary bits (example: the **The Ship**-only fields) +/// unnecessary bits (example: the **The Ship**-only fields). pub mod game { use crate::protocols::valve::types::get_optional_extracted_data; use super::{Server, ServerRule, ServerPlayer}; From a37e2506b41f59afe790daa7e0a3c54e2f2a6b68 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 31 Dec 2022 14:32:35 +0200 Subject: [PATCH 102/597] Change valve get_request_data return type from vec to Bufferer --- src/protocols/valve/protocol.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index e4fc464..031cf84 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -178,21 +178,24 @@ impl ValveProtocol { } /// Ask for a specific request only. - fn get_request_data(&mut self, app: &App, protocol: u8, kind: Request) -> GDResult> { + fn get_request_data(&mut self, app: &App, protocol: u8, kind: Request) -> GDResult { let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); self.socket.send(&request_initial_packet)?; let packet = self.receive(app, protocol, PACKET_SIZE)?; if packet.kind != 0x41 { //'A' - return Ok(packet.payload.clone()); + let data = packet.payload.clone(); + return Ok(Bufferer::new_with_data(Endianess::Little, &data)); } let challenge = packet.payload; let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); self.socket.send(&challenge_packet)?; - Ok(self.receive(app, protocol, PACKET_SIZE)?.payload) + + let data = self.receive(app, protocol, PACKET_SIZE)?.payload; + Ok(Bufferer::new_with_data(Endianess::Little, &data)) } fn get_goldsrc_server_info(buffer: &mut Bufferer) -> GDResult { @@ -256,8 +259,7 @@ impl ValveProtocol { /// Get the server information's. fn get_server_info(&mut self, app: &App) -> GDResult { - let data = self.get_request_data(&app, 0, Request::INFO)?; - let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); + let mut buffer = self.get_request_data(&app, 0, Request::INFO)?; if let App::GoldSrc(force) = app { if *force { @@ -356,8 +358,7 @@ impl ValveProtocol { /// Get the server player's. fn get_server_players(&mut self, app: &App, protocol: u8) -> GDResult> { - let data = self.get_request_data(&app, protocol, Request::PLAYERS)?; - let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); + let mut buffer = self.get_request_data(&app, protocol, Request::PLAYERS)?; let count = buffer.get_u8()? as usize; let mut players: Vec = Vec::with_capacity(count); @@ -384,8 +385,7 @@ impl ValveProtocol { /// Get the server rules's. fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult>> { - let data = self.get_request_data(&app, protocol, Request::RULES)?; - let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); + let mut buffer = self.get_request_data(&app, protocol, Request::RULES)?; let count = buffer.get_u16()? as usize; let mut rules: Vec = Vec::with_capacity(count); From ef8ac92506e9fb45ead60b42c747a519c9640100 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 31 Dec 2022 14:35:36 +0200 Subject: [PATCH 103/597] Change valve get_server_rules to not return an Option --- src/protocols/valve/protocol.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 031cf84..c03148f 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -383,8 +383,8 @@ impl ValveProtocol { Ok(players) } - /// Get the server rules's. - fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult>> { + /// Get the server's rules. + fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult> { let mut buffer = self.get_request_data(&app, protocol, Request::RULES)?; let count = buffer.get_u16()? as usize; @@ -397,7 +397,7 @@ impl ValveProtocol { }) } - Ok(Some(rules)) + Ok(rules) } } @@ -430,7 +430,7 @@ fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSe }, rules: match gather_settings.rules { false => None, - true => client.get_server_rules(&app, protocol)? + true => Some(client.get_server_rules(&app, protocol)?) } }) } From 7d164d40a127729774b7f1c773de82e097ec237e Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 31 Dec 2022 14:52:17 +0200 Subject: [PATCH 104/597] Add Copy trait to Valve Request Kind enum --- src/protocols/valve/protocol.rs | 10 +++++----- src/protocols/valve/types.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index c03148f..649016f 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -29,8 +29,8 @@ impl Packet { Self { header: initial.header, kind: initial.kind, - payload: match initial.kind { - 0x54 => { + payload: match kind { + Request::INFO => { initial.payload.extend(challenge); initial.payload }, @@ -42,7 +42,7 @@ impl Packet { fn initial(kind: Request) -> Self { Self { header: 4294967295, //FF FF FF FF - kind: kind.clone() as u8, + kind: kind as u8, payload: match kind { Request::INFO => String::from("Source Engine Query\0").into_bytes(), _ => vec![0xFF, 0xFF, 0xFF, 0xFF] @@ -179,7 +179,7 @@ impl ValveProtocol { /// Ask for a specific request only. fn get_request_data(&mut self, app: &App, protocol: u8, kind: Request) -> GDResult { - let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); + let request_initial_packet = Packet::initial(kind).to_bytes(); self.socket.send(&request_initial_packet)?; let packet = self.receive(app, protocol, PACKET_SIZE)?; @@ -190,7 +190,7 @@ impl ValveProtocol { } let challenge = packet.payload; - let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); + let challenge_packet = Packet::challenge(kind, challenge).to_bytes(); self.socket.send(&challenge_packet)?; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index f126ad0..5797f45 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -130,7 +130,7 @@ pub(crate) fn get_optional_extracted_data(data: Option) -> (Option Date: Tue, 3 Jan 2023 00:36:35 +0200 Subject: [PATCH 105/597] Bump version to 0.0.7 --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eaac5f..17836f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Who knows what the future holds... -# 0.0.7 - ??/??/2022 +# 0.0.7 - 03/01/2023 ### Changes: [Minecraft](https://www.minecraft.com) bedrock edition support. Fix Minecraft legacy v1.6 max/online players count being reversed. diff --git a/Cargo.toml b/Cargo.toml index c5b0ab9..231f65e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.0.6" +version = "0.0.7" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" From ededf93b38ad91634a1cc928121e98fa44e4f18c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 9 Jan 2023 19:02:31 +0200 Subject: [PATCH 106/597] Add Risk of Rain 2 support. --- CHANGELOG.md | 7 +++++++ GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/ror2.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/games/ror2.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 17836f4..365e5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Who knows what the future holds... +# X - DD/MM/YYYY +### Changes: +[Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. + +### Breaking: +Nothing (yet). + # 0.0.7 - 03/01/2023 ### Changes: [Minecraft](https://www.minecraft.com) bedrock edition support. diff --git a/GAMES.md b/GAMES.md index 712cd02..e018df9 100644 --- a/GAMES.md +++ b/GAMES.md @@ -30,6 +30,7 @@ A supported game is defined as a game that has been successfully tested, other g | Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | | Day of Infamy | DOI | Valve Protocol | | | Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | +| Risk of Rain 2 | ROR2 | Valve Protocol | Use the query port (by default its 27016 (the game connection port + 1)). | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index dd181ba..48a7562 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -69,6 +69,7 @@ fn main() -> GDResult<()> { "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), "doi" => println!("{:#?}", doi::query(ip, port)?), "hldms" => println!("{:#?}", hldms::query(ip, port)?), + "ror2" => println!("{:#?}", ror2::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/mod.rs b/src/games/mod.rs index 18ca089..6511bbc 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -57,3 +57,5 @@ pub mod arma2oa; pub mod doi; /// Half-Life Deathmatch: Source pub mod hldms; +/// Risk of Rain 2 +pub mod ror2; diff --git a/src/games/ror2.rs b/src/games/ror2.rs new file mode 100644 index 0000000..9bb5e0d --- /dev/null +++ b/src/games/ror2.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27016, + Some(port) => port + }, SteamID::ROR2.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 5797f45..1e54a0e 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -199,6 +199,8 @@ pub enum SteamID { INSS = 581320, /// Alien Swarm: Reactive Drop ASRD = 563560, + /// Risk of Rain 2 + ROR2 = 632360, } impl SteamID { From d8aef7d9e5bfc4e78cba443d91d818c4cc87cf18 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 9 Jan 2023 20:46:27 +0200 Subject: [PATCH 107/597] Move badges on the same line as the readme title --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 61760c7..74a6ac7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,4 @@ -# rust-gamedig -[![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) -[![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) -[![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) -[![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) +# rust-gamedig [![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) [![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) [![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) [![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) **rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! From 50012dd49fa4a943e38fa6fb5adfb2ad8d93b8c5 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 12 Jan 2023 23:04:57 +0200 Subject: [PATCH 108/597] Change Valve Protocol Rules to HashMap --- CHANGELOG.md | 2 +- src/games/ts.rs | 5 +++-- src/protocols/valve/protocol.rs | 15 ++++++++------- src/protocols/valve/types.rs | 18 ++++++------------ 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 365e5dc..3d914ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Who knows what the future holds... [Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. ### Breaking: -Nothing (yet). +Valve Protocol - Rules is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and value fields). # 0.0.7 - 03/01/2023 ### Changes: diff --git a/src/games/ts.rs b/src/games/ts.rs index 95c4d49..9684a41 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,6 +1,7 @@ +use std::collections::HashMap; use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{Server, ServerRule, ServerPlayer, get_optional_extracted_data, SteamID}; +use crate::protocols::valve::{Server, ServerPlayer, get_optional_extracted_data, SteamID}; #[derive(Debug)] pub struct TheShipPlayer { @@ -42,7 +43,7 @@ pub struct Response { pub tv_port: Option, pub tv_name: Option, pub keywords: Option, - pub rules: Vec, + pub rules: HashMap, pub mode: u8, pub witnesses: u8, pub duration: u8 diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 649016f..d81f338 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,9 +1,10 @@ +use std::collections::HashMap; use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::types::TimeoutSettings; use crate::protocols::valve::{App, ModData, SteamID}; -use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip}; +use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, TheShip}; use crate::socket::{Socket, UdpSocket}; use crate::utils::u8_lower_upper; @@ -384,17 +385,17 @@ impl ValveProtocol { } /// Get the server's rules. - fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult> { + fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult> { let mut buffer = self.get_request_data(&app, protocol, Request::RULES)?; let count = buffer.get_u16()? as usize; - let mut rules: Vec = Vec::with_capacity(count); + let mut rules: HashMap = HashMap::with_capacity(count); for _ in 0..count { - rules.push(ServerRule { - name: buffer.get_string_utf8()?, - value: buffer.get_string_utf8()? - }) + let name = buffer.get_string_utf8()?; + let value = buffer.get_string_utf8()?; + + rules.insert(name, value); } Ok(rules) diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 1e54a0e..d0697a1 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; /// The type of the server. #[derive(Debug)] @@ -20,7 +21,7 @@ pub enum Environment { pub struct Response { pub info: ServerInfo, pub players: Option>, - pub rules: Option> + pub rules: Option> } /// General server information's. @@ -79,13 +80,6 @@ pub struct ServerPlayer { pub money: Option, //the_ship } -/// A server rule. -#[derive(Debug)] -pub struct ServerRule { - pub name: String, - pub value: String -} - /// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). #[derive(Debug)] pub struct TheShip { @@ -244,8 +238,9 @@ impl Default for GatheringSettings { /// Generic response types that are used by many games, they are the protocol ones, but without the /// unnecessary bits (example: the **The Ship**-only fields). pub mod game { + use std::collections::HashMap; use crate::protocols::valve::types::get_optional_extracted_data; - use super::{Server, ServerRule, ServerPlayer}; + use super::{Server, ServerPlayer}; #[derive(Debug)] pub struct Player { @@ -283,7 +278,7 @@ pub mod game { pub tv_port: Option, pub tv_name: Option, pub keywords: Option, - pub rules: Vec + pub rules: HashMap } impl Response { @@ -308,9 +303,8 @@ pub mod game { tv_port, tv_name, keywords, - rules: response.rules.unwrap_or(vec![]) + rules: response.rules.unwrap_or(HashMap::new()) } } } } - From 66be14df0cb94dbc4ffcd1197acf93a1591a94cd Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 12 Jan 2023 23:11:46 +0200 Subject: [PATCH 109/597] Update the README example response. --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 74a6ac7..0b71f3a 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,8 @@ Response (note that some games have a different structure): tv_name: None, keywords: Some("alltalk,nocrits"), rules: [ - ServerRule { - name: "mp_autoteambalance", - value: "1", - } + "mp_autoteambalance": "1", + "mp_maxrounds": "5", //.... ] } From 5c664187f9588e4780c974e396434c38c203b509 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 12 Jan 2023 23:19:13 +0200 Subject: [PATCH 110/597] Remove Test: Value server rule from RoR2 --- src/protocols/valve/protocol.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index d81f338..b7e9b8c 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -398,6 +398,10 @@ impl ValveProtocol { rules.insert(name, value); } + if *app == SteamID::ROR2.as_app() { + rules.remove("Test"); + } + Ok(rules) } } From c5a35016d15aebab7e81c809759aa398d6834112 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 12 Jan 2023 23:40:43 +0200 Subject: [PATCH 111/597] Valve Protocol: Players with no name (name length == 0), are no more added to players details --- src/protocols/valve/protocol.rs | 36 +++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index b7e9b8c..014066b 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -366,19 +366,29 @@ impl ValveProtocol { for _ in 0..count { buffer.move_position_ahead(1); //skip the index byte - players.push(ServerPlayer { - name: buffer.get_string_utf8()?, - score: buffer.get_u32()?, - duration: buffer.get_f32()?, - deaths: match *app == SteamID::TS.as_app() { - false => None, - true => Some(buffer.get_u32()?) - }, - money: match *app == SteamID::TS.as_app() { - false => None, - true => Some(buffer.get_u32()?) - } - }); + + let name = buffer.get_string_utf8()?; + let score = buffer.get_u32()?; + let duration = buffer.get_f32()?; + + let deaths = match *app == SteamID::TS.as_app() { + false => None, + true => Some(buffer.get_u32()?) + }; + let money = match *app == SteamID::TS.as_app() { + false => None, + true => Some(buffer.get_u32()?) + }; + + if name.len() > 0 { + players.push(ServerPlayer { + name, + score, + duration, + deaths, + money + }); + } } Ok(players) From e8619a7df1385b090a4835fd4bca79317ea55c60 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 12 Jan 2023 23:50:54 +0200 Subject: [PATCH 112/597] Update CHANGELOG.md --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d914ff..063322f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ Who knows what the future holds... # X - DD/MM/YYYY ### Changes: -[Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. +Games: [Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. +Valve Protocol: Players with no name are no more added to the `players_details` field. ### Breaking: -Valve Protocol - Rules is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and value fields). +Valve Protocol: The rules field is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and a value fields). # 0.0.7 - 03/01/2023 ### Changes: From c263b17651b82b0a1eaccd3b3a411ae5158b245e Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 00:11:04 +0200 Subject: [PATCH 113/597] Remove errors details as they were quite useless --- src/bufferer.rs | 27 ++++----- src/errors.rs | 57 ++++++------------- src/protocols/minecraft/protocol/bedrock.rs | 17 +++--- src/protocols/minecraft/protocol/java.rs | 21 +++---- .../minecraft/protocol/legacy_bv1_8.rs | 9 +-- .../minecraft/protocol/legacy_v1_4.rs | 9 +-- .../minecraft/protocol/legacy_v1_6.rs | 13 +++-- src/protocols/minecraft/protocol/mod.rs | 7 ++- src/protocols/minecraft/types.rs | 9 +-- src/protocols/types.rs | 7 ++- src/protocols/valve/protocol.rs | 21 +++---- src/socket.rs | 15 ++--- src/utils.rs | 7 ++- 13 files changed, 103 insertions(+), 116 deletions(-) diff --git a/src/bufferer.rs b/src/bufferer.rs index 5db3f60..b3be8c8 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -1,4 +1,5 @@ -use crate::{GDError, GDResult}; +use crate::GDResult; +use crate::GDError::{PacketBad, PacketUnderflow}; pub enum Endianess { Little, Big @@ -25,7 +26,7 @@ impl Bufferer { pub fn get_u8(&mut self) -> GDResult { if self.check_size(1) { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u8.".to_string())); + return Err(PacketUnderflow); } let value = self.data[self.position]; @@ -35,7 +36,7 @@ impl Bufferer { pub fn get_u16(&mut self) -> GDResult { if self.check_size(2) { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u16.".to_string())); + return Err(PacketUnderflow); } let source_data: [u8; 2] = (&self.data[self.position..self.position + 2]).try_into().unwrap(); @@ -51,7 +52,7 @@ impl Bufferer { pub fn get_u32(&mut self) -> GDResult { if self.check_size(4) { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u32.".to_string())); + return Err(PacketUnderflow); } let source_data: [u8; 4] = (&self.data[self.position..self.position + 4]).try_into().unwrap(); @@ -67,7 +68,7 @@ impl Bufferer { pub fn get_f32(&mut self) -> GDResult { if self.check_size(4) { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an f32.".to_string())); + return Err(PacketUnderflow); } let source_data: [u8; 4] = (&self.data[self.position..self.position + 4]).try_into().unwrap(); @@ -83,7 +84,7 @@ impl Bufferer { pub fn get_u64(&mut self) -> GDResult { if self.check_size(8) { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an u64.".to_string())); + return Err(PacketUnderflow); } let source_data: [u8; 8] = (&self.data[self.position..self.position + 8]).try_into().unwrap(); @@ -100,13 +101,13 @@ impl Bufferer { pub fn get_string_utf8(&mut self) -> GDResult { let sub_buf = &self.data[self.position..]; if sub_buf.len() == 0 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 string.".to_string())); + return Err(PacketUnderflow); } let first_null_position = sub_buf.iter().position(|&x| x == 0) - .ok_or(GDError::PacketBad("Unexpectedly formatted packet for getting an utf8 string.".to_string()))?; + .ok_or(PacketBad)?; let value = std::str::from_utf8(&sub_buf[..first_null_position]) - .map_err(|_| GDError::PacketBad("Badly formatted utf8 string.".to_string()))?.to_string(); + .map_err(|_| PacketBad)?.to_string(); self.position += value.len() + 1; Ok(value) @@ -115,11 +116,11 @@ impl Bufferer { pub fn get_string_utf8_unended(&mut self) -> GDResult { let sub_buf = &self.data[self.position..]; if sub_buf.len() == 0 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 unended string.".to_string())); + return Err(PacketUnderflow); } let value = std::str::from_utf8(&sub_buf) - .map_err(|_| GDError::PacketBad("Badly formatted utf8 unended string.".to_string()))?.to_string(); + .map_err(|_| PacketBad)?.to_string(); self.position += value.len(); Ok(value) @@ -128,7 +129,7 @@ impl Bufferer { pub fn get_string_utf16(&mut self) -> GDResult { let sub_buf = &self.data[self.position..]; if sub_buf.len() == 0 { - return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf16 string.".to_string())); + return Err(PacketUnderflow); } let paired_buf: Vec = sub_buf.chunks_exact(2) @@ -138,7 +139,7 @@ impl Bufferer { }).collect(); let value = String::from_utf16(&paired_buf) - .map_err(|_| GDError::PacketBad("Badly formatted utf16 string.".to_string()))?.to_string(); + .map_err(|_| PacketBad)?.to_string(); self.position += value.len() * 2; Ok(value) diff --git a/src/errors.rs b/src/errors.rs index 5f5cae4..45e1aa3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,9 +1,6 @@ //! The library's possible errors. -use core::fmt; -use std::fmt::Formatter; - /// Result of Type and GDError. pub type GDResult = Result; @@ -11,55 +8,33 @@ pub type GDResult = Result; #[derive(Debug, Clone)] pub enum GDError { /// The received packet was bigger than the buffer size. - PacketOverflow(String), + PacketOverflow, /// The received packet was shorter than the expected one. - PacketUnderflow(String), - /// The received packet was badly formatted. - PacketBad(String), + PacketUnderflow, + /// The received packet is badly formatted. + PacketBad, /// Couldn't send the packet. - PacketSend(String), + PacketSend, /// Couldn't send the receive. - PacketReceive(String), + PacketReceive, /// Couldn't decompress data. - Decompress(String), + Decompress, /// Unknown cast while translating a value to an enum. UnknownEnumCast, - /// The server queried is not from the queried game. - BadGame(String), - /// Couldn't bind a socket. - SocketBind(String), /// Invalid input. - InvalidInput(String), + InvalidInput, /// Couldn't create a socket connection. - SocketConnect(String), + SocketConnect, + /// Couldn't bind a socket. + SocketBind, /// Couldn't parse a json string. - JsonParse(String), + JsonParse, + /// The server queried is not from the queried game. + BadGame, /// Couldn't automatically query. AutoQuery, /// A protocol-defined expected format was not met. - ProtocolFormat(String), + ProtocolFormat, /// Couldn't parse a value. - TypeParse(String), -} - -impl fmt::Display for GDError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - GDError::PacketOverflow(details) => write!(f, "Packet overflow: {details}"), - GDError::PacketUnderflow(details) => write!(f, "Packet underflow: {details}"), - GDError::PacketBad(details) => write!(f, "Packet bad: {details}"), - GDError::PacketSend(details) => write!(f, "Couldn't send a packet: {details}"), - GDError::PacketReceive(details) => write!(f, "Couldn't receive a packet: {details}"), - GDError::Decompress(details) => write!(f, "Couldn't decompress data: {details}"), - GDError::UnknownEnumCast => write!(f, "Unknown enum cast encountered."), - GDError::BadGame(details) => write!(f, "Queried another game that the supposed one: {details}"), - GDError::SocketBind(details) => write!(f, "Socket bind: {details}"), - GDError::InvalidInput(details) => write!(f, "Invalid input: {details}"), - GDError::SocketConnect(details) => write!(f, "Socket connect: {details}"), - GDError::JsonParse(details) => write!(f, "Json parse: {details}"), - GDError::AutoQuery => write!(f, "Auto query failed."), - GDError::ProtocolFormat(details) => write!(f, "Protocol rule: {details}"), - GDError::TypeParse(details) => write!(f, "Type parse: {details}"), - } - } + TypeParse, } diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index 50ff0ca..b731cb0 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -4,8 +4,9 @@ This file has code that has been documented by the NodeJS GameDig library (MIT) https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js */ -use crate::{GDError, GDResult}; +use crate::GDResult; use crate::bufferer::{Bufferer, Endianess}; +use crate::GDError::{PacketBad, TypeParse}; use crate::protocols::minecraft::{BedrockResponse, GameMode, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; @@ -45,12 +46,12 @@ impl Bedrock { let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); if buffer.get_u8()? != 0x1c { - return Err(GDError::PacketBad("Invalid message id.".to_string())); + return Err(PacketBad); } // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). if buffer.get_u64()? != 9833440827789222417 { - return Err(GDError::PacketBad("Invalid nonce.".to_string())); + return Err(PacketBad); } // These 8 bytes are identical to the serverId string we receive in decimal below @@ -58,11 +59,11 @@ impl Bedrock { // Verifying the magic value (as we need 16 bytes, cast to two u64 values) if buffer.get_u64()? != 18374403896610127616 { - return Err(GDError::PacketBad("Invalid magic (part 1).".to_string())); + return Err(PacketBad); } if buffer.get_u64()? != 8671175388723805693 { - return Err(GDError::PacketBad("Invalid magic (part 2).".to_string())); + return Err(PacketBad); } let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize; @@ -74,7 +75,7 @@ impl Bedrock { // We must have at least 6 values if status.len() < 6 { - return Err(GDError::PacketBad("Not enough status parts.".to_string())); + return Err(PacketBad); } Ok(BedrockResponse { @@ -82,8 +83,8 @@ impl Bedrock { name: status[1].to_string(), version_name: status[3].to_string(), version_protocol: status[2].to_string(), - max_players: status[5].parse().map_err(|_| GDError::TypeParse("couldn't parse.".to_string()))?, - online_players: status[4].parse().map_err(|_| GDError::TypeParse("couldn't parse.".to_string()))?, + max_players: status[5].parse().map_err(|_| TypeParse)?, + online_players: status[4].parse().map_err(|_| TypeParse)?, id: status.get(6).and_then(|v| Some(v.to_string())), map: status.get(7).and_then(|v| Some(v.to_string())), game_mode: match status.get(8) { diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index c042241..a2611e6 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -1,5 +1,6 @@ use serde_json::Value; -use crate::{GDError, GDResult}; +use crate::GDResult; +use crate::GDError::{JsonParse, PacketBad}; use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::minecraft::{as_varint, get_string, get_varint, Player, Response, Server}; use crate::protocols::types::TimeoutSettings; @@ -72,33 +73,33 @@ impl Java { let mut buffer = self.receive()?; if get_varint(&mut buffer)? != 0 { //first var int is the packet id - return Err(GDError::PacketBad("Bad receive packet id.".to_string())); + return Err(PacketBad); } let json_response = get_string(&mut buffer)?; let value_response: Value = serde_json::from_str(&json_response) - .map_err(|e| GDError::JsonParse(e.to_string()))?; + .map_err(|_|JsonParse)?; let version_name = value_response["version"]["name"].as_str() - .ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string(); + .ok_or(PacketBad)?.to_string(); let version_protocol = value_response["version"]["protocol"].as_i64() - .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as i32; + .ok_or(PacketBad)? as i32; let max_players = value_response["players"]["max"].as_u64() - .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as u32; + .ok_or(PacketBad)? as u32; let online_players = value_response["players"]["online"].as_u64() - .ok_or(GDError::PacketBad("Couldn't get expected number.".to_string()))? as u32; + .ok_or(PacketBad)? as u32; let sample_players: Option> = match value_response["players"]["sample"].is_null() { true => None, false => Some({ let players_values = value_response["players"]["sample"].as_array() - .ok_or(GDError::PacketBad("Couldn't get expected array.".to_string()))?; + .ok_or(PacketBad)?; let mut players = Vec::with_capacity(players_values.len()); for player in players_values { players.push(Player { - name: player["name"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string(), - id: player["id"].as_str().ok_or(GDError::PacketBad("Couldn't get expected string.".to_string()))?.to_string() + name: player["name"].as_str().ok_or(PacketBad)?.to_string(), + id: player["id"].as_str().ok_or(PacketBad)?.to_string() }) } diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index 0da7d6e..caa251c 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -1,6 +1,7 @@ -use crate::{GDError, GDResult}; +use crate::GDResult; use crate::bufferer::{Bufferer, Endianess}; +use crate::GDError::{PacketBad, ProtocolFormat}; use crate::protocols::minecraft::{LegacyGroup, Response, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; @@ -30,7 +31,7 @@ impl LegacyBV1_8 { let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); if buffer.get_u8()? != 0xFF { - return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.".to_string())); + return Err(ProtocolFormat); } let length = buffer.get_u16()? * 2; @@ -43,9 +44,9 @@ impl LegacyBV1_8 { let description = split[0].to_string(); let online_players = split[1].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; + .map_err(|_| PacketBad)?; let max_players = split[2].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; + .map_err(|_| PacketBad)?; Ok(Response { version_name: "Beta 1.8+".to_string(), diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index 108b53d..e201dd1 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -1,6 +1,7 @@ -use crate::{GDError, GDResult}; +use crate::GDResult; use crate::bufferer::{Bufferer, Endianess}; +use crate::GDError::{PacketBad, ProtocolFormat}; use crate::protocols::minecraft::{LegacyGroup, Response, Server}; use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; use crate::protocols::types::TimeoutSettings; @@ -31,7 +32,7 @@ impl LegacyV1_4 { let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); if buffer.get_u8()? != 0xFF { - return Err(GDError::ProtocolFormat("Expected 0xFF at the begin of the packet.".to_string())); + return Err(ProtocolFormat); } let length = buffer.get_u16()? * 2; @@ -48,9 +49,9 @@ impl LegacyV1_4 { let description = split[0].to_string(); let online_players = split[1].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; + .map_err(|_| PacketBad)?; let max_players = split[2].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; + .map_err(|_| PacketBad)?; Ok(Response { version_name: "1.4+".to_string(), diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index cd92380..0f80e44 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -1,4 +1,5 @@ -use crate::{GDError, GDResult}; +use crate::GDResult; +use crate::GDError::{PacketBad, ProtocolFormat}; use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::minecraft::{LegacyGroup, Response, Server}; use crate::protocols::types::TimeoutSettings; @@ -52,13 +53,13 @@ impl LegacyV1_6 { error_by_expected_size(5, split.len())?; let version_protocol = split[0].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; + .map_err(|_| PacketBad)?; let version_name = split[1].to_string(); let description = split[2].to_string(); let online_players = split[3].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; + .map_err(|_| PacketBad)?; let max_players = split[4].parse() - .map_err(|_| GDError::PacketBad("Failed to parse to expected int.".to_string()))?; + .map_err(|_| PacketBad)?; Ok(Response { version_name, @@ -80,14 +81,14 @@ impl LegacyV1_6 { let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); if buffer.get_u8()? != 0xFF { - return Err(GDError::ProtocolFormat("Expected a certain byte (0xFF) at the begin of the packet.".to_string())); + return Err(ProtocolFormat); } let length = buffer.get_u16()? * 2; error_by_expected_size((length + 3) as usize, buffer.data_length())?; if !LegacyV1_6::is_protocol(&mut buffer)? { - return Err(GDError::ProtocolFormat("Expected certain bytes at the beginning of the packet.".to_string())); + return Err(ProtocolFormat); } LegacyV1_6::get_response(&mut buffer) diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index a3ae26a..2750b45 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,4 +1,5 @@ -use crate::{GDError, GDResult}; +use crate::GDError::AutoQuery; +use crate::GDResult; use crate::protocols::minecraft::{BedrockResponse, LegacyGroup, Response}; use crate::protocols::minecraft::protocol::bedrock::Bedrock; use crate::protocols::minecraft::protocol::java::Java; @@ -27,7 +28,7 @@ pub fn query(address: &str, port: u16, timeout_settings: Option return Ok(response); } - Err(GDError::AutoQuery) + Err(AutoQuery) } /// Query a Java Server. @@ -49,7 +50,7 @@ pub fn query_legacy(address: &str, port: u16, timeout_settings: Option Ok(GameMode::Hardcore), "Spectator" => Ok(GameMode::Spectator), "Adventure" => Ok(GameMode::Adventure), - _ => Err(GDError::UnknownEnumCast) + _ => Err(UnknownEnumCast) } } } @@ -136,7 +137,7 @@ pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult { // The 5th byte is only allowed to have the 4 smallest bits set if i == 4 && (current_byte & 0xf0 != 0) { - return Err(GDError::PacketBad("Couldn't parse to VarInt: Overflow.".to_string())) + return Err(PacketBad) } if (current_byte & msb) == 0 { @@ -180,7 +181,7 @@ pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult { } Ok(String::from_utf8(text) - .map_err(|_| GDError::PacketBad("Couldn't parse to a Minecraft String.".to_string()))?) + .map_err(|_| PacketBad)?) } #[allow(dead_code)] diff --git a/src/protocols/types.rs b/src/protocols/types.rs index bb2719d..eebeb88 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -1,5 +1,6 @@ use std::time::Duration; -use crate::{GDError, GDResult}; +use crate::GDResult; +use crate::GDError::InvalidInput; /// Timeout settings for socket operations #[derive(Clone)] @@ -13,13 +14,13 @@ impl TimeoutSettings { pub fn new(read: Option, write: Option) -> GDResult { if let Some(read_duration) = read { if read_duration == Duration::new(0, 0) { - return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings.".to_string())) + return Err(InvalidInput) } } if let Some(write_duration) = write { if write_duration == Duration::new(0, 0) { - return Err(GDError::InvalidInput("Can't pass duration 0 to timeout settings.".to_string())) + return Err(InvalidInput) } } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 014066b..fa489fe 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; use bzip2_rs::decoder::Decoder; -use crate::{GDError, GDResult}; +use crate::GDResult; use crate::bufferer::{Bufferer, Endianess}; +use crate::GDError::{BadGame, Decompress, UnknownEnumCast}; use crate::protocols::types::TimeoutSettings; use crate::protocols::valve::{App, ModData, SteamID}; use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, TheShip}; @@ -116,18 +117,18 @@ impl SplitPacket { fn get_payload(&self) -> GDResult> { if self.compressed { let mut decoder = Decoder::new(); - decoder.write(&self.payload).map_err(|e| GDError::Decompress(e.to_string()))?; + decoder.write(&self.payload).map_err(|_| Decompress)?; let decompressed_size = self.decompressed_size.unwrap() as usize; let mut decompressed_payload = Vec::with_capacity(decompressed_size); - decoder.read(&mut decompressed_payload).map_err(|e| GDError::Decompress(e.to_string()))?; + decoder.read(&mut decompressed_payload).map_err(|_| Decompress)?; if decompressed_payload.len() != decompressed_size { - Err(GDError::Decompress("The decompressed payload size doesn't match the expected one.".to_string())) + Err(Decompress) } else if crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { - Err(GDError::Decompress("The decompressed crc32 hash does not match the expected one.".to_string())) + Err(Decompress) } else { Ok(decompressed_payload) @@ -213,12 +214,12 @@ impl ValveProtocol { 68 => Server::Dedicated, //'D' 76 => Server::NonDedicated, //'L' 80 => Server::TV, //'P' - _ => Err(GDError::UnknownEnumCast)? + _ => Err(UnknownEnumCast)? }; let environment_type = match buffer.get_u8()? { 76 => Environment::Linux, //'L' 87 => Environment::Windows, //'W' - _ => Err(GDError::UnknownEnumCast)? + _ => Err(UnknownEnumCast)? }; let has_password = buffer.get_u8()? == 1; let is_mod = buffer.get_u8()? == 1; @@ -281,13 +282,13 @@ impl ValveProtocol { 100 => Server::Dedicated, //'d' 108 => Server::NonDedicated, //'l' 112 => Server::TV, //'p' - _ => Err(GDError::UnknownEnumCast)? + _ => Err(UnknownEnumCast)? }; let environment_type = match buffer.get_u8()? { 108 => Environment::Linux, //'l' 119 => Environment::Windows, //'w' 109 | 111 => Environment::Mac, //'m' or 'o' - _ => Err(GDError::UnknownEnumCast)? + _ => Err(UnknownEnumCast)? }; let has_password = buffer.get_u8()? == 1; let vac_secured = buffer.get_u8()? == 1; @@ -432,7 +433,7 @@ fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSe if let App::Source(x) = &app { if let Some(appid) = x { if *appid != info.appid { - return Err(GDError::BadGame(format!("Expected {}, found {} instead!", *appid, info.appid))); + return Err(BadGame); } } } diff --git a/src/socket.rs b/src/socket.rs index 65765a5..33889b8 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -1,6 +1,7 @@ use std::io::{Read, Write}; use std::net; -use crate::{GDError, GDResult}; +use crate::GDResult; +use crate::GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}; use crate::protocols::types::TimeoutSettings; use crate::utils::address_and_port_as_string; @@ -22,7 +23,7 @@ pub struct TcpSocket { impl Socket for TcpSocket { fn new(address: &str, port: u16) -> GDResult { let complete_address = address_and_port_as_string(address, port); - let socket = net::TcpStream::connect(complete_address).map_err(|e| GDError::SocketConnect(e.to_string()))?; + let socket = net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)?; Ok(Self { socket @@ -38,13 +39,13 @@ impl Socket for TcpSocket { } fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket.write(&data).map_err(|e| GDError::PacketSend(e.to_string()))?; + self.socket.write(&data).map_err(|_| PacketSend)?; Ok(()) } fn receive(&mut self, size: Option) -> GDResult> { let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); - self.socket.read_to_end(&mut buf).map_err(|e| GDError::PacketReceive(e.to_string()))?; + self.socket.read_to_end(&mut buf).map_err(|_| PacketReceive)?; Ok(buf) } @@ -58,7 +59,7 @@ pub struct UdpSocket { impl Socket for UdpSocket { fn new(address: &str, port: u16) -> GDResult { let complete_address = address_and_port_as_string(address, port); - let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|e| GDError::SocketBind(e.to_string()))?; + let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|_| SocketBind)?; Ok(Self { socket, @@ -75,13 +76,13 @@ impl Socket for UdpSocket { } fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket.send_to(&data, &self.complete_address).map_err(|e| GDError::PacketSend(e.to_string()))?; + self.socket.send_to(&data, &self.complete_address).map_err(|_| PacketSend)?; Ok(()) } fn receive(&mut self, size: Option) -> GDResult> { let mut buf: Vec = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; - let (number_of_bytes_received, _) = self.socket.recv_from(&mut buf).map_err(|e| GDError::PacketReceive(e.to_string()))?; + let (number_of_bytes_received, _) = self.socket.recv_from(&mut buf).map_err(|_| PacketReceive)?; Ok(buf[..number_of_bytes_received].to_vec()) } diff --git a/src/utils.rs b/src/utils.rs index 0043843..ba79a83 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,11 +1,12 @@ -use crate::{GDResult, GDError}; +use crate::GDResult; +use crate::GDError::{PacketOverflow, PacketUnderflow}; pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { if size < expected { - Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())) + Err(PacketUnderflow) } else if size > expected { - Err(GDError::PacketOverflow("Unexpectedly long packet.".to_string())) + Err(PacketOverflow) } else { Ok(()) From e72d7bdf8bc31b7b0ad53d25f3742ba4808cdb4c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 00:39:58 +0200 Subject: [PATCH 114/597] Revert error details for the BadGame error --- CHANGELOG.md | 1 + src/errors.rs | 16 ++++++++-------- src/protocols/valve/protocol.rs | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 063322f..65f2106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Valve Protocol: Players with no name are no more added to the `players_details` ### Breaking: Valve Protocol: The rules field is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and a value fields). +Errors: Besides the `BadGame` error, now no other errors returns details about what happened (as it was quite pointless). # 0.0.7 - 03/01/2023 ### Changes: diff --git a/src/errors.rs b/src/errors.rs index 45e1aa3..ca360cf 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -19,22 +19,22 @@ pub enum GDError { PacketReceive, /// Couldn't decompress data. Decompress, - /// Unknown cast while translating a value to an enum. - UnknownEnumCast, - /// Invalid input. - InvalidInput, /// Couldn't create a socket connection. SocketConnect, /// Couldn't bind a socket. SocketBind, - /// Couldn't parse a json string. - JsonParse, - /// The server queried is not from the queried game. - BadGame, + /// Invalid input. + InvalidInput, + /// The server queried is not the queried game server. + BadGame(String), /// Couldn't automatically query. AutoQuery, /// A protocol-defined expected format was not met. ProtocolFormat, + /// Couldn't cast a value to an enum. + UnknownEnumCast, + /// Couldn't parse a json string. + JsonParse, /// Couldn't parse a value. TypeParse, } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index fa489fe..3d5ba45 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -430,10 +430,10 @@ fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSe let info = client.get_server_info(&app)?; let protocol = info.protocol; - if let App::Source(x) = &app { - if let Some(appid) = x { + if let App::Source(source_app) = &app { + if let Some(appid) = source_app { if *appid != info.appid { - return Err(BadGame); + return Err(BadGame(format!("AppId: {appid}"))); } } } From 3928d3a818f4d73e2a2806c8751f46073ab57b9c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 01:00:31 +0200 Subject: [PATCH 115/597] Implement std::error::Error for GDError --- src/errors.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/errors.rs b/src/errors.rs index ca360cf..afefef1 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,10 +1,12 @@ //! The library's possible errors. +use std::fmt; +use std::{fmt::Formatter, error::Error}; /// Result of Type and GDError. pub type GDResult = Result; -/// GameDigError. +/// GameDig Error. #[derive(Debug, Clone)] pub enum GDError { /// The received packet was bigger than the buffer size. @@ -38,3 +40,11 @@ pub enum GDError { /// Couldn't parse a value. TypeParse, } + +impl Error for GDError {} + +impl fmt::Display for GDError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} From 824c4d34c0d9a0c419b404e4772b052255a48ae7 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 01:31:57 +0200 Subject: [PATCH 116/597] Add proper MSRV to Cargo.toml --- Cargo.toml | 4 +--- examples/minecraft.rs | 2 +- examples/tf2.rs | 2 +- src/protocols/valve/protocol.rs | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 231f65e..989db67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,7 @@ documentation = "https://docs.rs/gamedig/latest/gamedig/" repository = "https://github.com/CosminPerRam/rust-gamedig" readme = "README.md" keywords = ["server", "query", "game", "check", "status"] - -[package.metadata] -msrv = "1.58.1" +rust-version = "1.56.1" [dependencies] bzip2-rs = "0.1.2" # for compression diff --git a/examples/minecraft.rs b/examples/minecraft.rs index f675508..ad1b98e 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -6,7 +6,7 @@ fn main() { let response = mc::query("127.0.0.1", None); match response { - Err(error) => println!("Couldn't query, error: {error}"), + Err(error) => println!("Couldn't query, error: {}", error), Ok(r) => println!("{:#?}", r) } } diff --git a/examples/tf2.rs b/examples/tf2.rs index f901d01..8a612c9 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -4,7 +4,7 @@ use gamedig::games::tf2; fn main() { let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port (which is 27015) match response { - Err(error) => println!("Couldn't query, error: {error}"), + Err(error) => println!("Couldn't query, error: {}", error), Ok(r) => println!("{:#?}", r) } } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 3d5ba45..80dbb25 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -433,7 +433,7 @@ fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSe if let App::Source(source_app) = &app { if let Some(appid) = source_app { if *appid != info.appid { - return Err(BadGame(format!("AppId: {appid}"))); + return Err(BadGame(format!("AppId: {}", appid))); } } } From 6786636945fef2a4c1f4c979514ae426d453e4e6 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 01:32:28 +0200 Subject: [PATCH 117/597] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b71f3a..d10dfb9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! -MSRV is `1.58.1` and the code is cross-platform. +MSRV is `1.56.1` and the code is cross-platform. ## Games/Services/Protocols List To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. From 637252ca187a2167be7617342cc048e77f53d801 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 01:44:24 +0200 Subject: [PATCH 118/597] Update CHANGELOG.md --- CHANGELOG.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f2106..4a3397f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,24 @@ Who knows what the future holds... # X - DD/MM/YYYY ### Changes: -Games: [Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. -Valve Protocol: Players with no name are no more added to the `players_details` field. +Games: +- [Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. + +Protocols: +- Valve: Players with no name are no more added to the `players_details` field. + +Crate: +- `MSRV` is now `1.56.1` (from `1.58.1`) ### Breaking: -Valve Protocol: The rules field is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and a value fields). -Errors: Besides the `BadGame` error, now no other errors returns details about what happened (as it was quite pointless). +Protocols: +- Valve: The rules field is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and a value fields). + +Errors: +- Besides the `BadGame` error, now no other errors returns details about what happened (as it was quite pointless). + +Crate: +- `package.metadata.msrv` has been replaced with `package.rust-version` # 0.0.7 - 03/01/2023 ### Changes: From ff789fcb90ea21fdee0da917f8cad5e7501fd4b1 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 19:36:03 +0200 Subject: [PATCH 119/597] Valve: Rename players-related fields --- CHANGELOG.md | 2 ++ README.md | 6 +++--- src/games/ts.rs | 6 +++--- src/protocols/valve/protocol.rs | 12 ++++++------ src/protocols/valve/types.rs | 18 +++++++++--------- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a3397f..df05d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Crate: ### Breaking: Protocols: - Valve: The rules field is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and a value fields). +- Valve: `ServerInfo`'s `players`, `max_players` and `bots` have been renamed to `players_online`, `players_maximum` and `players_bots` respectively. +- Valve: `Response`'s `players`, `max_players` and `bots` have been renamed to `players_online`, `players_maximum` and `players_bots` respectively. Errors: - Besides the `BadGame` error, now no other errors returns details about what happened (as it was quite pointless). diff --git a/README.md b/README.md index d10dfb9..e10c783 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,10 @@ Response (note that some games have a different structure): name: "Team Fortress 2 Dedicated Server.", map: "ctf_turbine", game: "tf2", - players: 0, + players_online: 0, players_details: [], - max_players: 69, - bots: 0, + players_maximum: 69, + players_bots: 0, server_type: Dedicated, has_password: false, vac_secured: true, diff --git a/src/games/ts.rs b/src/games/ts.rs index 9684a41..4809619 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -60,10 +60,10 @@ impl Response { name: response.info.name, map: response.info.map, game: response.info.game, - players: response.info.players, + players: response.info.players_online, players_details: response.players.unwrap().iter().map(TheShipPlayer::new_from_valve_player).collect(), - max_players: response.info.max_players, - bots: response.info.bots, + max_players: response.info.players_maximum, + bots: response.info.players_bots, server_type: response.info.server_type, has_password: response.info.has_password, vac_secured: response.info.vac_secured, diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 80dbb25..80eddc8 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -244,9 +244,9 @@ impl ValveProtocol { folder, game, appid: 0, //not present in the obsolete response - players, - max_players, - bots, + players_online: players, + players_maximum: max_players, + players_bots: bots, server_type, environment_type, has_password, @@ -343,9 +343,9 @@ impl ValveProtocol { folder, game, appid, - players, - max_players, - bots, + players_online: players, + players_maximum: max_players, + players_bots: bots, server_type, environment_type, has_password, diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index d0697a1..668c6fa 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -40,11 +40,11 @@ pub struct ServerInfo { /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. pub appid: u32, /// Number of players on the server. - pub players: u8, + pub players_online: u8, /// Maximum number of players the server reports it can hold. - pub max_players: u8, + pub players_maximum: u8, /// Number of bots on the server. - pub bots: u8, + pub players_bots: u8, /// Dedicated, NonDedicated or SourceTV pub server_type: Server, /// The Operating System that the server is on. @@ -265,10 +265,10 @@ pub mod game { pub name: String, pub map: String, pub game: String, - pub players: u8, + pub players_online: u8, pub players_details: Vec, - pub max_players: u8, - pub bots: u8, + pub players_maximum: u8, + pub players_bots: u8, pub server_type: Server, pub has_password: bool, pub vac_secured: bool, @@ -290,10 +290,10 @@ pub mod game { name: response.info.name, map: response.info.map, game: response.info.game, - players: response.info.players, + players_online: response.info.players_online, players_details: response.players.unwrap_or(vec![]).iter().map(Player::from_valve_response).collect(), - max_players: response.info.max_players, - bots: response.info.bots, + players_maximum: response.info.players_maximum, + players_bots: response.info.players_bots, server_type: response.info.server_type, has_password: response.info.has_password, vac_secured: response.info.vac_secured, From 018935fd29a7f662ceb84505ff9a8f5e890f4b28 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 22:58:36 +0200 Subject: [PATCH 120/597] Valve Protocol: Fix BadGame reported appid --- src/protocols/valve/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 80eddc8..231f4be 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -433,7 +433,7 @@ fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSe if let App::Source(source_app) = &app { if let Some(appid) = source_app { if *appid != info.appid { - return Err(BadGame(format!("AppId: {}", appid))); + return Err(BadGame(format!("AppId: {}", info.appid))); } } } From 9bcbfbc1986d4bb5b9e63df777011fcf9567cdae Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 23:08:09 +0200 Subject: [PATCH 121/597] Games: Add Battalion 1944 support. --- CHANGELOG.md | 1 + GAMES.md | 9 +++++---- examples/master_querant.rs | 3 ++- src/games/bat1944.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 src/games/bat1944.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index df05d9f..ebbc150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Who knows what the future holds... ### Changes: Games: - [Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. +- [Battalion 1944](https://store.steampowered.com/app/489940/BATTALION_Legacy/) support. Protocols: - Valve: Players with no name are no more added to the `players_details` field. diff --git a/GAMES.md b/GAMES.md index e018df9..d46ff3d 100644 --- a/GAMES.md +++ b/GAMES.md @@ -14,7 +14,7 @@ A supported game is defined as a game that has been successfully tested, other g | Alien Swarm | ALIENS | Valve Protocol | | | Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | | Insurgency | INS | Valve Protocol | | -| Insurgency: Sandstorm | INSS | Valve Protocol | Use the query port. | +| Insurgency: Sandstorm | INSS | Valve Protocol | Query port offset: 1. | | Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | | Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | | Day of Defeat | DOD | Valve Protocol (GoldSrc) | | @@ -22,15 +22,16 @@ A supported game is defined as a game that has been successfully tested, other g | 7 Days To Die | SDTD | Valve Protocol | | | ARK: Survival Evolved | ASE | Valve Protocol | | | Unturned | UNTURNED | Valve Protocol | | -| The Forest | TF | Valve Protocol (GoldSrc) | Use the query port. | +| The Forest | TF | Valve Protocol (GoldSrc) | Query port offset: 1. | | Team Fortress Classic | TFC | Valve Protocol | | | Sven Co-op | SC | Valve Protocol (GoldSrc) | | | Rust | RUST | Valve Protocol | | | Counter-Strike | CS | Valve Protocol (GoldSrc) | | -| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | +| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Query port offset: 1. | | Day of Infamy | DOI | Valve Protocol | | | Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | -| Risk of Rain 2 | ROR2 | Valve Protocol | Use the query port (by default its 27016 (the game connection port + 1)). | +| Risk of Rain 2 | ROR2 | Valve Protocol | Query port offset: 1. | +| Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 48a7562..3fe98b6 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, arma2oa, ase, asrd, bat1944, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -70,6 +70,7 @@ fn main() -> GDResult<()> { "doi" => println!("{:#?}", doi::query(ip, port)?), "hldms" => println!("{:#?}", hldms::query(ip, port)?), "ror2" => println!("{:#?}", ror2::query(ip, port)?), + "bat1944" => println!("{:?}", bat1944::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs new file mode 100644 index 0000000..9bfbcdc --- /dev/null +++ b/src/games/bat1944.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 7780, + Some(port) => port + }, SteamID::BAT1944.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 6511bbc..2b2a688 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -59,3 +59,5 @@ pub mod doi; pub mod hldms; /// Risk of Rain 2 pub mod ror2; +/// Battalion 1944 +pub mod bat1944; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 668c6fa..7e756f8 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -189,6 +189,8 @@ pub enum SteamID { UNTURNED = 304930, /// ARK: Survival Evolved ASE = 346110, + /// Battalion 1944 + BAT1944 = 489940, /// Insurgency: Sandstorm INSS = 581320, /// Alien Swarm: Reactive Drop From 4a7eb400db06fa8466b05142a74db6924a26c3c7 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 13 Jan 2023 23:52:45 +0200 Subject: [PATCH 122/597] Games: Add Battalion 1944 from-rules server information's --- GAMES.md | 2 +- examples/master_querant.rs | 2 +- src/games/bat1944.rs | 32 +++++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/GAMES.md b/GAMES.md index d46ff3d..d351c7c 100644 --- a/GAMES.md +++ b/GAMES.md @@ -31,7 +31,7 @@ A supported game is defined as a game that has been successfully tested, other g | Day of Infamy | DOI | Valve Protocol | | | Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | | Risk of Rain 2 | ROR2 | Valve Protocol | Query port offset: 1. | -| Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. | +| Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. It is strongly recommended to also query the rules, as the game sends server information's in them. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 3fe98b6..9d04185 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -70,7 +70,7 @@ fn main() -> GDResult<()> { "doi" => println!("{:#?}", doi::query(ip, port)?), "hldms" => println!("{:#?}", hldms::query(ip, port)?), "ror2" => println!("{:#?}", ror2::query(ip, port)?), - "bat1944" => println!("{:?}", bat1944::query(ip, port)?), + "bat1944" => println!("{:#?}", bat1944::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs index 9bfbcdc..135038c 100644 --- a/src/games/bat1944.rs +++ b/src/games/bat1944.rs @@ -1,12 +1,42 @@ +use crate::GDError::TypeParse; use crate::GDResult; use crate::protocols::valve; use crate::protocols::valve::{game, SteamID}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { + let mut valve_response = valve::query(address, match port { None => 7780, Some(port) => port }, SteamID::BAT1944.as_app(), None, None)?; + if let Some(rules) = &mut valve_response.rules { + if let Some(bat_max_players) = rules.get("bat_max_players_i") { + valve_response.info.players_maximum = bat_max_players.parse().map_err(|_| TypeParse)?; + rules.remove("bat_max_players_i"); + } + + if let Some(bat_player_count) = rules.get("bat_player_count_s") { + valve_response.info.players_online = bat_player_count.parse().map_err(|_| TypeParse)?; + rules.remove("bat_player_count_s"); + } + + if let Some(bat_has_password) = rules.get("bat_has_password_s") { + valve_response.info.has_password = bat_has_password == "Y"; + rules.remove("bat_has_password_s"); + } + + if let Some(bat_name) = rules.get("bat_name_s") { + valve_response.info.name = bat_name.clone(); + rules.remove("bat_name_s"); + } + + if let Some(bat_gamemode) = rules.get("bat_gamemode_s") { + valve_response.info.game = bat_gamemode.clone(); + rules.remove("bat_gamemode_s"); + } + + rules.remove("bat_map_s"); + } + Ok(game::Response::new_from_valve_response(valve_response)) } From 328de37b2d7478ab096f7f72b35cf2dcb26cacd4 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 16 Jan 2023 21:33:28 +0200 Subject: [PATCH 123/597] Games: Add Black Mesa support. --- CHANGELOG.md | 5 +++-- GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/bm.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/games/bm.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ebbc150..0292f6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,14 @@ Who knows what the future holds... ### Changes: Games: - [Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. -- [Battalion 1944](https://store.steampowered.com/app/489940/BATTALION_Legacy/) support. +- [Battalion 1944](https://store.steampowered.com/app/489940/BATTALION_Legacy/) support. +- [Black Mesa](https://store.steampowered.com/app/362890/Black_Mesa/) support. Protocols: - Valve: Players with no name are no more added to the `players_details` field. Crate: -- `MSRV` is now `1.56.1` (from `1.58.1`) +- `MSRV` is now `1.56.1` (was `1.58.1`) ### Breaking: Protocols: diff --git a/GAMES.md b/GAMES.md index d351c7c..6e0c4c2 100644 --- a/GAMES.md +++ b/GAMES.md @@ -32,6 +32,7 @@ A supported game is defined as a game that has been successfully tested, other g | Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | | Risk of Rain 2 | ROR2 | Valve Protocol | Query port offset: 1. | | Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. It is strongly recommended to also query the rules, as the game sends server information's in them. | +| Black Mesa | BM | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 9d04185..aa912c3 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, bat1944, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, arma2oa, ase, asrd, bat1944, bm, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -71,6 +71,7 @@ fn main() -> GDResult<()> { "hldms" => println!("{:#?}", hldms::query(ip, port)?), "ror2" => println!("{:#?}", ror2::query(ip, port)?), "bat1944" => println!("{:#?}", bat1944::query(ip, port)?), + "bm" => println!("{:#?}", bm::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/bm.rs b/src/games/bm.rs new file mode 100644 index 0000000..93daf35 --- /dev/null +++ b/src/games/bm.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::BM.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 2b2a688..e038862 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -61,3 +61,5 @@ pub mod hldms; pub mod ror2; /// Battalion 1944 pub mod bat1944; +/// Black Mesa +pub mod bm; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 7e756f8..4e9ad6a 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -179,6 +179,8 @@ pub enum SteamID { SC = 225840, /// Rust RUST = 252490, + /// Black Mesa + BM = 362890, /// Day of Infamy DOI = 447820, /// The Forrest From f03a1de03565d83925af666ed1726c949b3eb546 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 16 Jan 2023 22:03:28 +0200 Subject: [PATCH 124/597] Games: Add Project Zomboid support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/pz.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/pz.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0292f6b..7b10b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Games: - [Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. - [Battalion 1944](https://store.steampowered.com/app/489940/BATTALION_Legacy/) support. - [Black Mesa](https://store.steampowered.com/app/362890/Black_Mesa/) support. +- [Project Zomboid](https://store.steampowered.com/app/108600/Project_Zomboid/) support. Protocols: - Valve: Players with no name are no more added to the `players_details` field. diff --git a/GAMES.md b/GAMES.md index 6e0c4c2..05fa2dd 100644 --- a/GAMES.md +++ b/GAMES.md @@ -33,6 +33,7 @@ A supported game is defined as a game that has been successfully tested, other g | Risk of Rain 2 | ROR2 | Valve Protocol | Query port offset: 1. | | Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. It is strongly recommended to also query the rules, as the game sends server information's in them. | | Black Mesa | BM | Valve Protocol | | +| Project Zomboid | PZ | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index aa912c3..2ea2b79 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, bat1944, bm, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, arma2oa, ase, asrd, bat1944, bm, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -72,6 +72,7 @@ fn main() -> GDResult<()> { "ror2" => println!("{:#?}", ror2::query(ip, port)?), "bat1944" => println!("{:#?}", bat1944::query(ip, port)?), "bm" => println!("{:#?}", bm::query(ip, port)?), + "pz" => println!("{:#?}", pz::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/mod.rs b/src/games/mod.rs index e038862..033ed38 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -63,3 +63,5 @@ pub mod ror2; pub mod bat1944; /// Black Mesa pub mod bm; +/// Project Zomboid +pub mod pz; diff --git a/src/games/pz.rs b/src/games/pz.rs new file mode 100644 index 0000000..2188145 --- /dev/null +++ b/src/games/pz.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 16261, + Some(port) => port + }, SteamID::PZ.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 4e9ad6a..d8e4585 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -173,6 +173,8 @@ pub enum SteamID { INSMIC = 17700, /// ARMA 2: Operation Arrowhead ARMA2OA = 33930, + /// Project Zomboid + PZ = 108600, /// Insurgency INS = 222880, /// Sven Co-op From 9c9a096b16bc567c1a3bee7ac98f01bc8eda953f Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 16 Jan 2023 22:44:55 +0200 Subject: [PATCH 125/597] Valve Protocol: Extend split packets correctly --- CHANGELOG.md | 1 + src/protocols/valve/protocol.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b10b67..ba855c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Games: Protocols: - Valve: Players with no name are no more added to the `players_details` field. +- Valve: Split packets are now appending in the correct order. Crate: - `MSRV` is now `1.56.1` (was `1.58.1`) diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 231f4be..d2c8c5c 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -163,11 +163,18 @@ impl ValveProtocol { buffer.move_position_backward(1); if header == 0xFE { //the packet is split let mut main_packet = SplitPacket::new(&app, protocol, &mut buffer)?; + let mut chunk_packets = Vec::with_capacity((main_packet.total - 1) as usize); for _ in 1..main_packet.total { let new_data = self.socket.receive(Some(buffer_size))?; buffer = Bufferer::new_with_data(Endianess::Little, &new_data); let chunk_packet = SplitPacket::new(&app, protocol, &mut buffer)?; + chunk_packets.push(chunk_packet); + } + + chunk_packets.sort_by(|a, b| a.number.cmp(&b.number)); + + for chunk_packet in chunk_packets { main_packet.payload.extend(chunk_packet.payload); } From e2f42008b2ef6357deb0daeb6531bbde1be65fe6 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 16 Jan 2023 23:10:16 +0200 Subject: [PATCH 126/597] Games: Age of Chivalry support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/aoc.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/aoc.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ba855c4..0cacbf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Games: - [Battalion 1944](https://store.steampowered.com/app/489940/BATTALION_Legacy/) support. - [Black Mesa](https://store.steampowered.com/app/362890/Black_Mesa/) support. - [Project Zomboid](https://store.steampowered.com/app/108600/Project_Zomboid/) support. +- [Age of Chivalry](https://store.steampowered.com/app/17510/Age_of_Chivalry/) support. Protocols: - Valve: Players with no name are no more added to the `players_details` field. diff --git a/GAMES.md b/GAMES.md index 05fa2dd..92ae331 100644 --- a/GAMES.md +++ b/GAMES.md @@ -34,6 +34,7 @@ A supported game is defined as a game that has been successfully tested, other g | Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. It is strongly recommended to also query the rules, as the game sends server information's in them. | | Black Mesa | BM | Valve Protocol | | | Project Zomboid | PZ | Valve Protocol | | +| Age of Chivalry | AOC | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 2ea2b79..6a1e4d4 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, bat1944, bm, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -73,6 +73,7 @@ fn main() -> GDResult<()> { "bat1944" => println!("{:#?}", bat1944::query(ip, port)?), "bm" => println!("{:#?}", bm::query(ip, port)?), "pz" => println!("{:#?}", pz::query(ip, port)?), + "aoc" => println!("{:#?}", aoc::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/aoc.rs b/src/games/aoc.rs new file mode 100644 index 0000000..55f1357 --- /dev/null +++ b/src/games/aoc.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::AOC.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 033ed38..6e59157 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -65,3 +65,5 @@ pub mod bat1944; pub mod bm; /// Project Zomboid pub mod pz; +/// Age of Chivalry +pub mod aoc; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index d8e4585..986f3ed 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -169,6 +169,8 @@ pub enum SteamID { TS = 2400, /// Garry's Mod GM = 4000, + /// Age of Chivalry + AOC = 17510, /// Insurgency: Modern Infantry Combat INSMIC = 17700, /// ARMA 2: Operation Arrowhead From 4fb13507533321b538580841864957b6b5437a2c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 16 Jan 2023 23:31:59 +0200 Subject: [PATCH 127/597] Update some github documentation. --- GAMES.md | 3 ++- README.md | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/GAMES.md b/GAMES.md index 92ae331..6bcfb33 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,4 +1,5 @@ -A supported game is defined as a game that has been successfully tested, other games that use the implemented protocols might work too, but it isn't guaranteed. +A supported game is defined as a game that has been successfully tested, other games that use the implemented protocols might work too, but it isn't guaranteed. +Beware of the `Notes` column, as it contains information about query port offsets or other query requirements. # Supported games: | Game | Use name | Protocol | Notes | diff --git a/README.md b/README.md index e10c783..69e37d3 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ MSRV is `1.56.1` and the code is cross-platform. To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. ## Usage -Just pick a game/service/protocol, provide the ip and the port (can be optional) (some use a special query port) then query on it. +Just pick a game/service/protocol, provide the ip and the port (be aware that some game servers use a separate port for the info queries, the port can also be optional if the server is running the default ports) then query on it. Team Fortress 2 query example: ```rust use gamedig::games::tf2; fn main() { - let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port + let response = tf2::query("127.0.0.1", None); // None is the default port (which is 27015), could also be Some(27015) match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:#?}", r) From f2ae81002ef380a9df5df2605647f4383528b576 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 17 Jan 2023 01:01:53 +0200 Subject: [PATCH 128/597] Protocol: Minecraft: Rename players-related fields --- CHANGELOG.md | 4 ++-- src/protocols/minecraft/protocol/bedrock.rs | 4 ++-- src/protocols/minecraft/protocol/java.rs | 6 +++--- src/protocols/minecraft/protocol/legacy_bv1_8.rs | 6 +++--- src/protocols/minecraft/protocol/legacy_v1_4.rs | 6 +++--- src/protocols/minecraft/protocol/legacy_v1_6.rs | 6 +++--- src/protocols/minecraft/types.rs | 16 ++++++++-------- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cacbf0..8bc5b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,8 @@ Crate: ### Breaking: Protocols: - Valve: The rules field is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and a value fields). -- Valve: `ServerInfo`'s `players`, `max_players` and `bots` have been renamed to `players_online`, `players_maximum` and `players_bots` respectively. -- Valve: `Response`'s `players`, `max_players` and `bots` have been renamed to `players_online`, `players_maximum` and `players_bots` respectively. +- Valve: Structs that contained the `players`, `max_players` and `bots` fields have been renamed to `players_online`, `players_maximum` and `players_bots` respectively. +- Minecraft: Structs that contained the `online_players`, `max_players` and `sample_players` fields have been renamed to `players_online`, `players_maximum` and `players_sample` respectively. Errors: - Besides the `BadGame` error, now no other errors returns details about what happened (as it was quite pointless). diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index b731cb0..33abbef 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -83,8 +83,8 @@ impl Bedrock { name: status[1].to_string(), version_name: status[3].to_string(), version_protocol: status[2].to_string(), - max_players: status[5].parse().map_err(|_| TypeParse)?, - online_players: status[4].parse().map_err(|_| TypeParse)?, + players_maximum: status[5].parse().map_err(|_| TypeParse)?, + players_online: status[4].parse().map_err(|_| TypeParse)?, id: status.get(6).and_then(|v| Some(v.to_string())), map: status.get(7).and_then(|v| Some(v.to_string())), game_mode: match status.get(8) { diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index a2611e6..1d95c03 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -110,9 +110,9 @@ impl Java { Ok(Response { version_name, version_protocol, - max_players, - online_players, - sample_players, + players_maximum: max_players, + players_online: online_players, + players_sample: sample_players, description: value_response["description"].to_string(), favicon: value_response["favicon"].as_str().map(str::to_string), previews_chat: value_response["previewsChat"].as_bool(), diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index caa251c..e02e7dc 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -51,9 +51,9 @@ impl LegacyBV1_8 { Ok(Response { version_name: "Beta 1.8+".to_string(), version_protocol: -1, - max_players, - online_players, - sample_players: None, + players_maximum: max_players, + players_online: online_players, + players_sample: None, description, favicon: None, previews_chat: None, diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index e201dd1..cdd5a95 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -56,9 +56,9 @@ impl LegacyV1_4 { Ok(Response { version_name: "1.4+".to_string(), version_protocol: -1, - max_players, - online_players, - sample_players: None, + players_maximum: max_players, + players_online: online_players, + players_sample: None, description, favicon: None, previews_chat: None, diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 0f80e44..da86356 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -64,9 +64,9 @@ impl LegacyV1_6 { Ok(Response { version_name, version_protocol, - max_players, - online_players, - sample_players: None, + players_maximum: max_players, + players_online: online_players, + players_sample: None, description, favicon: None, previews_chat: None, diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index e95e098..b974b73 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -46,11 +46,11 @@ pub struct Response { /// Version protocol, example: 760 (for 1.19.2). pub version_protocol: i32, /// Number of server capacity. - pub max_players: u32, + pub players_maximum: u32, /// Number of online players. - pub online_players: u32, + pub players_online: u32, /// Some online players (can be missing). - pub sample_players: Option>, + pub players_sample: Option>, /// Server's description or MOTD. pub description: String, /// The favicon (can be missing). @@ -75,9 +75,9 @@ pub struct BedrockResponse { /// Version protocol, example: 760 (for 1.19.2). pub version_protocol: String, /// Number of server capacity. - pub max_players: u32, + pub players_maximum: u32, /// Number of online players. - pub online_players: u32, + pub players_online: u32, /// Server id. pub id: Option, /// The map. @@ -93,9 +93,9 @@ impl Response { Self { version_name: response.version_name, version_protocol: 0, - max_players: response.max_players, - online_players: response.online_players, - sample_players: None, + players_maximum: response.players_maximum, + players_online: response.players_online, + players_sample: None, description: response.name, favicon: None, previews_chat: None, From 21a27fd9cceef98599f25f4d1e17fa5db451acbd Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 17 Jan 2023 01:21:17 +0200 Subject: [PATCH 129/597] Protocol: Minecraft: Rename java's response struct to JavaResponse --- src/games/mc.rs | 12 ++++++------ src/protocols/minecraft/protocol/java.rs | 8 ++++---- src/protocols/minecraft/protocol/legacy_bv1_8.rs | 8 ++++---- src/protocols/minecraft/protocol/legacy_v1_4.rs | 8 ++++---- src/protocols/minecraft/protocol/legacy_v1_6.rs | 10 +++++----- src/protocols/minecraft/protocol/mod.rs | 12 ++++++------ src/protocols/minecraft/types.rs | 6 +++--- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/games/mc.rs b/src/games/mc.rs index 32443e7..6b03328 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,15 +1,15 @@ use crate::{GDError, GDResult}; use crate::protocols::minecraft; -use crate::protocols::minecraft::{Response, LegacyGroup, BedrockResponse}; +use crate::protocols::minecraft::{JavaResponse, LegacyGroup, BedrockResponse}; /// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &str, port: Option) -> GDResult { if let Ok(response) = query_java(address, port) { return Ok(response); } if let Ok(response) = query_bedrock(address, port) { - return Ok(Response::from_bedrock_response(response)); + return Ok(JavaResponse::from_bedrock_response(response)); } if let Ok(response) = query_legacy(address, port) { @@ -20,17 +20,17 @@ pub fn query(address: &str, port: Option) -> GDResult { } /// Query a Java Server. -pub fn query_java(address: &str, port: Option) -> GDResult { +pub fn query_java(address: &str, port: Option) -> GDResult { minecraft::query_java(address, port_or_java_default(port), None) } /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: Option) -> GDResult { +pub fn query_legacy(address: &str, port: Option) -> GDResult { minecraft::query_legacy(address, port_or_java_default(port), None) } /// Query a specific (Java) Legacy Server. -pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { +pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) } diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index 1d95c03..e71ecb1 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -2,7 +2,7 @@ use serde_json::Value; use crate::GDResult; use crate::GDError::{JsonParse, PacketBad}; use crate::bufferer::{Bufferer, Endianess}; -use crate::protocols::minecraft::{as_varint, get_string, get_varint, Player, Response, Server}; +use crate::protocols::minecraft::{as_varint, get_string, get_varint, Player, JavaResponse, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; @@ -65,7 +65,7 @@ impl Java { Ok(()) } - fn get_info(&mut self) -> GDResult { + fn get_info(&mut self) -> GDResult { self.send_handshake()?; self.send_status_request()?; self.send_ping_request()?; @@ -107,7 +107,7 @@ impl Java { }) }; - Ok(Response { + Ok(JavaResponse { version_name, version_protocol, players_maximum: max_players, @@ -121,7 +121,7 @@ impl Java { }) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { Java::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index e02e7dc..e4cdf29 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -2,7 +2,7 @@ use crate::GDResult; use crate::bufferer::{Bufferer, Endianess}; use crate::GDError::{PacketBad, ProtocolFormat}; -use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; use crate::utils::error_by_expected_size; @@ -25,7 +25,7 @@ impl LegacyBV1_8 { self.socket.send(&[0xFE]) } - fn get_info(&mut self) -> GDResult { + fn get_info(&mut self) -> GDResult { self.send_initial_request()?; let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); @@ -48,7 +48,7 @@ impl LegacyBV1_8 { let max_players = split[2].parse() .map_err(|_| PacketBad)?; - Ok(Response { + Ok(JavaResponse { version_name: "Beta 1.8+".to_string(), version_protocol: -1, players_maximum: max_players, @@ -62,7 +62,7 @@ impl LegacyBV1_8 { }) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { LegacyBV1_8::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index cdd5a95..69195eb 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -2,7 +2,7 @@ use crate::GDResult; use crate::bufferer::{Bufferer, Endianess}; use crate::GDError::{PacketBad, ProtocolFormat}; -use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; @@ -26,7 +26,7 @@ impl LegacyV1_4 { self.socket.send(&[0xFE, 0x01]) } - fn get_info(&mut self) -> GDResult { + fn get_info(&mut self) -> GDResult { self.send_initial_request()?; let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); @@ -53,7 +53,7 @@ impl LegacyV1_4 { let max_players = split[2].parse() .map_err(|_| PacketBad)?; - Ok(Response { + Ok(JavaResponse { version_name: "1.4+".to_string(), version_protocol: -1, players_maximum: max_players, @@ -67,7 +67,7 @@ impl LegacyV1_4 { }) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { LegacyV1_4::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index da86356..6188dbf 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -1,7 +1,7 @@ use crate::GDResult; use crate::GDError::{PacketBad, ProtocolFormat}; use crate::bufferer::{Bufferer, Endianess}; -use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, TcpSocket}; use crate::utils::error_by_expected_size; @@ -46,7 +46,7 @@ impl LegacyV1_6 { Ok(state) } - pub fn get_response(buffer: &mut Bufferer) -> GDResult { + pub fn get_response(buffer: &mut Bufferer) -> GDResult { let packet_string = buffer.get_string_utf16()?; let split: Vec<&str> = packet_string.split("\x00").collect(); @@ -61,7 +61,7 @@ impl LegacyV1_6 { let max_players = split[4].parse() .map_err(|_| PacketBad)?; - Ok(Response { + Ok(JavaResponse { version_name, version_protocol, players_maximum: max_players, @@ -75,7 +75,7 @@ impl LegacyV1_6 { }) } - fn get_info(&mut self) -> GDResult { + fn get_info(&mut self) -> GDResult { self.send_initial_request()?; let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); @@ -94,7 +94,7 @@ impl LegacyV1_6 { LegacyV1_6::get_response(&mut buffer) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { LegacyV1_6::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 2750b45..73bcb90 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,6 +1,6 @@ use crate::GDError::AutoQuery; use crate::GDResult; -use crate::protocols::minecraft::{BedrockResponse, LegacyGroup, Response}; +use crate::protocols::minecraft::{BedrockResponse, LegacyGroup, JavaResponse}; use crate::protocols::minecraft::protocol::bedrock::Bedrock; use crate::protocols::minecraft::protocol::java::Java; use crate::protocols::minecraft::protocol::legacy_v1_4::LegacyV1_4; @@ -15,13 +15,13 @@ mod legacy_bv1_8; mod bedrock; /// Queries a Minecraft server with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { if let Ok(response) = query_java(address, port, timeout_settings.clone()) { return Ok(response); } if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) { - return Ok(Response::from_bedrock_response(response)); + return Ok(JavaResponse::from_bedrock_response(response)); } if let Ok(response) = query_legacy(address, port, timeout_settings) { @@ -32,12 +32,12 @@ pub fn query(address: &str, port: u16, timeout_settings: Option } /// Query a Java Server. -pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { Java::query(address, port, timeout_settings) } /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { return Ok(response); } @@ -54,7 +54,7 @@ pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: u16, timeout_settings: Option) -> GDResult { match group { LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index b974b73..aa85e82 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -38,9 +38,9 @@ pub struct Player { pub id: String } -/// A query response. +/// A Java query response. #[derive(Debug)] -pub struct Response { +pub struct JavaResponse { /// Version name, example: "1.19.2". pub version_name: String, /// Version protocol, example: 760 (for 1.19.2). @@ -88,7 +88,7 @@ pub struct BedrockResponse { pub server_type: Server } -impl Response { +impl JavaResponse { pub fn from_bedrock_response(response: BedrockResponse) -> Self { Self { version_name: response.version_name, From 6ec2b8952c12b04d551c7670657b03e092276b4d Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 17 Jan 2023 01:21:34 +0200 Subject: [PATCH 130/597] Update docs. --- CHANGELOG.md | 1 + src/protocols/minecraft/types.rs | 16 ++++++++-------- src/protocols/types.rs | 2 +- src/protocols/valve/types.rs | 33 +++++++++++++++++++++++++++----- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bc5b57..ebfee80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Protocols: - Valve: The rules field is now a `HashMap` instead of a `Vec` (where the `ServerRule` structure had a name and a value fields). - Valve: Structs that contained the `players`, `max_players` and `bots` fields have been renamed to `players_online`, `players_maximum` and `players_bots` respectively. - Minecraft: Structs that contained the `online_players`, `max_players` and `sample_players` fields have been renamed to `players_online`, `players_maximum` and `players_sample` respectively. +- Minecraft: The Java query response struct named `Response` has been renamed to `JavaResponse`. Errors: - Besides the `BadGame` error, now no other errors returns details about what happened (as it was quite pointless). diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index aa85e82..1723b81 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -43,7 +43,7 @@ pub struct Player { pub struct JavaResponse { /// Version name, example: "1.19.2". pub version_name: String, - /// Version protocol, example: 760 (for 1.19.2). + /// Version protocol, example: 760 (for 1.19.2). Note that for versions below 1.6 this field is always -1. pub version_protocol: i32, /// Number of server capacity. pub players_maximum: u32, @@ -66,25 +66,25 @@ pub struct JavaResponse { /// A Bedrock Edition query response. #[derive(Debug)] pub struct BedrockResponse { - /// Server edition. + /// Server's edition. pub edition: String, - /// Server name. + /// Server's name. pub name: String, /// Version name, example: "1.19.40". pub version_name: String, /// Version protocol, example: 760 (for 1.19.2). pub version_protocol: String, - /// Number of server capacity. + /// Maximum number of players the server reports it can hold. pub players_maximum: u32, - /// Number of online players. + /// Number of players on the server. pub players_online: u32, /// Server id. pub id: Option, - /// The map. + /// Currently running map's name. pub map: Option, - /// Game mode. + /// Current game mode. pub game_mode: Option, - /// Tell's the server type. + /// Tells the server type. pub server_type: Server } diff --git a/src/protocols/types.rs b/src/protocols/types.rs index eebeb88..5e0bac6 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -10,7 +10,7 @@ pub struct TimeoutSettings { } impl TimeoutSettings { - /// Construct new settings, passing None will block indefinitely. Passing zero Duration throws GDError::[InvalidInput](GDError::InvalidInput). + /// Construct new settings, passing None will block indefinitely. Passing zero Duration throws GDError::[InvalidInput](InvalidInput). pub fn new(read: Option, write: Option) -> GDResult { if let Some(read_duration) = read { if read_duration == Duration::new(0, 0) { diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 986f3ed..fcb955f 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -35,7 +35,7 @@ pub struct ServerInfo { pub map: String, /// Name of the folder containing the game files. pub folder: String, - /// Full name of the game. + /// The name of the game. pub game: String, /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. pub appid: u32, @@ -72,7 +72,7 @@ pub struct ServerPlayer { pub name: String, /// General score. pub score: u32, - /// How long they've been on the server for. + /// How long a player has been in the server (seconds). pub duration: f32, /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): deaths count pub deaths: Option, //the_ship @@ -95,11 +95,11 @@ pub struct ExtraData { pub port: Option, /// Server's SteamID. pub steam_id: Option, - /// Spectator port number for SourceTV. + /// SourceTV's port. pub tv_port: Option, - /// Name of the spectator server for SourceTV. + /// SourceTV's name. pub tv_name: Option, - /// Tags that describe the game according to the server. + /// Keywords that describe the server according to it. pub keywords: Option, /// The server's 64-bit GameID. pub game_id: Option @@ -250,10 +250,14 @@ pub mod game { use crate::protocols::valve::types::get_optional_extracted_data; use super::{Server, ServerPlayer}; + /// A player's details. #[derive(Debug)] pub struct Player { + /// Player's name. pub name: String, + /// Player's score. pub score: u32, + /// How long a player has been in the server (seconds). pub duration: f32 } @@ -267,25 +271,44 @@ pub mod game { } } + /// The query response. #[derive(Debug)] pub struct Response { + /// Protocol used by the server. pub protocol: u8, + /// Name of the server. pub name: String, + /// Map name. pub map: String, + /// The name of the game. pub game: String, + /// Number of players on the server. pub players_online: u8, + /// Details about the server's players (not all players necessarily). pub players_details: Vec, + /// Maximum number of players the server reports it can hold. pub players_maximum: u8, + /// Number of bots on the server. pub players_bots: u8, + /// Dedicated, NonDedicated or SourceTV pub server_type: Server, + /// Indicates whether the server requires a password. pub has_password: bool, + /// Indicated whether the server uses VAC. pub vac_secured: bool, + /// Version of the game installed on the server. pub version: String, + /// The server's reported connection port. pub port: Option, + /// Server's SteamID. pub steam_id: Option, + /// SourceTV's connection port. pub tv_port: Option, + /// SourceTV's name. pub tv_name: Option, + /// Keywords that describe the server according to it. pub keywords: Option, + /// Server's rules. pub rules: HashMap } From dfe544c6aa5fe803a7079c05fa8a931b79f1a612 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 17 Jan 2023 02:02:49 +0200 Subject: [PATCH 131/597] Bump version to 0.1.0 --- CHANGELOG.md | 2 +- Cargo.toml | 4 ++-- README.md | 2 +- src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebfee80..3bda8cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Who knows what the future holds... -# X - DD/MM/YYYY +# 0.1.0 - 17/01/2023 ### Changes: Games: - [Risk of Rain 2](https://store.steampowered.com/app/632360/Risk_of_Rain_2/) support. diff --git a/Cargo.toml b/Cargo.toml index 989db67..ff89607 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.0.7" +version = "0.1.0" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" @@ -16,4 +16,4 @@ rust-version = "1.56.1" bzip2-rs = "0.1.2" # for compression crc32fast = "1.3.2" -serde_json = "1.0.87" # json to structs +serde_json = "1.0.91" # json to structs diff --git a/README.md b/README.md index 69e37d3..a36b697 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ use gamedig::games::tf2; fn main() { let response = tf2::query("127.0.0.1", None); // None is the default port (which is 27015), could also be Some(27015) match response { - Err(error) => println!("Couldn't query, error: {error}"), + Err(error) => println!("Couldn't query, error: {}", error), Ok(r) => println!("{:#?}", r) } } diff --git a/src/lib.rs b/src/lib.rs index b892c3f..3b53070 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ //! fn main() { //! let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port //! match response { -//! Err(error) => println!("Couldn't query, error: {error}"), +//! Err(error) => println!("Couldn't query, error: {}", error), //! Ok(r) => println!("{:?}", r) //! } //! } From bbd2dd7d97ae594110320431795b0ab0388d892c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Wed, 18 Jan 2023 17:57:19 +0200 Subject: [PATCH 132/597] Games: Don't Starve Together support. --- CHANGELOG.md | 8 ++++++++ GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/dst.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/games/dst.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bda8cc..f16e0ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Who knows what the future holds... +# 0.X.Y - DD/MM/2023 +### Changes: +Games: +- [Don't Starve Together](https://store.steampowered.com/app/322330/Dont_Starve_Together/) support. + +### Breaking: +Nothing. + # 0.1.0 - 17/01/2023 ### Changes: Games: diff --git a/GAMES.md b/GAMES.md index 6bcfb33..32e10f9 100644 --- a/GAMES.md +++ b/GAMES.md @@ -36,6 +36,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Black Mesa | BM | Valve Protocol | | | Project Zomboid | PZ | Valve Protocol | | | Age of Chivalry | AOC | Valve Protocol | | +| Don't Starve Together | DST | Valve Protocol | Query port is 27016. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 6a1e4d4..1900616 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -74,6 +74,7 @@ fn main() -> GDResult<()> { "bm" => println!("{:#?}", bm::query(ip, port)?), "pz" => println!("{:#?}", pz::query(ip, port)?), "aoc" => println!("{:#?}", aoc::query(ip, port)?), + "dst" => println!("{:#?}", dst::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/dst.rs b/src/games/dst.rs new file mode 100644 index 0000000..012f141 --- /dev/null +++ b/src/games/dst.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27016, + Some(port) => port + }, SteamID::DST.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 6e59157..c2c17d9 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -67,3 +67,5 @@ pub mod bm; pub mod pz; /// Age of Chivalry pub mod aoc; +/// Don't Starve Together +pub mod dst; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index fcb955f..04963b9 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -183,6 +183,8 @@ pub enum SteamID { SC = 225840, /// Rust RUST = 252490, + /// Don't Starve Together + DST = 322320, /// Black Mesa BM = 362890, /// Day of Infamy From 2312ba9114eb4a8d4d2258b4b9b28b15929b6b45 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Wed, 18 Jan 2023 18:06:08 +0200 Subject: [PATCH 133/597] Games: Colony Survival support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/cosu.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/cosu.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f16e0ca..4f6873e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Who knows what the future holds... ### Changes: Games: - [Don't Starve Together](https://store.steampowered.com/app/322330/Dont_Starve_Together/) support. +- [Colony Survival](https://store.steampowered.com/app/366090/Colony_Survival/) support. ### Breaking: Nothing. diff --git a/GAMES.md b/GAMES.md index 32e10f9..17e9fd0 100644 --- a/GAMES.md +++ b/GAMES.md @@ -37,6 +37,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Project Zomboid | PZ | Valve Protocol | | | Age of Chivalry | AOC | Valve Protocol | | | Don't Starve Together | DST | Valve Protocol | Query port is 27016. | +| Colony Survival | COLU | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 1900616..0263456 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -75,6 +75,7 @@ fn main() -> GDResult<()> { "pz" => println!("{:#?}", pz::query(ip, port)?), "aoc" => println!("{:#?}", aoc::query(ip, port)?), "dst" => println!("{:#?}", dst::query(ip, port)?), + "cosu" => println!("{:#?}", cosu::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/cosu.rs b/src/games/cosu.rs new file mode 100644 index 0000000..0b883fd --- /dev/null +++ b/src/games/cosu.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27004, + Some(port) => port + }, SteamID::COSU.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index c2c17d9..bb843ee 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -69,3 +69,5 @@ pub mod pz; pub mod aoc; /// Don't Starve Together pub mod dst; +/// Colony Survival +pub mod cosu; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 04963b9..e97f2e5 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -187,6 +187,8 @@ pub enum SteamID { DST = 322320, /// Black Mesa BM = 362890, + /// Colony Survival + COSU = 366090, /// Day of Infamy DOI = 447820, /// The Forrest From 649dfd81ed3e9c9ca2248b5f2133909e5adb06ee Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Wed, 18 Jan 2023 18:13:50 +0200 Subject: [PATCH 134/597] Games: Onset support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/onset.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/onset.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6873e..cae349d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Who knows what the future holds... Games: - [Don't Starve Together](https://store.steampowered.com/app/322330/Dont_Starve_Together/) support. - [Colony Survival](https://store.steampowered.com/app/366090/Colony_Survival/) support. +- [Onset](https://store.steampowered.com/app/1105810/Onset/) support. ### Breaking: Nothing. diff --git a/GAMES.md b/GAMES.md index 17e9fd0..b3ce845 100644 --- a/GAMES.md +++ b/GAMES.md @@ -38,6 +38,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Age of Chivalry | AOC | Valve Protocol | | | Don't Starve Together | DST | Valve Protocol | Query port is 27016. | | Colony Survival | COLU | Valve Protocol | | +| Onset | ONSET | Valve Protocol | Query port is 7776. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 0263456..f0f39da 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -76,6 +76,7 @@ fn main() -> GDResult<()> { "aoc" => println!("{:#?}", aoc::query(ip, port)?), "dst" => println!("{:#?}", dst::query(ip, port)?), "cosu" => println!("{:#?}", cosu::query(ip, port)?), + "onset" => println!("{:#?}", onset::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/mod.rs b/src/games/mod.rs index bb843ee..fcff5f3 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -71,3 +71,5 @@ pub mod aoc; pub mod dst; /// Colony Survival pub mod cosu; +/// Onset +pub mod onset; diff --git a/src/games/onset.rs b/src/games/onset.rs new file mode 100644 index 0000000..1f82d52 --- /dev/null +++ b/src/games/onset.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 7776, + Some(port) => port + }, SteamID::ONSET.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index e97f2e5..3ceb42f 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -207,6 +207,8 @@ pub enum SteamID { ASRD = 563560, /// Risk of Rain 2 ROR2 = 632360, + /// Onset + ONSET = 1105810, } impl SteamID { From bdaa1c4f643eb7d66889f54abb49a0c9086e9988 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 18:02:21 +0200 Subject: [PATCH 135/597] Move up SDTD Steam ID --- src/protocols/valve/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 3ceb42f..5f0c1cc 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -181,6 +181,8 @@ pub enum SteamID { INS = 222880, /// Sven Co-op SC = 225840, + /// 7 Days To Die + SDTD = 251570, /// Rust RUST = 252490, /// Don't Starve Together @@ -193,8 +195,6 @@ pub enum SteamID { DOI = 447820, /// The Forrest TF = 556450, //this is the id for the dedicated server, for the game its 242760 - /// 7 Days To Die - SDTD = 251570, /// Unturned UNTURNED = 304930, /// ARK: Survival Evolved From df9005cc9f57d9ffc0b21b86968290d627c89f47 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 18:09:20 +0200 Subject: [PATCH 136/597] Games: Codename CURE support. --- CHANGELOG.md | 3 ++- GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/ccure.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/games/ccure.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cae349d..8349d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ Who knows what the future holds... -# 0.X.Y - DD/MM/2023 +# 0.1.Y - DD/MM/2023 ### Changes: Games: - [Don't Starve Together](https://store.steampowered.com/app/322330/Dont_Starve_Together/) support. - [Colony Survival](https://store.steampowered.com/app/366090/Colony_Survival/) support. - [Onset](https://store.steampowered.com/app/1105810/Onset/) support. +- [Codename CURE](https://store.steampowered.com/app/355180/Codename_CURE/) support. ### Breaking: Nothing. diff --git a/GAMES.md b/GAMES.md index b3ce845..941fbc6 100644 --- a/GAMES.md +++ b/GAMES.md @@ -39,6 +39,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Don't Starve Together | DST | Valve Protocol | Query port is 27016. | | Colony Survival | COLU | Valve Protocol | | | Onset | ONSET | Valve Protocol | Query port is 7776. | +| Codename CURE | CCURE | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index f0f39da..03112f1 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -77,6 +77,7 @@ fn main() -> GDResult<()> { "dst" => println!("{:#?}", dst::query(ip, port)?), "cosu" => println!("{:#?}", cosu::query(ip, port)?), "onset" => println!("{:#?}", onset::query(ip, port)?), + "ccure" => println!("{:#?}", ccure::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/ccure.rs b/src/games/ccure.rs new file mode 100644 index 0000000..062b32f --- /dev/null +++ b/src/games/ccure.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::CCURE.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index fcff5f3..2038c42 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -73,3 +73,5 @@ pub mod dst; pub mod cosu; /// Onset pub mod onset; +/// Codename CURE +pub mod ccure; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 5f0c1cc..c11049a 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -187,6 +187,8 @@ pub enum SteamID { RUST = 252490, /// Don't Starve Together DST = 322320, + /// Codename CURE + CCURE = 355180, /// Black Mesa BM = 362890, /// Colony Survival From eca9757421e88a88bc7a197c3282f865b42e29bf Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 18:17:19 +0200 Subject: [PATCH 137/597] Ballistic Overkill support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/bo.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/bo.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8349d34..905669e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Games: - [Colony Survival](https://store.steampowered.com/app/366090/Colony_Survival/) support. - [Onset](https://store.steampowered.com/app/1105810/Onset/) support. - [Codename CURE](https://store.steampowered.com/app/355180/Codename_CURE/) support. +- [Ballistic Overkill](https://store.steampowered.com/app/296300/Ballistic_Overkill/) support. ### Breaking: Nothing. diff --git a/GAMES.md b/GAMES.md index 941fbc6..a5a9f94 100644 --- a/GAMES.md +++ b/GAMES.md @@ -40,6 +40,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Colony Survival | COLU | Valve Protocol | | | Onset | ONSET | Valve Protocol | Query port is 7776. | | Codename CURE | CCURE | Valve Protocol | | +| Ballistic Overkill | BO | Valve Protocol | Query port is 27016. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 03112f1..a461300 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -78,6 +78,7 @@ fn main() -> GDResult<()> { "cosu" => println!("{:#?}", cosu::query(ip, port)?), "onset" => println!("{:#?}", onset::query(ip, port)?), "ccure" => println!("{:#?}", ccure::query(ip, port)?), + "bo" => println!("{:#?}", bo::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/bo.rs b/src/games/bo.rs new file mode 100644 index 0000000..dcd9a54 --- /dev/null +++ b/src/games/bo.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27016, + Some(port) => port + }, SteamID::BO.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 2038c42..7e1578b 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -75,3 +75,5 @@ pub mod cosu; pub mod onset; /// Codename CURE pub mod ccure; +/// Ballistic Overkill +pub mod bo; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index c11049a..a98c61e 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -185,6 +185,8 @@ pub enum SteamID { SDTD = 251570, /// Rust RUST = 252490, + /// Vallistic Overkill + BO = 296300, /// Don't Starve Together DST = 322320, /// Codename CURE From e16efee488118456468601d59d00392eb48d435c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 18:25:17 +0200 Subject: [PATCH 138/597] Games: BrainBread 2 support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/bb2.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/bb2.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 905669e..1b64c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Games: - [Onset](https://store.steampowered.com/app/1105810/Onset/) support. - [Codename CURE](https://store.steampowered.com/app/355180/Codename_CURE/) support. - [Ballistic Overkill](https://store.steampowered.com/app/296300/Ballistic_Overkill/) support. +- [BrainBread 2](https://store.steampowered.com/app/346330/BrainBread_2/) support. ### Breaking: Nothing. diff --git a/GAMES.md b/GAMES.md index a5a9f94..cfdda17 100644 --- a/GAMES.md +++ b/GAMES.md @@ -41,6 +41,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Onset | ONSET | Valve Protocol | Query port is 7776. | | Codename CURE | CCURE | Valve Protocol | | | Ballistic Overkill | BO | Valve Protocol | Query port is 27016. | +| BrainBread 2 | BB2 | Valve Protocol | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index a461300..0878adb 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -79,6 +79,7 @@ fn main() -> GDResult<()> { "onset" => println!("{:#?}", onset::query(ip, port)?), "ccure" => println!("{:#?}", ccure::query(ip, port)?), "bo" => println!("{:#?}", bo::query(ip, port)?), + "bb2" => println!("{:#?}", bb2::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/bb2.rs b/src/games/bb2.rs new file mode 100644 index 0000000..14dc9e1 --- /dev/null +++ b/src/games/bb2.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27015, + Some(port) => port + }, SteamID::BB2.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 7e1578b..8dea30d 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -77,3 +77,5 @@ pub mod onset; pub mod ccure; /// Ballistic Overkill pub mod bo; +/// BrainBread 2 +pub mod bb2; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index a98c61e..704c57e 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -189,6 +189,8 @@ pub enum SteamID { BO = 296300, /// Don't Starve Together DST = 322320, + /// BrainBread 2 + BB2 = 346330, /// Codename CURE CCURE = 355180, /// Black Mesa From 3231653e4c89ccac8edcb60a83788534abc248b5 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 18:28:06 +0200 Subject: [PATCH 139/597] Reword Battalion 1944 query note message --- GAMES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GAMES.md b/GAMES.md index cfdda17..f20fea8 100644 --- a/GAMES.md +++ b/GAMES.md @@ -32,7 +32,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Day of Infamy | DOI | Valve Protocol | | | Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | | Risk of Rain 2 | ROR2 | Valve Protocol | Query port offset: 1. | -| Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. It is strongly recommended to also query the rules, as the game sends server information's in them. | +| Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. It is strongly recommended to also query the rules, as it sends basic server info in them. | | Black Mesa | BM | Valve Protocol | | | Project Zomboid | PZ | Valve Protocol | | | Age of Chivalry | AOC | Valve Protocol | | From 719ae9d591489a8d773268e1aefeafd7511063cb Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 18:37:02 +0200 Subject: [PATCH 140/597] Games: Avorion support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/avorion.rs | 12 ++++++++++++ src/games/mod.rs | 2 ++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/avorion.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b64c06..787bdf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Games: - [Codename CURE](https://store.steampowered.com/app/355180/Codename_CURE/) support. - [Ballistic Overkill](https://store.steampowered.com/app/296300/Ballistic_Overkill/) support. - [BrainBread 2](https://store.steampowered.com/app/346330/BrainBread_2/) support. +- [Avorion](https://store.steampowered.com/app/445220/Avorion/) support. ### Breaking: Nothing. diff --git a/GAMES.md b/GAMES.md index f20fea8..2a01210 100644 --- a/GAMES.md +++ b/GAMES.md @@ -42,6 +42,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Codename CURE | CCURE | Valve Protocol | | | Ballistic Overkill | BO | Valve Protocol | Query port is 27016. | | BrainBread 2 | BB2 | Valve Protocol | | +| Avorion | AVORION | Valve Protocol | Query port is 27020. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 0878adb..fa2020b 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -80,6 +80,7 @@ fn main() -> GDResult<()> { "ccure" => println!("{:#?}", ccure::query(ip, port)?), "bo" => println!("{:#?}", bo::query(ip, port)?), "bb2" => println!("{:#?}", bb2::query(ip, port)?), + "avorion" => println!("{:#?}", avorion::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/avorion.rs b/src/games/avorion.rs new file mode 100644 index 0000000..a0b5a20 --- /dev/null +++ b/src/games/avorion.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27020, + Some(port) => port + }, SteamID::AVORION.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 8dea30d..056cd42 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -79,3 +79,5 @@ pub mod ccure; pub mod bo; /// BrainBread 2 pub mod bb2; +/// Avorion +pub mod avorion; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 704c57e..42d50d6 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -197,6 +197,8 @@ pub enum SteamID { BM = 362890, /// Colony Survival COSU = 366090, + /// Avorion + AVORION = 445220, /// Day of Infamy DOI = 447820, /// The Forrest From fe46359e4721ab1774365e30e8759d924879098a Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 19:00:54 +0200 Subject: [PATCH 141/597] Games: Operation Harsh Doorstop support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/ohd.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 2 ++ 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/games/ohd.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 787bdf2..edf6508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Games: - [Ballistic Overkill](https://store.steampowered.com/app/296300/Ballistic_Overkill/) support. - [BrainBread 2](https://store.steampowered.com/app/346330/BrainBread_2/) support. - [Avorion](https://store.steampowered.com/app/445220/Avorion/) support. +- [Operation: Harsh Doorstop](https://store.steampowered.com/app/736590/Operation_Harsh_Doorstop/) support. ### Breaking: Nothing. diff --git a/GAMES.md b/GAMES.md index 2a01210..2e80457 100644 --- a/GAMES.md +++ b/GAMES.md @@ -43,6 +43,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Ballistic Overkill | BO | Valve Protocol | Query port is 27016. | | BrainBread 2 | BB2 | Valve Protocol | | | Avorion | AVORION | Valve Protocol | Query port is 27020. | +| Operation: Harsh Doorstop | OHD | Valve Protocol | Query port is 27005. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index fa2020b..1c8581d 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -81,6 +81,7 @@ fn main() -> GDResult<()> { "bo" => println!("{:#?}", bo::query(ip, port)?), "bb2" => println!("{:#?}", bb2::query(ip, port)?), "avorion" => println!("{:#?}", avorion::query(ip, port)?), + "ohd" => println!("{:#?}", ohd::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/mod.rs b/src/games/mod.rs index 056cd42..89cf589 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -81,3 +81,5 @@ pub mod bo; pub mod bb2; /// Avorion pub mod avorion; +/// Operation: Harsh Doorstop +pub mod ohd; diff --git a/src/games/ohd.rs b/src/games/ohd.rs new file mode 100644 index 0000000..c52c40b --- /dev/null +++ b/src/games/ohd.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamID}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27005, + Some(port) => port + }, SteamID::OHD.as_app(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 42d50d6..1074b45 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -215,6 +215,8 @@ pub enum SteamID { ASRD = 563560, /// Risk of Rain 2 ROR2 = 632360, + /// Operation: Harsh Doorstop + OHD = 950900, // this is the id for the dedicated server, for the game its 736590 /// Onset ONSET = 1105810, } From 150bc1762e7f3fe768f3004ecf51a6f92ef69622 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 21:44:17 +0200 Subject: [PATCH 142/597] [Protocol] Valve: support app and dedicated app id --- CHANGELOG.md | 23 ++++- Cargo.toml | 2 +- LICENSE.md | 2 +- README.md | 3 +- examples/master_querant.rs | 8 +- examples/tf2.rs | 2 +- src/games/aliens.rs | 4 +- src/games/aoc.rs | 4 +- src/games/arma2oa.rs | 4 +- src/games/ase.rs | 4 +- src/games/asrd.rs | 4 +- src/games/avorion.rs | 4 +- src/games/bat1944.rs | 4 +- src/games/bb2.rs | 4 +- src/games/bm.rs | 4 +- src/games/bo.rs | 4 +- src/games/ccure.rs | 4 +- src/games/cosu.rs | 4 +- src/games/cs.rs | 4 +- src/games/cscz.rs | 4 +- src/games/csgo.rs | 4 +- src/games/css.rs | 4 +- src/games/dod.rs | 4 +- src/games/dods.rs | 4 +- src/games/doi.rs | 4 +- src/games/dst.rs | 4 +- src/games/gm.rs | 4 +- src/games/hl2dm.rs | 4 +- src/games/hldms.rs | 4 +- src/games/ins.rs | 4 +- src/games/insmic.rs | 4 +- src/games/inss.rs | 4 +- src/games/l4d.rs | 4 +- src/games/l4d2.rs | 4 +- src/games/ohd.rs | 4 +- src/games/onset.rs | 4 +- src/games/pz.rs | 4 +- src/games/ror2.rs | 4 +- src/games/rust.rs | 4 +- src/games/sc.rs | 4 +- src/games/sdtd.rs | 4 +- src/games/tf.rs | 4 +- src/games/tf2.rs | 4 +- src/games/tfc.rs | 4 +- src/games/ts.rs | 4 +- src/games/unturned.rs | 4 +- src/protocols/valve/protocol.rs | 74 +++++++------- src/protocols/valve/types.rs | 164 +++++++++++++++++++++----------- 48 files changed, 259 insertions(+), 179 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edf6508..d6985f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ Who knows what the future holds... -# 0.1.Y - DD/MM/2023 +# 0.X.Y - DD/MM/2023 +### Changes: +None. + +### Breaking: +None. + +# 0.2.0 - 18/02/2023 ### Changes: Games: - [Don't Starve Together](https://store.steampowered.com/app/322330/Dont_Starve_Together/) support. @@ -13,8 +20,20 @@ Games: - [Avorion](https://store.steampowered.com/app/445220/Avorion/) support. - [Operation: Harsh Doorstop](https://store.steampowered.com/app/736590/Operation_Harsh_Doorstop/) support. +Protocols: +- Valve: +1. `appid` is now a field in the `Response` struct. + ### Breaking: -Nothing. +Protocols: +- Valve: +due to some games being able to host a server from within the game AND from a dedicated server, +if you were to query one of them, the query would fail for the other one, as the `SteamID` enum +for that game could specify only one id. +1. `SteamID` is now `SteamApp`, was an u32 enum, and now it's a simple enum. +2. `App` is now `Engine`, the `Source` enum's structure has been changed from `Option` to +`Option>`, where the first parameter is the game app id and the second is +the dedicated server app id (if there is one). # 0.1.0 - 17/01/2023 ### Changes: diff --git a/Cargo.toml b/Cargo.toml index ff89607..584416a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" diff --git a/LICENSE.md b/LICENSE.md index 7fa2763..9362443 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 CosminPerRam +Copyright (c) 2023 CosminPerRam Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a36b697..9b202ef 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ use gamedig::games::tf2; fn main() { let response = tf2::query("127.0.0.1", None); // None is the default port (which is 27015), could also be Some(27015) - match response { + match response { // Result type, must check what it is... Err(error) => println!("Couldn't query, error: {}", error), Ok(r) => println!("{:#?}", r) } @@ -28,6 +28,7 @@ Response (note that some games have a different structure): name: "Team Fortress 2 Dedicated Server.", map: "ctf_turbine", game: "tf2", + appid: 440, players_online: 0, players_details: [], players_maximum: 69, diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 1c8581d..e89c643 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -3,7 +3,7 @@ use std::env; use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; -use gamedig::protocols::valve::App; +use gamedig::protocols::valve::Engine; fn main() -> GDResult<()> { let args: Vec = env::args().collect(); @@ -48,9 +48,9 @@ fn main() -> GDResult<()> { "ts" => println!("{:#?}", ts::query(ip, port)?), "cscz" => println!("{:#?}", cscz::query(ip, port)?), "dod" => println!("{:#?}", dod::query(ip, port)?), - "_src" => println!("{:#?}", valve::query(ip, port.unwrap(), App::Source(None), None, None)?), - "_gld" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(false), None, None)?), - "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(true), None, None)?), + "_src" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::Source(None), None, None)?), + "_gld" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::GoldSrc(false), None, None)?), + "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::GoldSrc(true), None, None)?), "mc" => println!("{:#?}", mc::query(ip, port)?), "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), diff --git a/examples/tf2.rs b/examples/tf2.rs index 8a612c9..058fde8 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -3,7 +3,7 @@ use gamedig::games::tf2; fn main() { let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port (which is 27015) - match response { + match response { // Result type, must check what it is... Err(error) => println!("Couldn't query, error: {}", error), Ok(r) => println!("{:#?}", r) } diff --git a/src/games/aliens.rs b/src/games/aliens.rs index 3e8dc43..07f55f2 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::ALIENS.as_app(), None, None)?; + }, SteamApp::ALIENS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/aoc.rs b/src/games/aoc.rs index 55f1357..ff7e421 100644 --- a/src/games/aoc.rs +++ b/src/games/aoc.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::AOC.as_app(), None, None)?; + }, SteamApp::AOC.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/arma2oa.rs b/src/games/arma2oa.rs index 5ea6f86..c5b88ff 100644 --- a/src/games/arma2oa.rs +++ b/src/games/arma2oa.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 2304, Some(port) => port - }, SteamID::ARMA2OA.as_app(), None, None)?; + }, SteamApp::ARMA2OA.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ase.rs b/src/games/ase.rs index 30d2092..e863186 100644 --- a/src/games/ase.rs +++ b/src/games/ase.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::ASE.as_app(), None, None)?; + }, SteamApp::ASE.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 4852233..5caf1f4 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::ASRD.as_app(), None, None)?; + }, SteamApp::ASRD.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/avorion.rs b/src/games/avorion.rs index a0b5a20..51f9fa1 100644 --- a/src/games/avorion.rs +++ b/src/games/avorion.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27020, Some(port) => port - }, SteamID::AVORION.as_app(), None, None)?; + }, SteamApp::AVORION.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs index 135038c..c564b8d 100644 --- a/src/games/bat1944.rs +++ b/src/games/bat1944.rs @@ -1,13 +1,13 @@ use crate::GDError::TypeParse; use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let mut valve_response = valve::query(address, match port { None => 7780, Some(port) => port - }, SteamID::BAT1944.as_app(), None, None)?; + }, SteamApp::BAT1944.as_engine(), None, None)?; if let Some(rules) = &mut valve_response.rules { if let Some(bat_max_players) = rules.get("bat_max_players_i") { diff --git a/src/games/bb2.rs b/src/games/bb2.rs index 14dc9e1..add46de 100644 --- a/src/games/bb2.rs +++ b/src/games/bb2.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::BB2.as_app(), None, None)?; + }, SteamApp::BB2.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bm.rs b/src/games/bm.rs index 93daf35..4904fb0 100644 --- a/src/games/bm.rs +++ b/src/games/bm.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::BM.as_app(), None, None)?; + }, SteamApp::BM.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bo.rs b/src/games/bo.rs index dcd9a54..8a8441a 100644 --- a/src/games/bo.rs +++ b/src/games/bo.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27016, Some(port) => port - }, SteamID::BO.as_app(), None, None)?; + }, SteamApp::BO.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ccure.rs b/src/games/ccure.rs index 062b32f..ef1de45 100644 --- a/src/games/ccure.rs +++ b/src/games/ccure.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CCURE.as_app(), None, None)?; + }, SteamApp::CCURE.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cosu.rs b/src/games/cosu.rs index 0b883fd..bb033fe 100644 --- a/src/games/cosu.rs +++ b/src/games/cosu.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27004, Some(port) => port - }, SteamID::COSU.as_app(), None, None)?; + }, SteamApp::COSU.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cs.rs b/src/games/cs.rs index 3e662cb..d117d06 100644 --- a/src/games/cs.rs +++ b/src/games/cs.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CS.as_app(), None, None)?; + }, SteamApp::CS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cscz.rs b/src/games/cscz.rs index 56106dc..6cb4a3d 100644 --- a/src/games/cscz.rs +++ b/src/games/cscz.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CSCZ.as_app(), None, None)?; + }, SteamApp::CSCZ.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 3786606..4502694 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CSGO.as_app(), None, None)?; + }, SteamApp::CSGO.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/css.rs b/src/games/css.rs index 383a3f6..c054ceb 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::CSS.as_app(), None, None)?; + }, SteamApp::CSS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dod.rs b/src/games/dod.rs index 928f94f..99ad0b0 100644 --- a/src/games/dod.rs +++ b/src/games/dod.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::DOD.as_app(), None, None)?; + }, SteamApp::DOD.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dods.rs b/src/games/dods.rs index 0e3cdd1..7d05166 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::DODS.as_app(), None, None)?; + }, SteamApp::DODS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/doi.rs b/src/games/doi.rs index f5f8d69..53ccc0d 100644 --- a/src/games/doi.rs +++ b/src/games/doi.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::DOI.as_app(), None, None)?; + }, SteamApp::DOI.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dst.rs b/src/games/dst.rs index 012f141..be27ba4 100644 --- a/src/games/dst.rs +++ b/src/games/dst.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27016, Some(port) => port - }, SteamID::DST.as_app(), None, None)?; + }, SteamApp::DST.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index 7040c28..ca15b7b 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::GM.as_app(), None, None)?; + }, SteamApp::GM.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index c45741e..187eb21 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::HL2DM.as_app(), None, None)?; + }, SteamApp::HL2DM.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hldms.rs b/src/games/hldms.rs index 862c5e3..8049140 100644 --- a/src/games/hldms.rs +++ b/src/games/hldms.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::HLDMS.as_app(), None, None)?; + }, SteamApp::HLDMS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ins.rs b/src/games/ins.rs index 82341b3..819ee26 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::INS.as_app(), None, None)?; + }, SteamApp::INS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/insmic.rs b/src/games/insmic.rs index cf31b69..9678a06 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::INSMIC.as_app(), None, None)?; + }, SteamApp::INSMIC.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/inss.rs b/src/games/inss.rs index d57c036..2f009f4 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27131, Some(port) => port - }, SteamID::INSS.as_app(), None, None)?; + }, SteamApp::INSS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 58a861c..0bb98c6 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::L4D.as_app(), None, None)?; + }, SteamApp::L4D.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 23dded8..3fb161f 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::L4D2.as_app(), None, None)?; + }, SteamApp::L4D2.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ohd.rs b/src/games/ohd.rs index c52c40b..245a05f 100644 --- a/src/games/ohd.rs +++ b/src/games/ohd.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27005, Some(port) => port - }, SteamID::OHD.as_app(), None, None)?; + }, SteamApp::OHD.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/onset.rs b/src/games/onset.rs index 1f82d52..c5b40bd 100644 --- a/src/games/onset.rs +++ b/src/games/onset.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 7776, Some(port) => port - }, SteamID::ONSET.as_app(), None, None)?; + }, SteamApp::ONSET.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/pz.rs b/src/games/pz.rs index 2188145..2a02a0a 100644 --- a/src/games/pz.rs +++ b/src/games/pz.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 16261, Some(port) => port - }, SteamID::PZ.as_app(), None, None)?; + }, SteamApp::PZ.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ror2.rs b/src/games/ror2.rs index 9bb5e0d..625cf0a 100644 --- a/src/games/ror2.rs +++ b/src/games/ror2.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27016, Some(port) => port - }, SteamID::ROR2.as_app(), None, None)?; + }, SteamApp::ROR2.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/rust.rs b/src/games/rust.rs index 8f0e5a9..32845e6 100644 --- a/src/games/rust.rs +++ b/src/games/rust.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::RUST.as_app(), None, None)?; + }, SteamApp::RUST.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/sc.rs b/src/games/sc.rs index 7ab933d..27d1ae0 100644 --- a/src/games/sc.rs +++ b/src/games/sc.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::SC.as_app(), None, None)?; + }, SteamApp::SC.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/sdtd.rs b/src/games/sdtd.rs index 7ca1ec6..3d3db26 100644 --- a/src/games/sdtd.rs +++ b/src/games/sdtd.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 26900, Some(port) => port - }, SteamID::SDTD.as_app(), None, None)?; + }, SteamApp::SDTD.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf.rs b/src/games/tf.rs index 730573b..6e3fb5d 100644 --- a/src/games/tf.rs +++ b/src/games/tf.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27016, Some(port) => port - }, SteamID::TF.as_app(), None, None)?; + }, SteamApp::TF.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index ba00201..79d9379 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::TF2.as_app(), None, None)?; + }, SteamApp::TF2.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tfc.rs b/src/games/tfc.rs index aa8eef2..125f7e9 100644 --- a/src/games/tfc.rs +++ b/src/games/tfc.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::TFC.as_app(), None, None)?; + }, SteamApp::TFC.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ts.rs b/src/games/ts.rs index 4809619..78d0143 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{Server, ServerPlayer, get_optional_extracted_data, SteamID}; +use crate::protocols::valve::{Server, ServerPlayer, get_optional_extracted_data, SteamApp}; #[derive(Debug)] pub struct TheShipPlayer { @@ -85,7 +85,7 @@ pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::TS.as_app(), None, None)?; + }, SteamApp::TS.as_engine(), None, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/unturned.rs b/src/games/unturned.rs index 4d9c9b9..2871101 100644 --- a/src/games/unturned.rs +++ b/src/games/unturned.rs @@ -1,12 +1,12 @@ use crate::GDResult; use crate::protocols::valve; -use crate::protocols::valve::{game, SteamID}; +use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { let valve_response = valve::query(address, match port { None => 27015, Some(port) => port - }, SteamID::UNTURNED.as_app(), None, None)?; + }, SteamApp::UNTURNED.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index d2c8c5c..258be29 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -4,7 +4,7 @@ use crate::GDResult; use crate::bufferer::{Bufferer, Endianess}; use crate::GDError::{BadGame, Decompress, UnknownEnumCast}; use crate::protocols::types::TimeoutSettings; -use crate::protocols::valve::{App, ModData, SteamID}; +use crate::protocols::valve::{Engine, ModData, SteamApp}; use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, TheShip}; use crate::socket::{Socket, UdpSocket}; use crate::utils::u8_lower_upper; @@ -77,18 +77,18 @@ struct SplitPacket { } impl SplitPacket { - fn new(app: &App, protocol: u8, buffer: &mut Bufferer) -> GDResult { + fn new(engine: &Engine, protocol: u8, buffer: &mut Bufferer) -> GDResult { let header = buffer.get_u32()?; let id = buffer.get_u32()?; - let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match app { - App::GoldSrc(_) => { + let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match engine { + Engine::GoldSrc(_) => { let (lower, upper) = u8_lower_upper(buffer.get_u8()?); (lower, upper, 0, false, None, None) } - App::Source(_) => { + Engine::Source(_) => { let total = buffer.get_u8()?; let number = buffer.get_u8()?; - let size = match protocol == 7 && (*app == SteamID::CSS.as_app()) { //certain apps with protocol = 7 doesnt have this field + let size = match protocol == 7 && (*engine == SteamApp::CSS.as_engine()) { //certain apps with protocol = 7 dont have this field false => buffer.get_u16()?, true => 1248 }; @@ -155,20 +155,20 @@ impl ValveProtocol { }) } - fn receive(&mut self, app: &App, protocol: u8, buffer_size: usize) -> GDResult { + fn receive(&mut self, engine: &Engine, protocol: u8, buffer_size: usize) -> GDResult { let data = self.socket.receive(Some(buffer_size))?; let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); let header = buffer.get_u8()?; buffer.move_position_backward(1); if header == 0xFE { //the packet is split - let mut main_packet = SplitPacket::new(&app, protocol, &mut buffer)?; + let mut main_packet = SplitPacket::new(&engine, protocol, &mut buffer)?; let mut chunk_packets = Vec::with_capacity((main_packet.total - 1) as usize); for _ in 1..main_packet.total { let new_data = self.socket.receive(Some(buffer_size))?; buffer = Bufferer::new_with_data(Endianess::Little, &new_data); - let chunk_packet = SplitPacket::new(&app, protocol, &mut buffer)?; + let chunk_packet = SplitPacket::new(&engine, protocol, &mut buffer)?; chunk_packets.push(chunk_packet); } @@ -187,11 +187,11 @@ impl ValveProtocol { } /// Ask for a specific request only. - fn get_request_data(&mut self, app: &App, protocol: u8, kind: Request) -> GDResult { + fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult { let request_initial_packet = Packet::initial(kind).to_bytes(); self.socket.send(&request_initial_packet)?; - let packet = self.receive(app, protocol, PACKET_SIZE)?; + let packet = self.receive(engine, protocol, PACKET_SIZE)?; if packet.kind != 0x41 { //'A' let data = packet.payload.clone(); @@ -203,7 +203,7 @@ impl ValveProtocol { self.socket.send(&challenge_packet)?; - let data = self.receive(app, protocol, PACKET_SIZE)?.payload; + let data = self.receive(engine, protocol, PACKET_SIZE)?.payload; Ok(Bufferer::new_with_data(Endianess::Little, &data)) } @@ -267,10 +267,10 @@ impl ValveProtocol { } /// Get the server information's. - fn get_server_info(&mut self, app: &App) -> GDResult { - let mut buffer = self.get_request_data(&app, 0, Request::INFO)?; + fn get_server_info(&mut self, engine: &Engine) -> GDResult { + let mut buffer = self.get_request_data(&engine, 0, Request::INFO)?; - if let App::GoldSrc(force) = app { + if let Engine::GoldSrc(force) = engine { if *force { return ValveProtocol::get_goldsrc_server_info(&mut buffer); } @@ -299,7 +299,7 @@ impl ValveProtocol { }; let has_password = buffer.get_u8()? == 1; let vac_secured = buffer.get_u8()? == 1; - let the_ship = match *app == SteamID::TS.as_app() { + let the_ship = match *engine == SteamApp::TS.as_engine() { false => None, true => Some(TheShip { mode: buffer.get_u8()?, @@ -366,8 +366,8 @@ impl ValveProtocol { } /// Get the server player's. - fn get_server_players(&mut self, app: &App, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(&app, protocol, Request::PLAYERS)?; + fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult> { + let mut buffer = self.get_request_data(&engine, protocol, Request::PLAYERS)?; let count = buffer.get_u8()? as usize; let mut players: Vec = Vec::with_capacity(count); @@ -379,11 +379,11 @@ impl ValveProtocol { let score = buffer.get_u32()?; let duration = buffer.get_f32()?; - let deaths = match *app == SteamID::TS.as_app() { + let deaths = match *engine == SteamApp::TS.as_engine() { false => None, true => Some(buffer.get_u32()?) }; - let money = match *app == SteamID::TS.as_app() { + let money = match *engine == SteamApp::TS.as_engine() { false => None, true => Some(buffer.get_u32()?) }; @@ -403,8 +403,8 @@ impl ValveProtocol { } /// Get the server's rules. - fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(&app, protocol, Request::RULES)?; + fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult> { + let mut buffer = self.get_request_data(&engine, protocol, Request::RULES)?; let count = buffer.get_u16()? as usize; let mut rules: HashMap = HashMap::with_capacity(count); @@ -416,7 +416,7 @@ impl ValveProtocol { rules.insert(name, value); } - if *app == SteamID::ROR2.as_app() { + if *engine == SteamApp::ROR2.as_engine() { rules.remove("Test"); } @@ -426,20 +426,30 @@ impl ValveProtocol { /// Query a server by providing the address, the port, the app, gather and timeout settings. /// Providing None to the settings results in using the default values for them (GatherSettings::[default](GatheringSettings::default), TimeoutSettings::[default](TimeoutSettings::default)). -pub fn query(address: &str, port: u16, app: App, gather_settings: Option, timeout_settings: Option) -> GDResult { +pub fn query(address: &str, port: u16, engine: Engine, gather_settings: Option, timeout_settings: Option) -> GDResult { let response_gather_settings = gather_settings.unwrap_or(GatheringSettings::default()); - get_response(address, port, app, response_gather_settings, timeout_settings) + get_response(address, port, engine, response_gather_settings, timeout_settings) } -fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSettings, timeout_settings: Option) -> GDResult { +fn get_response(address: &str, port: u16, engine: Engine, gather_settings: GatheringSettings, timeout_settings: Option) -> GDResult { let mut client = ValveProtocol::new(address, port, timeout_settings)?; - let info = client.get_server_info(&app)?; + let info = client.get_server_info(&engine)?; let protocol = info.protocol; - if let App::Source(source_app) = &app { - if let Some(appid) = source_app { - if *appid != info.appid { + if let Engine::Source(source_app) = &engine { + if let Some(appids) = source_app { + let mut is_specified_id = false; + + if appids.0 == info.appid { + is_specified_id = true; + } else if let Some(dedicated_appid) = appids.1 { + if dedicated_appid == info.appid { + is_specified_id = true; + } + } + + if !is_specified_id { return Err(BadGame(format!("AppId: {}", info.appid))); } } @@ -449,11 +459,11 @@ fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSe info, players: match gather_settings.players { false => None, - true => Some(client.get_server_players(&app, protocol)?) + true => Some(client.get_server_players(&engine, protocol)?) }, rules: match gather_settings.rules { false => None, - true => Some(client.get_server_rules(&app, protocol)?) + true => Some(client.get_server_rules(&engine, protocol)?) } }) } diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 1074b45..8c24f9c 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -135,114 +135,161 @@ pub(crate) enum Request { RULES = 0x56 } -/// Supported steam apps id's -#[repr(u32)] +/// Supported steam apps #[derive(PartialEq, Clone)] -pub enum SteamID { +pub enum SteamApp { /// Counter-Strike - CS = 10, + CS, /// Team Fortress Classic - TFC = 20, + TFC, /// Day of Defeat - DOD = 30, + DOD, /// Counter-Strike: Condition Zero - CSCZ = 80, + CSCZ, /// Counter-Strike: Source - CSS = 240, + CSS, /// Day of Defeat: Source - DODS = 300, + DODS, /// Half-Life 2 Deathmatch - HL2DM = 320, + HL2DM, /// Half-Life Deathmatch: Source - HLDMS = 360, + HLDMS, /// Team Fortress 2 - TF2 = 440, + TF2, /// Left 4 Dead - L4D = 500, + L4D, /// Left 4 Dead - L4D2 = 550, + L4D2, /// Alien Swarm - ALIENS = 630, + ALIENS, /// Counter-Strike: Global Offensive - CSGO = 730, + CSGO, /// The Ship - TS = 2400, + TS, /// Garry's Mod - GM = 4000, + GM, /// Age of Chivalry - AOC = 17510, + AOC, /// Insurgency: Modern Infantry Combat - INSMIC = 17700, + INSMIC, /// ARMA 2: Operation Arrowhead - ARMA2OA = 33930, + ARMA2OA, /// Project Zomboid - PZ = 108600, + PZ, /// Insurgency - INS = 222880, + INS, /// Sven Co-op - SC = 225840, + SC, /// 7 Days To Die - SDTD = 251570, + SDTD, /// Rust - RUST = 252490, + RUST, /// Vallistic Overkill - BO = 296300, + BO, /// Don't Starve Together - DST = 322320, + DST, /// BrainBread 2 - BB2 = 346330, + BB2, /// Codename CURE - CCURE = 355180, + CCURE, /// Black Mesa - BM = 362890, + BM, /// Colony Survival - COSU = 366090, + COSU, /// Avorion - AVORION = 445220, + AVORION, /// Day of Infamy - DOI = 447820, - /// The Forrest - TF = 556450, //this is the id for the dedicated server, for the game its 242760 + DOI, + /// The Forest + TF, /// Unturned - UNTURNED = 304930, + UNTURNED, /// ARK: Survival Evolved - ASE = 346110, + ASE, /// Battalion 1944 - BAT1944 = 489940, + BAT1944, /// Insurgency: Sandstorm - INSS = 581320, + INSS, /// Alien Swarm: Reactive Drop - ASRD = 563560, + ASRD, /// Risk of Rain 2 - ROR2 = 632360, + ROR2, /// Operation: Harsh Doorstop - OHD = 950900, // this is the id for the dedicated server, for the game its 736590 + OHD, /// Onset - ONSET = 1105810, + ONSET, } -impl SteamID { - /// Get ID as App (the engine is specified). - pub fn as_app(&self) -> App { +impl SteamApp { + /// Get the specified app as engine. + pub fn as_engine(&self) -> Engine { match self { - SteamID::CS | SteamID::TFC | SteamID::DOD | SteamID::CSCZ | SteamID::SC => App::GoldSrc(false), - x => App::Source(Some(x.clone() as u32)) + SteamApp::CS => Engine::GoldSrc(false), //10 + SteamApp::TFC => Engine::GoldSrc(false), //20 + SteamApp::DOD => Engine::GoldSrc(false), //30 + SteamApp::CSCZ => Engine::GoldSrc(false), //80 + SteamApp::CSS => Engine::new_source(240), + SteamApp::DODS => Engine::new_source(300), + SteamApp::HL2DM => Engine::new_source(320), + SteamApp::HLDMS => Engine::new_source(360), + SteamApp::TF2 => Engine::new_source(440), + SteamApp::L4D => Engine::new_source(500), + SteamApp::L4D2 => Engine::new_source(550), + SteamApp::ALIENS => Engine::new_source(630), + SteamApp::CSGO => Engine::new_source(730), + SteamApp::TS => Engine::new_source(2400), + SteamApp::GM => Engine::new_source(4000), + SteamApp::AOC => Engine::new_source(17510), + SteamApp::INSMIC => Engine::new_source(17700), + SteamApp::ARMA2OA => Engine::new_source(33930), + SteamApp::PZ => Engine::new_source(108600), + SteamApp::INS => Engine::new_source(222880), + SteamApp::SC => Engine::GoldSrc(false), //225840 + SteamApp::SDTD => Engine::new_source(251570), + SteamApp::RUST => Engine::new_source(252490), + SteamApp::BO => Engine::new_source(296300), + SteamApp::DST => Engine::new_source(322320), + SteamApp::BB2 => Engine::new_source(346330), + SteamApp::CCURE => Engine::new_source(355180), + SteamApp::BM => Engine::new_source(362890), + SteamApp::COSU => Engine::new_source(366090), + SteamApp::AVORION => Engine::new_source(445220), + SteamApp::DOI => Engine::new_source(447820), + SteamApp::TF => Engine::new_source(556450), + SteamApp::UNTURNED => Engine::new_source(304930), + SteamApp::ASE => Engine::new_source(346110), + SteamApp::BAT1944 => Engine::new_source(489940), + SteamApp::INSS => Engine::new_source(581320), + SteamApp::ASRD => Engine::new_source(563560), + SteamApp::ROR2 => Engine::new_source(632360), + SteamApp::OHD => Engine::new_source_with_dedicated(736590, 950900), + SteamApp::ONSET => Engine::new_source(1105810) } } } -/// App type. +/// Engine type. #[derive(PartialEq, Clone)] -pub enum App { - /// A Source game, the argument represents the wanted response steam app id, if its **None**, - /// let the query find it, if its **Some**, the query fails if the response id is not the - /// specified one. - Source(Option), - /// A GoldSrc game, the argument indicates whether to enforce getting the obsolete A2S_INFO - /// goldsrc response or not. +pub enum Engine { + /// A Source game, the argument represents the possible steam app ids, if its **None**, let + /// the query find it, if its **Some**, the query fails if the response id is not the first + /// one, which is the game app id, or the other one, which is the dedicated server app id. + Source(Option<(u32, Option)>), + /// A GoldSrc game, the argument indicates whether to enforce + /// requesting the obsolete A2S_INFO response or not. GoldSrc(bool) } +impl Engine { + pub fn new_source(appid: u32) -> Self { + Engine::Source(Some((appid, None))) + } + + pub fn new_source_with_dedicated(appid: u32, dedicated_appid: u32) -> Self { + Engine::Source(Some((appid, Some(dedicated_appid)))) + } +} + /// What data to gather, purely used only with the query function. pub struct GatheringSettings { pub players: bool, @@ -298,6 +345,8 @@ pub mod game { pub map: String, /// The name of the game. pub game: String, + /// Server's app id. + pub appid: u32, /// Number of players on the server. pub players_online: u8, /// Details about the server's players (not all players necessarily). @@ -337,6 +386,7 @@ pub mod game { name: response.info.name, map: response.info.map, game: response.info.game, + appid: response.info.appid, players_online: response.info.players_online, players_details: response.players.unwrap_or(vec![]).iter().map(Player::from_valve_response).collect(), players_maximum: response.info.players_maximum, From ab43675ae59876decd5d65af4b3d9f3e7d9d031c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 18 Feb 2023 22:13:25 +0200 Subject: [PATCH 143/597] [Crate] Add feature 'no_games' --- CHANGELOG.md | 4 +++- Cargo.toml | 3 +++ src/lib.rs | 10 +++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6985f4..83965ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ Who knows what the future holds... # 0.X.Y - DD/MM/2023 ### Changes: -None. +Crate: +- Added feature `no_games` which disables the supported games (useful when you are only using +the protocols/services, also saves storage space). ### Breaking: None. diff --git a/Cargo.toml b/Cargo.toml index 584416a..4cf2e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,9 @@ readme = "README.md" keywords = ["server", "query", "game", "check", "status"] rust-version = "1.56.1" +[features] +no_games = [] + [dependencies] bzip2-rs = "0.1.2" # for compression crc32fast = "1.3.2" diff --git a/src/lib.rs b/src/lib.rs index 3b53070..493f4cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -//! Query many servers +//! Game Server Query Library. //! //! # Example //! @@ -14,13 +14,21 @@ //! } //! } //! ``` +//! +//! # Features: +//! Enabled by default: None +//! +//! `no_games` - disables the included games support. pub mod errors; pub mod protocols; +#[cfg(not(feature = "no_games"))] pub mod games; + mod utils; mod socket; mod bufferer; pub use errors::*; +#[cfg(not(feature = "no_games"))] pub use games::*; From 99c87557c28502ceaecf86726fbc99acbd84d431 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 19 Feb 2023 16:43:55 +0200 Subject: [PATCH 144/597] [Protocol] Valve: Reverse: No name players not being added to the list --- CHANGELOG.md | 4 ++++ PROTOCOLS.md | 8 ++++---- src/protocols/valve/protocol.rs | 35 ++++++++++++--------------------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83965ea..df88d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Crate: - Added feature `no_games` which disables the supported games (useful when you are only using the protocols/services, also saves storage space). +Protocols: +- Valve: +1. Reversed (from `0.1.0`) "Players with no name are no more added to the `players_details` field.", also added a note in the [protocols](PROTOCOLS.md) file regarding this. + ### Breaking: None. diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 0422ebd..35b8a0f 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,10 +1,10 @@ A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft). # Supported protocols: -| Name | For | Proprietary? | Documentation reference | Notes | -|----------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------| -| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | -| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | +| 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. | +| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | ## Planned to add support: _ diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 258be29..4a8caa7 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -375,28 +375,19 @@ impl ValveProtocol { for _ in 0..count { buffer.move_position_ahead(1); //skip the index byte - let name = buffer.get_string_utf8()?; - let score = buffer.get_u32()?; - let duration = buffer.get_f32()?; - - let deaths = match *engine == SteamApp::TS.as_engine() { - false => None, - true => Some(buffer.get_u32()?) - }; - let money = match *engine == SteamApp::TS.as_engine() { - false => None, - true => Some(buffer.get_u32()?) - }; - - if name.len() > 0 { - players.push(ServerPlayer { - name, - score, - duration, - deaths, - money - }); - } + players.push(ServerPlayer { + name: buffer.get_string_utf8()?, + score: buffer.get_u32()?, + duration: buffer.get_f32()?, + deaths: match *engine == SteamApp::TS.as_engine() { + false => None, + true => Some(buffer.get_u32()?) + }, + money: match *engine == SteamApp::TS.as_engine() { + false => None, + true => Some(buffer.get_u32()?) + }, + }); } Ok(players) From e26f0f871a9849d6f850f5aa3f57562d80306c6a Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 19 Feb 2023 17:56:39 +0200 Subject: [PATCH 145/597] [Protocol] Valve: Fix queries that require multiple challenge responses --- CHANGELOG.md | 1 + src/protocols/valve/protocol.rs | 20 +++++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df88d9f..7805877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ the protocols/services, also saves storage space). Protocols: - Valve: 1. Reversed (from `0.1.0`) "Players with no name are no more added to the `players_details` field.", also added a note in the [protocols](PROTOCOLS.md) file regarding this. +2. Fixed querying while multiple challenge responses might happen. ### Breaking: None. diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 4a8caa7..6b51780 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -189,21 +189,19 @@ impl ValveProtocol { /// Ask for a specific request only. fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult { let request_initial_packet = Packet::initial(kind).to_bytes(); - self.socket.send(&request_initial_packet)?; - let packet = self.receive(engine, protocol, PACKET_SIZE)?; - if packet.kind != 0x41 { //'A' - let data = packet.payload.clone(); - return Ok(Bufferer::new_with_data(Endianess::Little, &data)); + let mut packet = self.receive(engine, protocol, PACKET_SIZE)?; + while packet.kind == 0x41 {// 'A' + let challenge = packet.payload.clone(); + let challenge_packet = Packet::challenge(kind, challenge).to_bytes(); + + self.socket.send(&challenge_packet)?; + + packet = self.receive(engine, protocol, PACKET_SIZE)?; } - let challenge = packet.payload; - let challenge_packet = Packet::challenge(kind, challenge).to_bytes(); - - self.socket.send(&challenge_packet)?; - - let data = self.receive(engine, protocol, PACKET_SIZE)?.payload; + let data = packet.payload; Ok(Bufferer::new_with_data(Endianess::Little, &data)) } From cd4cbc09dbd3911362838f91320e4a378efc34d9 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 19 Feb 2023 18:37:10 +0200 Subject: [PATCH 146/597] [Games] V Rising support. --- CHANGELOG.md | 3 +++ GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/vr.rs | 12 ++++++++++++ src/protocols/valve/types.rs | 5 ++++- 6 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 src/games/vr.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7805877..f65d885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Crate: - Added feature `no_games` which disables the supported games (useful when you are only using the protocols/services, also saves storage space). +Games: +- [V Rising](https://store.steampowered.com/app/1604030/V_Rising/) support. + Protocols: - Valve: 1. Reversed (from `0.1.0`) "Players with no name are no more added to the `players_details` field.", also added a note in the [protocols](PROTOCOLS.md) file regarding this. diff --git a/GAMES.md b/GAMES.md index 2e80457..d6e4629 100644 --- a/GAMES.md +++ b/GAMES.md @@ -44,6 +44,7 @@ Beware of the `Notes` column, as it contains information about query port offset | BrainBread 2 | BB2 | Valve Protocol | | | Avorion | AVORION | Valve Protocol | Query port is 27020. | | Operation: Harsh Doorstop | OHD | Valve Protocol | Query port is 27005. | +| V Rising | VR | Valve Protocol | Query port is 27016. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index e89c643..2311564 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned, vr}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::Engine; @@ -82,6 +82,7 @@ fn main() -> GDResult<()> { "bb2" => println!("{:#?}", bb2::query(ip, port)?), "avorion" => println!("{:#?}", avorion::query(ip, port)?), "ohd" => println!("{:#?}", ohd::query(ip, port)?), + "vr" => println!("{:#?}", vr::query(ip, port)?), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/mod.rs b/src/games/mod.rs index 89cf589..893fbc7 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -83,3 +83,5 @@ pub mod bb2; pub mod avorion; /// Operation: Harsh Doorstop pub mod ohd; +/// V Rising +pub mod vr; diff --git a/src/games/vr.rs b/src/games/vr.rs new file mode 100644 index 0000000..acba3a5 --- /dev/null +++ b/src/games/vr.rs @@ -0,0 +1,12 @@ +use crate::GDResult; +use crate::protocols::valve; +use crate::protocols::valve::{game, SteamApp}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query(address, match port { + None => 27016, + Some(port) => port + }, SteamApp::VR.as_engine(), None, None)?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 8c24f9c..5ba6a67 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -218,6 +218,8 @@ pub enum SteamApp { OHD, /// Onset ONSET, + /// V Rising + VR, } impl SteamApp { @@ -263,7 +265,8 @@ impl SteamApp { SteamApp::ASRD => Engine::new_source(563560), SteamApp::ROR2 => Engine::new_source(632360), SteamApp::OHD => Engine::new_source_with_dedicated(736590, 950900), - SteamApp::ONSET => Engine::new_source(1105810) + SteamApp::ONSET => Engine::new_source(1105810), + SteamApp::VR => Engine::new_source(1604030), } } } From 56044365539c19d4f197a95882948a4be35e3994 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Wed, 22 Feb 2023 13:11:38 +0200 Subject: [PATCH 147/597] Update the README.md to capitalize project name --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b202ef..6f9525c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# rust-gamedig [![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) [![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) [![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) [![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) +# rust-GameDig [![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) [![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) [![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) [![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) **rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! -MSRV is `1.56.1` and the code is cross-platform. +Minimum Supported Rust Version is `1.56.1` and the code is cross-platform. ## Games/Services/Protocols List To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. From 950c08c18eba1f66562466809ecbd241a1235f94 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 3 Mar 2023 17:45:18 +0200 Subject: [PATCH 148/597] [Protocol] GameSpy 1 support with the games Unreal Tournament and Battlefield 1942. (#9) * Initial files + unreal tournament * Fix master_querant * Split by delimiter and collect into hashmap * Furter port to accept more packets * Improve getting the server's values * Some initial players parsing * Players parsing * Add error handling * Add some more fields * Add Battlefield 1942 support. * Add query_vars and some docs --- CHANGELOG.md | 4 + GAMES.md | 2 + PROTOCOLS.md | 9 +- examples/master_querant.rs | 7 +- src/games/bf1942.rs | 10 ++ src/games/mod.rs | 4 + src/games/ut.rs | 10 ++ src/protocols/gamespy/mod.rs | 8 ++ src/protocols/gamespy/protocol/mod.rs | 3 + src/protocols/gamespy/protocol/one.rs | 186 ++++++++++++++++++++++++++ src/protocols/gamespy/types.rs | 36 +++++ src/protocols/mod.rs | 2 + 12 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 src/games/bf1942.rs create mode 100644 src/games/ut.rs create mode 100644 src/protocols/gamespy/mod.rs create mode 100644 src/protocols/gamespy/protocol/mod.rs create mode 100644 src/protocols/gamespy/protocol/one.rs create mode 100644 src/protocols/gamespy/types.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f65d885..d0832d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,16 @@ the protocols/services, also saves storage space). Games: - [V Rising](https://store.steampowered.com/app/1604030/V_Rising/) support. +- [Unreal Tournament](https://en.wikipedia.org/wiki/Unreal_Tournament) support. +- [Battlefield 1942](https://www.ea.com/games/battlefield/battlefield-1942) support. Protocols: - Valve: 1. Reversed (from `0.1.0`) "Players with no name are no more added to the `players_details` field.", also added a note in the [protocols](PROTOCOLS.md) file regarding this. 2. Fixed querying while multiple challenge responses might happen. +- GameSpy 1 support. + ### Breaking: None. diff --git a/GAMES.md b/GAMES.md index d6e4629..f0e9723 100644 --- a/GAMES.md +++ b/GAMES.md @@ -45,6 +45,8 @@ Beware of the `Notes` column, as it contains information about query port offset | Avorion | AVORION | Valve Protocol | Query port is 27020. | | Operation: Harsh Doorstop | OHD | Valve Protocol | Query port is 27005. | | V Rising | VR | Valve Protocol | Query port is 27016. | +| Unreal Tournament | UT | GameSpy 1 | Query Port offset: 1. | +| Battlefield 1942 | BF1942 | GameSpy 1 | Query port is 23000. | ## Planned to add support: _ diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 35b8a0f..53f5482 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,10 +1,11 @@ A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft). # Supported protocols: -| 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. | -| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | +| 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. | +| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
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) | 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. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 2311564..9e85693 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,9 +1,10 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned, vr}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bf1942, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned, ut, vr}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::Engine; +use gamedig::protocols::gamespy; fn main() -> GDResult<()> { let args: Vec = env::args().collect(); @@ -83,6 +84,10 @@ fn main() -> GDResult<()> { "avorion" => println!("{:#?}", avorion::query(ip, port)?), "ohd" => println!("{:#?}", ohd::query(ip, port)?), "vr" => println!("{:#?}", vr::query(ip, port)?), + "_gamespy1" => println!("{:#?}", gamespy::one::query(ip, port.unwrap(), None)), + "_gamespy1_vars" => println!("{:#?}", gamespy::one::query_vars(ip, port.unwrap(), None)), + "ut" => println!("{:#?}", ut::query(ip, port)), + "bf1942" => println!("{:#?}", bf1942::query(ip, port)), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/bf1942.rs b/src/games/bf1942.rs new file mode 100644 index 0000000..f61a9d9 --- /dev/null +++ b/src/games/bf1942.rs @@ -0,0 +1,10 @@ +use crate::GDResult; +use crate::protocols::gamespy; +use crate::protocols::gamespy::Response; + +pub fn query(address: &str, port: Option) -> GDResult { + gamespy::one::query(address, match port { + None => 23000, + Some(port) => port + }, None) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 893fbc7..225cfab 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -85,3 +85,7 @@ pub mod avorion; pub mod ohd; /// V Rising pub mod vr; +/// Unreal Tournament +pub mod ut; +/// Battlefield 1942 +pub mod bf1942; diff --git a/src/games/ut.rs b/src/games/ut.rs new file mode 100644 index 0000000..486b590 --- /dev/null +++ b/src/games/ut.rs @@ -0,0 +1,10 @@ +use crate::GDResult; +use crate::protocols::gamespy; +use crate::protocols::gamespy::Response; + +pub fn query(address: &str, port: Option) -> GDResult { + gamespy::one::query(address, match port { + None => 7778, + Some(port) => port + }, None) +} diff --git a/src/protocols/gamespy/mod.rs b/src/protocols/gamespy/mod.rs new file mode 100644 index 0000000..8ed7a9b --- /dev/null +++ b/src/protocols/gamespy/mod.rs @@ -0,0 +1,8 @@ + +/// The implementation. +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use types::*; +pub use protocol::*; diff --git a/src/protocols/gamespy/protocol/mod.rs b/src/protocols/gamespy/protocol/mod.rs new file mode 100644 index 0000000..afcffc5 --- /dev/null +++ b/src/protocols/gamespy/protocol/mod.rs @@ -0,0 +1,3 @@ + +/// GameSpy 1 +pub mod one; diff --git a/src/protocols/gamespy/protocol/one.rs b/src/protocols/gamespy/protocol/one.rs new file mode 100644 index 0000000..97d42f0 --- /dev/null +++ b/src/protocols/gamespy/protocol/one.rs @@ -0,0 +1,186 @@ +use std::collections::HashMap; +use crate::bufferer::{Bufferer, Endianess}; +use crate::{GDError, GDResult}; +use crate::protocols::gamespy::{Player, Response}; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, UdpSocket}; + +fn get_server_values(address: &str, port: u16, timeout_settings: Option) -> GDResult> { + let mut socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + socket.send("\\status\\xserverquery".as_bytes())?; + + let mut received_query_id: Option = None; + let mut parts: Vec = Vec::new(); + let mut is_finished = false; + + let mut server_values = HashMap::new(); + + while !is_finished { + let data = socket.receive(None)?; + let mut bufferer = Bufferer::new_with_data(Endianess::Little, &data); + + let mut as_string = bufferer.get_string_utf8_unended()?; + as_string.remove(0); + + let splited: Vec = as_string.split('\\').map(str::to_string).collect(); + + for i in 0..splited.len() / 2 { + let position = i * 2; + let key = splited[position].clone(); + let value = match splited.get(position + 1) { + None => "".to_string(), + Some(v) => v.clone() + }; + + server_values.insert(key, value); + } + + is_finished = server_values.contains_key("final"); + server_values.remove("final"); + + let query_data = server_values.get("queryid"); + + let mut part = parts.len(); //if the part number isn't provided, it's value is the parts length + let mut query_id = None; + if let Some(qid) = query_data { + let split: Vec<&str> = qid.split('.').collect(); + + query_id = Some(split[0].parse().map_err(|_| GDError::TypeParse)?); + match split.len() { + 1 => (), + 2 => part = split[1].parse().map_err(|_| GDError::TypeParse)?, + _ => Err(GDError::PacketBad)? //the queryid can't be splitted in more than 2 elements + }; + } + + server_values.remove("queryid"); + + if received_query_id.is_some() && received_query_id != query_id { + return Err(GDError::PacketBad); //wrong query id! + } + else { + received_query_id = query_id; + } + + match parts.contains(&part) { + true => Err(GDError::PacketBad)?, + false => parts.push(part) + } + } + + Ok(server_values) +} + +fn extract_players(server_vars: &mut HashMap, players_maximum: usize) -> GDResult> { + let mut players_data: Vec> = Vec::with_capacity(players_maximum); + + server_vars.retain(|key, value| { + let split: Vec<&str> = key.split('_').collect(); + + if split.len() != 2 { + return true; + } + + let kind = split[0]; + let id: usize = match split[1].parse() { + Ok(v) => v, + Err(_) => return true + }; + + let early_return = match kind { + "team" | "player" | "ping" | "face" | "skin" | "mesh" | "frags" | "ngsecret" | "deaths" | "health" => false, + _x => { + //println!("UNKNOWN {id} {x} {value}"); + true + } + }; + + if early_return { + return true; + } + + if id >= players_data.len() { + let others = vec![HashMap::new(); id - players_data.len() + 1]; + players_data.extend_from_slice(&others); + } + players_data[id].insert(kind.to_string(), value.to_string()); + + false + }); + + let mut players: Vec = Vec::with_capacity(players_data.len()); + + for player_data in players_data { + let new_player = Player { + name: match player_data.get("player") { + Some(v) => v.clone(), + None => player_data.get("playername").ok_or(GDError::PacketBad)?.clone() + }, + team: player_data.get("team").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, + ping: player_data.get("ping").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, + face: player_data.get("face").ok_or(GDError::PacketBad)?.clone(), + skin: player_data.get("skin").ok_or(GDError::PacketBad)?.clone(), + mesh: player_data.get("mesh").ok_or(GDError::PacketBad)?.clone(), + frags: player_data.get("frags").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, + deaths: match player_data.get("deaths") { + Some(v) => Some(v.trim().parse().map_err(|_| GDError::TypeParse)?), + None => None + }, + health: match player_data.get("health") { + Some(v) => Some(v.trim().parse().map_err(|_| GDError::TypeParse)?), + None => None + }, + secret: player_data.get("ngsecret").ok_or(GDError::PacketBad)?.to_lowercase().parse().map_err(|_| GDError::TypeParse)?, + }; + + players.push(new_player); + } + + Ok(players) +} + +fn has_password(server_vars: &mut HashMap) -> GDResult { + let password_value = server_vars.remove("password").ok_or(GDError::PacketBad)?.to_lowercase(); + + if let Ok(has) = password_value.parse::() { + return Ok(has); + } + + let as_numeral: u8 = password_value.parse().map_err(|_| GDError::TypeParse)?; + + Ok(as_numeral != 0) +} + +/// If there are parsing problems using the `query` function, you can directly get the server's values using this function. +pub fn query_vars(address: &str, port: u16, timeout_settings: Option) -> GDResult> { + get_server_values(address, port, timeout_settings) +} + +/// Query a server by providing the address, the port and timeout settings. +/// Providing None to the timeout settings results in using the default values. (TimeoutSettings::[default](TimeoutSettings::default)). +pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let mut server_vars = query_vars(address, port, timeout_settings)?; + + let players_maximum = server_vars.remove("maxplayers").ok_or(GDError::PacketBad)?.parse().map_err(|_| GDError::TypeParse)?; + + let players = extract_players(&mut server_vars, players_maximum)?; + + Ok(Response { + name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, + map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, + map_title: server_vars.remove("maptitle"), + admin_contact: server_vars.remove("AdminEMail"), + admin_name: server_vars.remove("AdminName"), + has_password: has_password(&mut server_vars)?, + game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, + game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, + players_maximum, + players_online: players.len(), + players_minimum: server_vars.remove("minplayers").unwrap_or("0".to_string()).parse().map_err(|_| GDError::TypeParse)?, + players, + tournament: server_vars.remove("tournament").unwrap_or("true".to_string()).to_lowercase().parse().map_err(|_| GDError::TypeParse)?, + unused_entries: server_vars + }) +} diff --git a/src/protocols/gamespy/types.rs b/src/protocols/gamespy/types.rs new file mode 100644 index 0000000..db4ed3e --- /dev/null +++ b/src/protocols/gamespy/types.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; + +/// A player’s details. +#[derive(Debug)] +pub struct Player { + pub name: String, + pub team: u8, + /// The ping from the server's perspective. + pub ping: u16, + pub face: String, + pub skin: String, + pub mesh: String, + pub frags: u32, + pub deaths: Option, + pub health: Option, + pub secret: bool +} + +/// A query response. +#[derive(Debug)] +pub struct Response { + pub name: String, + pub map: String, + pub map_title: Option, + pub admin_contact: Option, + pub admin_name: Option, + pub has_password: bool, + pub game_type: String, + pub game_version: String, + pub players_maximum: usize, + pub players_online: usize, + pub players_minimum: u8, + pub players: Vec, + pub tournament: bool, + pub unused_entries: HashMap +} diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 08f3ec2..e6e4fac 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -10,3 +10,5 @@ pub mod types; pub mod valve; /// Reference: [Server List Ping](https://wiki.vg/Server_List_Ping) pub mod minecraft; +/// Reference: [node-GameDig](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) +pub mod gamespy; From f97de3bb634da4222c351fae968ebcb611f4b259 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 3 Mar 2023 17:54:13 +0200 Subject: [PATCH 149/597] [Crate] Add warning about frequent API breaking changes in the Readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6f9525c..d4dfb3e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # rust-GameDig [![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) [![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) [![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) [![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) +**Warning**: This project goes through frequent API breaking changes and hasn't been thoroughly tested. + **rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! Minimum Supported Rust Version is `1.56.1` and the code is cross-platform. From 5f06f58df8fb92ebc42951536aa3145e5b21644a Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 3 Mar 2023 17:55:23 +0200 Subject: [PATCH 150/597] [Crate] Hyperlink tf2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4dfb3e..8a000e2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ To see the supported (or the planned to support) games/services/protocols, see [ ## Usage Just pick a game/service/protocol, provide the ip and the port (be aware that some game servers use a separate port for the info queries, the port can also be optional if the server is running the default ports) then query on it. -Team Fortress 2 query example: +[Team Fortress 2](https://store.steampowered.com/app/440/Team_Fortress_2/) query example: ```rust use gamedig::games::tf2; From 59994bc0865a1e1c92da2677d792c6cb84e9db33 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 3 Mar 2023 18:02:54 +0200 Subject: [PATCH 151/597] [Games] Use port.unwrap_or instead of matching it --- src/games/aliens.rs | 5 +---- src/games/aoc.rs | 5 +---- src/games/arma2oa.rs | 5 +---- src/games/ase.rs | 5 +---- src/games/asrd.rs | 5 +---- src/games/avorion.rs | 5 +---- src/games/bat1944.rs | 5 +---- src/games/bb2.rs | 5 +---- src/games/bf1942.rs | 5 +---- src/games/bm.rs | 5 +---- src/games/bo.rs | 5 +---- src/games/ccure.rs | 5 +---- src/games/cosu.rs | 5 +---- src/games/cs.rs | 5 +---- src/games/cscz.rs | 5 +---- src/games/csgo.rs | 5 +---- src/games/css.rs | 5 +---- src/games/dod.rs | 5 +---- src/games/dods.rs | 5 +---- src/games/doi.rs | 5 +---- src/games/dst.rs | 5 +---- src/games/gm.rs | 5 +---- src/games/hl2dm.rs | 5 +---- src/games/hldms.rs | 5 +---- src/games/ins.rs | 5 +---- src/games/insmic.rs | 5 +---- src/games/inss.rs | 5 +---- src/games/l4d.rs | 5 +---- src/games/l4d2.rs | 5 +---- src/games/mc.rs | 10 ++-------- src/games/ohd.rs | 5 +---- src/games/onset.rs | 5 +---- src/games/pz.rs | 5 +---- src/games/ror2.rs | 5 +---- src/games/rust.rs | 5 +---- src/games/sc.rs | 5 +---- src/games/sdtd.rs | 5 +---- src/games/tf.rs | 5 +---- src/games/tf2.rs | 5 +---- src/games/tfc.rs | 5 +---- src/games/ts.rs | 5 +---- src/games/unturned.rs | 5 +---- src/games/ut.rs | 5 +---- src/games/vr.rs | 5 +---- 44 files changed, 45 insertions(+), 180 deletions(-) diff --git a/src/games/aliens.rs b/src/games/aliens.rs index 07f55f2..dafc7b9 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::ALIENS.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ALIENS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/aoc.rs b/src/games/aoc.rs index ff7e421..29d913b 100644 --- a/src/games/aoc.rs +++ b/src/games/aoc.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::AOC.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::AOC.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/arma2oa.rs b/src/games/arma2oa.rs index c5b88ff..f54123c 100644 --- a/src/games/arma2oa.rs +++ b/src/games/arma2oa.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 2304, - Some(port) => port - }, SteamApp::ARMA2OA.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(2304), SteamApp::ARMA2OA.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ase.rs b/src/games/ase.rs index e863186..09ff37f 100644 --- a/src/games/ase.rs +++ b/src/games/ase.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::ASE.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ASE.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 5caf1f4..8f2808e 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::ASRD.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ASRD.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/avorion.rs b/src/games/avorion.rs index 51f9fa1..804389a 100644 --- a/src/games/avorion.rs +++ b/src/games/avorion.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27020, - Some(port) => port - }, SteamApp::AVORION.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27020), SteamApp::AVORION.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs index c564b8d..a401819 100644 --- a/src/games/bat1944.rs +++ b/src/games/bat1944.rs @@ -4,10 +4,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let mut valve_response = valve::query(address, match port { - None => 7780, - Some(port) => port - }, SteamApp::BAT1944.as_engine(), None, None)?; + let mut valve_response = valve::query(address, port.unwrap_or(7780), SteamApp::BAT1944.as_engine(), None, None)?; if let Some(rules) = &mut valve_response.rules { if let Some(bat_max_players) = rules.get("bat_max_players_i") { diff --git a/src/games/bb2.rs b/src/games/bb2.rs index add46de..97bec4b 100644 --- a/src/games/bb2.rs +++ b/src/games/bb2.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::BB2.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::BB2.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bf1942.rs b/src/games/bf1942.rs index f61a9d9..12d530b 100644 --- a/src/games/bf1942.rs +++ b/src/games/bf1942.rs @@ -3,8 +3,5 @@ use crate::protocols::gamespy; use crate::protocols::gamespy::Response; pub fn query(address: &str, port: Option) -> GDResult { - gamespy::one::query(address, match port { - None => 23000, - Some(port) => port - }, None) + gamespy::one::query(address, port.unwrap_or(23000), None) } diff --git a/src/games/bm.rs b/src/games/bm.rs index 4904fb0..3423c40 100644 --- a/src/games/bm.rs +++ b/src/games/bm.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::BM.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::BM.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bo.rs b/src/games/bo.rs index 8a8441a..88dcfc4 100644 --- a/src/games/bo.rs +++ b/src/games/bo.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27016, - Some(port) => port - }, SteamApp::BO.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::BO.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ccure.rs b/src/games/ccure.rs index ef1de45..357f5e3 100644 --- a/src/games/ccure.rs +++ b/src/games/ccure.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::CCURE.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CCURE.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cosu.rs b/src/games/cosu.rs index bb033fe..48e1595 100644 --- a/src/games/cosu.rs +++ b/src/games/cosu.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27004, - Some(port) => port - }, SteamApp::COSU.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27004), SteamApp::COSU.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cs.rs b/src/games/cs.rs index d117d06..81a18a0 100644 --- a/src/games/cs.rs +++ b/src/games/cs.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::CS.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cscz.rs b/src/games/cscz.rs index 6cb4a3d..ac8b199 100644 --- a/src/games/cscz.rs +++ b/src/games/cscz.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::CSCZ.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSCZ.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 4502694..f65944a 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::CSGO.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSGO.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/css.rs b/src/games/css.rs index c054ceb..01a0ae3 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::CSS.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dod.rs b/src/games/dod.rs index 99ad0b0..56687bf 100644 --- a/src/games/dod.rs +++ b/src/games/dod.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::DOD.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DOD.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dods.rs b/src/games/dods.rs index 7d05166..96be872 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::DODS.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DODS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/doi.rs b/src/games/doi.rs index 53ccc0d..bfee0f3 100644 --- a/src/games/doi.rs +++ b/src/games/doi.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::DOI.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DOI.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dst.rs b/src/games/dst.rs index be27ba4..256a1b7 100644 --- a/src/games/dst.rs +++ b/src/games/dst.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27016, - Some(port) => port - }, SteamApp::DST.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::DST.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index ca15b7b..6fec055 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::GM.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::GM.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index 187eb21..c885128 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::HL2DM.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::HL2DM.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hldms.rs b/src/games/hldms.rs index 8049140..325fd95 100644 --- a/src/games/hldms.rs +++ b/src/games/hldms.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::HLDMS.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::HLDMS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ins.rs b/src/games/ins.rs index 819ee26..61f7759 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::INS.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::INS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 9678a06..82ac53a 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::INSMIC.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::INSMIC.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/inss.rs b/src/games/inss.rs index 2f009f4..ee53a45 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27131, - Some(port) => port - }, SteamApp::INSS.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27131), SteamApp::INSS.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 0bb98c6..168f678 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::L4D.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::L4D.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 3fb161f..09120cf 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::L4D2.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::L4D2.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/mc.rs b/src/games/mc.rs index 6b03328..6172220 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -40,15 +40,9 @@ pub fn query_bedrock(address: &str, port: Option) -> GDResult) -> u16 { - match port { - None => 25565, - Some(port) => port - } + port.unwrap_or(25565) } fn port_or_bedrock_default(port: Option) -> u16 { - match port { - None => 19132, - Some(port) => port - } + port.unwrap_or(19132) } diff --git a/src/games/ohd.rs b/src/games/ohd.rs index 245a05f..b024917 100644 --- a/src/games/ohd.rs +++ b/src/games/ohd.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27005, - Some(port) => port - }, SteamApp::OHD.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27005), SteamApp::OHD.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/onset.rs b/src/games/onset.rs index c5b40bd..ce58ba9 100644 --- a/src/games/onset.rs +++ b/src/games/onset.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 7776, - Some(port) => port - }, SteamApp::ONSET.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(7776), SteamApp::ONSET.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/pz.rs b/src/games/pz.rs index 2a02a0a..8fda807 100644 --- a/src/games/pz.rs +++ b/src/games/pz.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 16261, - Some(port) => port - }, SteamApp::PZ.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(16261), SteamApp::PZ.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ror2.rs b/src/games/ror2.rs index 625cf0a..019010a 100644 --- a/src/games/ror2.rs +++ b/src/games/ror2.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27016, - Some(port) => port - }, SteamApp::ROR2.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::ROR2.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/rust.rs b/src/games/rust.rs index 32845e6..cdd1c02 100644 --- a/src/games/rust.rs +++ b/src/games/rust.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::RUST.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::RUST.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/sc.rs b/src/games/sc.rs index 27d1ae0..ea8b029 100644 --- a/src/games/sc.rs +++ b/src/games/sc.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::SC.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::SC.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/sdtd.rs b/src/games/sdtd.rs index 3d3db26..c618034 100644 --- a/src/games/sdtd.rs +++ b/src/games/sdtd.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 26900, - Some(port) => port - }, SteamApp::SDTD.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(26900), SteamApp::SDTD.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf.rs b/src/games/tf.rs index 6e3fb5d..5605c5f 100644 --- a/src/games/tf.rs +++ b/src/games/tf.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27016, - Some(port) => port - }, SteamApp::TF.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::TF.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 79d9379..ca83dc4 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::TF2.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TF2.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/tfc.rs b/src/games/tfc.rs index 125f7e9..69229cd 100644 --- a/src/games/tfc.rs +++ b/src/games/tfc.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::TFC.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TFC.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ts.rs b/src/games/ts.rs index 78d0143..3d72811 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -82,10 +82,7 @@ impl Response { } pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::TS.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TS.as_engine(), None, None)?; Ok(Response::new_from_valve_response(valve_response)) } diff --git a/src/games/unturned.rs b/src/games/unturned.rs index 2871101..b971dbf 100644 --- a/src/games/unturned.rs +++ b/src/games/unturned.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27015, - Some(port) => port - }, SteamApp::UNTURNED.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::UNTURNED.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ut.rs b/src/games/ut.rs index 486b590..a22fd79 100644 --- a/src/games/ut.rs +++ b/src/games/ut.rs @@ -3,8 +3,5 @@ use crate::protocols::gamespy; use crate::protocols::gamespy::Response; pub fn query(address: &str, port: Option) -> GDResult { - gamespy::one::query(address, match port { - None => 7778, - Some(port) => port - }, None) + gamespy::one::query(address, port.unwrap_or(7778), None) } diff --git a/src/games/vr.rs b/src/games/vr.rs index acba3a5..91d2105 100644 --- a/src/games/vr.rs +++ b/src/games/vr.rs @@ -3,10 +3,7 @@ use crate::protocols::valve; use crate::protocols::valve::{game, SteamApp}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, match port { - None => 27016, - Some(port) => port - }, SteamApp::VR.as_engine(), None, None)?; + let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::VR.as_engine(), None, None)?; Ok(game::Response::new_from_valve_response(valve_response)) } From 04299c1a2c5b66bdfdb86b6e5479776099a7b041 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 3 Mar 2023 18:09:17 +0200 Subject: [PATCH 152/597] [Crate] Bump version to 0.2.1. --- CHANGELOG.md | 6 +++--- Cargo.toml | 2 +- LICENSE.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0832d6..5a1df11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ Who knows what the future holds... -# 0.X.Y - DD/MM/2023 +# 0.2.1 - 03/03/2023 ### Changes: Crate: -- Added feature `no_games` which disables the supported games (useful when you are only using -the protocols/services, also saves storage space). +- Added feature `no_games` which disables the supported games (useful when only the +protocols/services are needed, also saves storage space). Games: - [V Rising](https://store.steampowered.com/app/1604030/V_Rising/) support. diff --git a/Cargo.toml b/Cargo.toml index 4cf2e6d..011d562 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.2.0" +version = "0.2.1" edition = "2021" authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] license = "MIT" diff --git a/LICENSE.md b/LICENSE.md index 9362443..118a3aa 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 CosminPerRam +Copyright (c) 2022 - 2023 CosminPerRam Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c7f706bf35e67315c7a987f6be05577d1f090b2d Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 3 Mar 2023 18:11:40 +0200 Subject: [PATCH 153/597] [Crate] Add preliminary changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1df11..6991e02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Who knows what the future holds... +# 0.X.Y - DD/MM/2023 +### Changes: +Nothing... yet. + +### Breaking: +None. + # 0.2.1 - 03/03/2023 ### Changes: Crate: From 9d0cc15f4c29795c27f08bfe41b0ae02770e3ee9 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 4 Mar 2023 13:47:20 +0200 Subject: [PATCH 154/597] [Crate] Update lib docs spelling --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 493f4cb..5c43f7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Game Server Query Library. //! -//! # Example +//! # An usage example: //! //! ```no_run //! use gamedig::games::tf2; @@ -15,7 +15,7 @@ //! } //! ``` //! -//! # Features: +//! # Crate features: //! Enabled by default: None //! //! `no_games` - disables the included games support. From 8992ffe4dfbf9be52e23bad98b78815099fff320 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 4 Mar 2023 14:04:38 +0200 Subject: [PATCH 155/597] [Docs] Match the docs example to the one from the readme --- src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5c43f7a..d1ab7b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,15 +4,15 @@ //! # An usage example: //! //! ```no_run -//! use gamedig::games::tf2; +//!use gamedig::games::tf2; //! -//! fn main() { -//! let response = tf2::query("91.216.250.10", None); //or Some(27015), None is the default protocol port -//! match response { -//! Err(error) => println!("Couldn't query, error: {}", error), -//! Ok(r) => println!("{:?}", r) -//! } -//! } +//!fn main() { +//! let response = tf2::query("127.0.0.1", None); // None is the default port (which is 27015), could also be Some(27015) +//! match response { // Result type, must check what it is... +//! Err(error) => println!("Couldn't query, error: {}", error), +//! Ok(r) => println!("{:#?}", r) +//! } +//!} //! ``` //! //! # Crate features: From 14c5edc1bee033e3af57f06ff3215a35b086216b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 4 Mar 2023 21:36:01 +0200 Subject: [PATCH 156/597] [Games] Serious Sam support. --- CHANGELOG.md | 7 +++++-- GAMES.md | 1 + examples/master_querant.rs | 3 ++- src/games/mod.rs | 2 ++ src/games/ss.rs | 7 +++++++ src/protocols/gamespy/protocol/one.rs | 2 +- 6 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 src/games/ss.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6991e02..2a36444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,11 @@ Who knows what the future holds... # 0.X.Y - DD/MM/2023 ### Changes: -Nothing... yet. +Protocols: +- GameSpy 1: Add key `admin` as a possible variable for `admin_name`. + +Games: +- [Serious Sam](https://www.gog.com/game/serious_sam_the_first_encounter) support. ### Breaking: None. @@ -23,7 +27,6 @@ Protocols: - Valve: 1. Reversed (from `0.1.0`) "Players with no name are no more added to the `players_details` field.", also added a note in the [protocols](PROTOCOLS.md) file regarding this. 2. Fixed querying while multiple challenge responses might happen. - - GameSpy 1 support. ### Breaking: diff --git a/GAMES.md b/GAMES.md index f0e9723..5b0105c 100644 --- a/GAMES.md +++ b/GAMES.md @@ -47,6 +47,7 @@ Beware of the `Notes` column, as it contains information about query port offset | V Rising | VR | Valve Protocol | Query port is 27016. | | Unreal Tournament | UT | GameSpy 1 | Query Port offset: 1. | | Battlefield 1942 | BF1942 | GameSpy 1 | Query port is 23000. | +| Serious Sam | SS | GameSpy 1 | Query Port offset: 1 | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 9e85693..501c584 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,6 +1,6 @@ use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bf1942, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, tf, tf2, tfc, ts, unturned, ut, vr}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bf1942, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, ss, tf, tf2, tfc, ts, unturned, ut, vr}; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::Engine; @@ -88,6 +88,7 @@ fn main() -> GDResult<()> { "_gamespy1_vars" => println!("{:#?}", gamespy::one::query_vars(ip, port.unwrap(), None)), "ut" => println!("{:#?}", ut::query(ip, port)), "bf1942" => println!("{:#?}", bf1942::query(ip, port)), + "ss" => println!("{:#?}", ss::query(ip, port)), _ => panic!("Undefined game: {}", args[1]) }; diff --git a/src/games/mod.rs b/src/games/mod.rs index 225cfab..6530c83 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -89,3 +89,5 @@ pub mod vr; pub mod ut; /// Battlefield 1942 pub mod bf1942; +/// Serious Sam +pub mod ss; diff --git a/src/games/ss.rs b/src/games/ss.rs new file mode 100644 index 0000000..40e132f --- /dev/null +++ b/src/games/ss.rs @@ -0,0 +1,7 @@ +use crate::GDResult; +use crate::protocols::gamespy; +use crate::protocols::gamespy::Response; + +pub fn query(address: &str, port: Option) -> GDResult { + gamespy::one::query(address, port.unwrap_or(25601), None) +} diff --git a/src/protocols/gamespy/protocol/one.rs b/src/protocols/gamespy/protocol/one.rs index 97d42f0..6acf458 100644 --- a/src/protocols/gamespy/protocol/one.rs +++ b/src/protocols/gamespy/protocol/one.rs @@ -172,7 +172,7 @@ pub fn query(address: &str, port: u16, timeout_settings: Option map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, map_title: server_vars.remove("maptitle"), admin_contact: server_vars.remove("AdminEMail"), - admin_name: server_vars.remove("AdminName"), + admin_name: server_vars.remove("AdminName").or(server_vars.remove("admin")), has_password: has_password(&mut server_vars)?, game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, From e6562d30cbd2ccee54ed70eae0ed621f3a0d970f Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Wed, 8 Mar 2023 21:50:34 +0200 Subject: [PATCH 157/597] [Crate] Update links to point to gamedig organization rather than cosminperram --- Cargo.toml | 6 +++--- README.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 011d562..c88318c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,12 @@ name = "gamedig" version = "0.2.1" edition = "2021" -authors = ["CosminPerRam [cosmin.p@live.com]", "node-GameDig [https://github.com/gamedig/node-gamedig]"] +authors = ["CosminPerRam [https://github.com/CosminPerRam]", "node-GameDig contributors [https://github.com/gamedig/node-gamedig/contributors]"] license = "MIT" description = "Check out servers with this." -homepage = "https://github.com/CosminPerRam/rust-gamedig" +homepage = "https://github.com/gamedig/rust-gamedig" documentation = "https://docs.rs/gamedig/latest/gamedig/" -repository = "https://github.com/CosminPerRam/rust-gamedig" +repository = "https://github.com/gamedig/rust-gamedig" readme = "README.md" keywords = ["server", "query", "game", "check", "status"] rust-version = "1.56.1" diff --git a/README.md b/README.md index 8a000e2..311ddbe 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# rust-GameDig [![CI](https://github.com/cosminperram/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/CosminPerRam/rust-gamedig/actions) [![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) [![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) [![License:MIT](https://img.shields.io/github/license/cosminperram/rust-gamedig?color=blue)](LICENSE.md) +# rust-GameDig [![CI](https://github.com/gamedig/rust-gamedig/actions/workflows/ci.yml/badge.svg)](https://github.com/gamedig/rust-gamedig/actions) [![Latest Version](https://img.shields.io/crates/v/gamedig.svg?color=yellow)](https://crates.io/crates/gamedig) [![Crates.io](https://img.shields.io/crates/d/gamedig?color=purple)](https://crates.io/crates/gamedig) [![License:MIT](https://img.shields.io/github/license/gamedig/rust-gamedig?color=blue)](LICENSE.md) **Warning**: This project goes through frequent API breaking changes and hasn't been thoroughly tested. -**rust-GameDig** is a games/services server query library that can fetch the availability and/or details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! +**rust-GameDig** is a game servers/services query library that fetches the availability and details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! Minimum Supported Rust Version is `1.56.1` and the code is cross-platform. From e1637746852a2a3ad9b808bce661d7ac974c348e Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 9 Mar 2023 01:30:28 +0200 Subject: [PATCH 158/597] [Protocols] Cargo clippy optimizations --- CHANGELOG.md | 3 +- src/bufferer.rs | 11 +++-- src/lib.rs | 2 +- src/protocols/gamespy/protocol/one.rs | 6 +-- src/protocols/minecraft/protocol/bedrock.rs | 6 +-- .../minecraft/protocol/legacy_bv1_8.rs | 2 +- .../minecraft/protocol/legacy_v1_4.rs | 2 +- .../minecraft/protocol/legacy_v1_6.rs | 2 +- src/protocols/minecraft/types.rs | 9 ++-- src/protocols/valve/protocol.rs | 42 +++++++++---------- src/protocols/valve/types.rs | 16 +++---- src/socket.rs | 8 ++-- src/utils.rs | 13 +++--- 13 files changed, 57 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a36444..8f74845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ Games: - [Serious Sam](https://www.gog.com/game/serious_sam_the_first_encounter) support. ### Breaking: -None. +Protocols: +- Valve: Request type enums have been renamed from all caps to starting-only uppercase, ex: `INFO` to `Info` # 0.2.1 - 03/03/2023 ### Changes: diff --git a/src/bufferer.rs b/src/bufferer.rs index b3be8c8..22453ce 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -100,7 +100,7 @@ impl Bufferer { pub fn get_string_utf8(&mut self) -> GDResult { let sub_buf = &self.data[self.position..]; - if sub_buf.len() == 0 { + if sub_buf.is_empty() { return Err(PacketUnderflow); } @@ -115,11 +115,11 @@ impl Bufferer { pub fn get_string_utf8_unended(&mut self) -> GDResult { let sub_buf = &self.data[self.position..]; - if sub_buf.len() == 0 { + if sub_buf.is_empty() { return Err(PacketUnderflow); } - let value = std::str::from_utf8(&sub_buf) + let value = std::str::from_utf8(sub_buf) .map_err(|_| PacketBad)?.to_string(); self.position += value.len(); @@ -128,7 +128,7 @@ impl Bufferer { pub fn get_string_utf16(&mut self) -> GDResult { let sub_buf = &self.data[self.position..]; - if sub_buf.len() == 0 { + if sub_buf.is_empty() { return Err(PacketUnderflow); } @@ -138,8 +138,7 @@ impl Bufferer { Endianess::Big => u16::from_be_bytes([a[0], a[1]]) }).collect(); - let value = String::from_utf16(&paired_buf) - .map_err(|_| PacketBad)?.to_string(); + let value = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?; self.position += value.len() * 2; Ok(value) diff --git a/src/lib.rs b/src/lib.rs index d1ab7b6..de0ae9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Game Server Query Library. //! -//! # An usage example: +//! # Usage example: //! //! ```no_run //!use gamedig::games::tf2; diff --git a/src/protocols/gamespy/protocol/one.rs b/src/protocols/gamespy/protocol/one.rs index 6acf458..c553804 100644 --- a/src/protocols/gamespy/protocol/one.rs +++ b/src/protocols/gamespy/protocol/one.rs @@ -172,15 +172,15 @@ pub fn query(address: &str, port: u16, timeout_settings: Option map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, map_title: server_vars.remove("maptitle"), admin_contact: server_vars.remove("AdminEMail"), - admin_name: server_vars.remove("AdminName").or(server_vars.remove("admin")), + admin_name: server_vars.remove("AdminName").or_else(|| server_vars.remove("admin")), has_password: has_password(&mut server_vars)?, game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, players_maximum, players_online: players.len(), - players_minimum: server_vars.remove("minplayers").unwrap_or("0".to_string()).parse().map_err(|_| GDError::TypeParse)?, + players_minimum: server_vars.remove("minplayers").unwrap_or_else(|| "0".to_string()).parse().map_err(|_| GDError::TypeParse)?, players, - tournament: server_vars.remove("tournament").unwrap_or("true".to_string()).to_lowercase().parse().map_err(|_| GDError::TypeParse)?, + tournament: server_vars.remove("tournament").unwrap_or_else(|| "true".to_string()).to_lowercase().parse().map_err(|_| GDError::TypeParse)?, unused_entries: server_vars }) } diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index 33abbef..c37c29e 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -71,7 +71,7 @@ impl Bedrock { error_by_expected_size(remaining_length, buffer.remaining_length())?; let binding = buffer.get_string_utf8_unended()?; - let status: Vec<&str> = binding.split(";").collect(); + let status: Vec<&str> = binding.split(';').collect(); // We must have at least 6 values if status.len() < 6 { @@ -85,8 +85,8 @@ impl Bedrock { version_protocol: status[2].to_string(), players_maximum: status[5].parse().map_err(|_| TypeParse)?, players_online: status[4].parse().map_err(|_| TypeParse)?, - id: status.get(6).and_then(|v| Some(v.to_string())), - map: status.get(7).and_then(|v| Some(v.to_string())), + id: status.get(6).map(|v| v.to_string()), + map: status.get(7).map(|v| v.to_string()), game_mode: match status.get(8) { None => None, Some(v) => Some(GameMode::from_bedrock(v)?) diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index e4cdf29..2423996 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -39,7 +39,7 @@ impl LegacyBV1_8 { let packet_string = buffer.get_string_utf16()?; - let split: Vec<&str> = packet_string.split("§").collect(); + let split: Vec<&str> = packet_string.split('§').collect(); error_by_expected_size(3, split.len())?; let description = split[0].to_string(); diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index 69195eb..5c5451c 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -44,7 +44,7 @@ impl LegacyV1_4 { let packet_string = buffer.get_string_utf16()?; - let split: Vec<&str> = packet_string.split("§").collect(); + let split: Vec<&str> = packet_string.split('§').collect(); error_by_expected_size(3, split.len())?; let description = split[0].to_string(); diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 6188dbf..4954298 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -49,7 +49,7 @@ impl LegacyV1_6 { pub fn get_response(buffer: &mut Bufferer) -> GDResult { let packet_string = buffer.get_string_utf16()?; - let split: Vec<&str> = packet_string.split("\x00").collect(); + let split: Vec<&str> = packet_string.split('\x00').collect(); error_by_expected_size(5, split.len())?; let version_protocol = split[0].parse() diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index 1723b81..a51f6d7 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -174,14 +174,13 @@ pub(crate) fn as_varint(value: i32) -> Vec { pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult { let length = get_varint(buffer)? as usize; - let mut text = vec![0; length]; + let mut text = Vec::with_capacity(length); - for i in 0..length { - text[i] = buffer.get_u8()?; + for _ in 0..length { + text.push(buffer.get_u8()?) } - Ok(String::from_utf8(text) - .map_err(|_| PacketBad)?) + String::from_utf8(text).map_err(|_| PacketBad) } #[allow(dead_code)] diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 6b51780..6c1c60c 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -32,7 +32,7 @@ impl Packet { header: initial.header, kind: initial.kind, payload: match kind { - Request::INFO => { + Request::Info => { initial.payload.extend(challenge); initial.payload }, @@ -46,7 +46,7 @@ impl Packet { header: 4294967295, //FF FF FF FF kind: kind as u8, payload: match kind { - Request::INFO => String::from("Source Engine Query\0").into_bytes(), + Request::Info => String::from("Source Engine Query\0").into_bytes(), _ => vec![0xFF, 0xFF, 0xFF, 0xFF] } } @@ -124,10 +124,8 @@ impl SplitPacket { let mut decompressed_payload = Vec::with_capacity(decompressed_size); decoder.read(&mut decompressed_payload).map_err(|_| Decompress)?; - if decompressed_payload.len() != decompressed_size { - Err(Decompress) - } - else if crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { + if decompressed_payload.len() != decompressed_size + || crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { Err(Decompress) } else { @@ -162,13 +160,13 @@ impl ValveProtocol { let header = buffer.get_u8()?; buffer.move_position_backward(1); if header == 0xFE { //the packet is split - let mut main_packet = SplitPacket::new(&engine, protocol, &mut buffer)?; + let mut main_packet = SplitPacket::new(engine, protocol, &mut buffer)?; let mut chunk_packets = Vec::with_capacity((main_packet.total - 1) as usize); for _ in 1..main_packet.total { let new_data = self.socket.receive(Some(buffer_size))?; buffer = Bufferer::new_with_data(Endianess::Little, &new_data); - let chunk_packet = SplitPacket::new(&engine, protocol, &mut buffer)?; + let chunk_packet = SplitPacket::new(engine, protocol, &mut buffer)?; chunk_packets.push(chunk_packet); } @@ -266,7 +264,7 @@ impl ValveProtocol { /// Get the server information's. fn get_server_info(&mut self, engine: &Engine) -> GDResult { - let mut buffer = self.get_request_data(&engine, 0, Request::INFO)?; + let mut buffer = self.get_request_data(engine, 0, Request::Info)?; if let Engine::GoldSrc(force) = engine { if *force { @@ -365,7 +363,7 @@ impl ValveProtocol { /// Get the server player's. fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(&engine, protocol, Request::PLAYERS)?; + let mut buffer = self.get_request_data(engine, protocol, Request::Players)?; let count = buffer.get_u8()? as usize; let mut players: Vec = Vec::with_capacity(count); @@ -393,7 +391,7 @@ impl ValveProtocol { /// Get the server's rules. fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(&engine, protocol, Request::RULES)?; + let mut buffer = self.get_request_data(engine, protocol, Request::Rules)?; let count = buffer.get_u16()? as usize; let mut rules: HashMap = HashMap::with_capacity(count); @@ -416,7 +414,7 @@ impl ValveProtocol { /// Query a server by providing the address, the port, the app, gather and timeout settings. /// Providing None to the settings results in using the default values for them (GatherSettings::[default](GatheringSettings::default), TimeoutSettings::[default](TimeoutSettings::default)). pub fn query(address: &str, port: u16, engine: Engine, gather_settings: Option, timeout_settings: Option) -> GDResult { - let response_gather_settings = gather_settings.unwrap_or(GatheringSettings::default()); + let response_gather_settings = gather_settings.unwrap_or_default(); get_response(address, port, engine, response_gather_settings, timeout_settings) } @@ -426,21 +424,19 @@ fn get_response(address: &str, port: u16, engine: Engine, gather_settings: Gathe let info = client.get_server_info(&engine)?; let protocol = info.protocol; - if let Engine::Source(source_app) = &engine { - if let Some(appids) = source_app { - let mut is_specified_id = false; + if let Engine::Source(Some(appids)) = &engine { + let mut is_specified_id = false; - if appids.0 == info.appid { + if appids.0 == info.appid { + is_specified_id = true; + } else if let Some(dedicated_appid) = appids.1 { + if dedicated_appid == info.appid { is_specified_id = true; - } else if let Some(dedicated_appid) = appids.1 { - if dedicated_appid == info.appid { - is_specified_id = true; - } } + } - if !is_specified_id { - return Err(BadGame(format!("AppId: {}", info.appid))); - } + if !is_specified_id { + return Err(BadGame(format!("AppId: {}", info.appid))); } } diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 5ba6a67..bb45ed0 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -124,19 +124,19 @@ pub(crate) fn get_optional_extracted_data(data: Option) -> (Option) -> GDResult<()> { - let settings = timeout_settings.unwrap_or(TimeoutSettings::default()); + let settings = timeout_settings.unwrap_or_default(); self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error @@ -39,7 +39,7 @@ impl Socket for TcpSocket { } fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket.write(&data).map_err(|_| PacketSend)?; + self.socket.write(data).map_err(|_| PacketSend)?; Ok(()) } @@ -68,7 +68,7 @@ impl Socket for UdpSocket { } fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { - let settings = timeout_settings.unwrap_or(TimeoutSettings::default()); + let settings = timeout_settings.unwrap_or_default(); self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error @@ -76,7 +76,7 @@ impl Socket for UdpSocket { } fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket.send_to(&data, &self.complete_address).map_err(|_| PacketSend)?; + self.socket.send_to(data, &self.complete_address).map_err(|_| PacketSend)?; Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index ba79a83..0db5e31 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,15 +1,12 @@ +use std::cmp::Ordering; use crate::GDResult; use crate::GDError::{PacketOverflow, PacketUnderflow}; pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { - if size < expected { - Err(PacketUnderflow) - } - else if size > expected { - Err(PacketOverflow) - } - else { - Ok(()) + match size.cmp(&expected) { + Ordering::Greater => Err(PacketOverflow), + Ordering::Less => Err(PacketUnderflow), + Ordering::Equal => Ok(()) } } From 9ad2f143dd1e5729816833e00d391e1be3d23044 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 9 Mar 2023 16:41:01 +0200 Subject: [PATCH 159/597] [Crate] Use Byteorder crate --- Cargo.toml | 2 ++ src/bufferer.rs | 33 +++++++++++++-------------------- src/protocols/valve/protocol.rs | 4 ++-- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c88318c..60ba981 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ rust-version = "1.56.1" no_games = [] [dependencies] +byteorder = "1.4.3" + bzip2-rs = "0.1.2" # for compression crc32fast = "1.3.2" diff --git a/src/bufferer.rs b/src/bufferer.rs index 22453ce..4a6ddc8 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -1,5 +1,6 @@ use crate::GDResult; use crate::GDError::{PacketBad, PacketUnderflow}; +use byteorder::{ByteOrder, LittleEndian, BigEndian}; pub enum Endianess { Little, Big @@ -39,11 +40,9 @@ impl Bufferer { return Err(PacketUnderflow); } - let source_data: [u8; 2] = (&self.data[self.position..self.position + 2]).try_into().unwrap(); - let value = match self.endianess { - Endianess::Little => u16::from_le_bytes(source_data), - Endianess::Big => u16::from_be_bytes(source_data) + Endianess::Little => LittleEndian::read_u16(self.remaining_data()), + Endianess::Big => BigEndian::read_u16(self.remaining_data()), }; self.position += 2; @@ -55,11 +54,9 @@ impl Bufferer { return Err(PacketUnderflow); } - let source_data: [u8; 4] = (&self.data[self.position..self.position + 4]).try_into().unwrap(); - let value = match self.endianess { - Endianess::Little => u32::from_le_bytes(source_data), - Endianess::Big => u32::from_be_bytes(source_data) + Endianess::Little => LittleEndian::read_u32(self.remaining_data()), + Endianess::Big => BigEndian::read_u32(self.remaining_data()), }; self.position += 4; @@ -71,11 +68,9 @@ impl Bufferer { return Err(PacketUnderflow); } - let source_data: [u8; 4] = (&self.data[self.position..self.position + 4]).try_into().unwrap(); - let value = match self.endianess { - Endianess::Little => f32::from_le_bytes(source_data), - Endianess::Big => f32::from_be_bytes(source_data) + Endianess::Little => LittleEndian::read_f32(self.remaining_data()), + Endianess::Big => BigEndian::read_f32(self.remaining_data()) }; self.position += 4; @@ -87,11 +82,9 @@ impl Bufferer { return Err(PacketUnderflow); } - let source_data: [u8; 8] = (&self.data[self.position..self.position + 8]).try_into().unwrap(); - let value = match self.endianess { - Endianess::Little => u64::from_le_bytes(source_data), - Endianess::Big => u64::from_be_bytes(source_data) + Endianess::Little => LittleEndian::read_u64(self.remaining_data()), + Endianess::Big => BigEndian::read_u64(self.remaining_data()) }; self.position += 8; @@ -151,10 +144,6 @@ impl Bufferer { pub fn move_position_backward(&mut self, by: usize) { self.position -= by; } - - pub fn get_data_in_front_of_position(&self) -> Vec { - self.data[self.position..].to_vec() - } pub fn data_length(&self) -> usize { self.data.len() @@ -164,6 +153,10 @@ impl Bufferer { &self.data[self.position..] } + pub fn remaining_data_vec(&self) -> Vec { + self.data[self.position..].to_vec() + } + pub fn remaining_length(&self) -> usize { self.data.len() - self.position } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 6c1c60c..ee3b675 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -21,7 +21,7 @@ impl Packet { Ok(Self { header: buffer.get_u32()?, kind: buffer.get_u8()?, - payload: buffer.get_data_in_front_of_position() + payload: buffer.remaining_data_vec() }) } @@ -110,7 +110,7 @@ impl SplitPacket { compressed, decompressed_size, uncompressed_crc32, - payload: buffer.get_data_in_front_of_position() + payload: buffer.remaining_data_vec() }) } From a3cbb24d0db6c9e8353a870d7edd8598ba2ccb45 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 9 Mar 2023 17:17:48 +0200 Subject: [PATCH 160/597] [Crate] Further use the Byteorder crate --- src/bufferer.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bufferer.rs b/src/bufferer.rs index 4a6ddc8..5597084 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -126,9 +126,9 @@ impl Bufferer { } let paired_buf: Vec = sub_buf.chunks_exact(2) - .into_iter().map(|a| match self.endianess { - Endianess::Little => u16::from_le_bytes([a[0], a[1]]), - Endianess::Big => u16::from_be_bytes([a[0], a[1]]) + .into_iter().map(|pair| match self.endianess { + Endianess::Little => LittleEndian::read_u16(pair), + Endianess::Big => BigEndian::read_u16(pair) }).collect(); let value = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?; From 2865543975c670bb9dee577f637a0b356c78f35b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 10 Mar 2023 21:52:56 +0200 Subject: [PATCH 161/597] [Repo] Add Discord Server link --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 311ddbe..a930d68 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Minimum Supported Rust Version is `1.56.1` and the code is cross-platform. +Check out the GameDig Community Discord Server [here](https://discord.gg/NVCMn3tnxH). + ## Games/Services/Protocols List To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. From bf2a05f48893e9ef16cd50efefdeee9add1b93ee Mon Sep 17 00:00:00 2001 From: Cain <75994858+cainthebest@users.noreply.github.com> Date: Fri, 10 Mar 2023 21:27:30 +0100 Subject: [PATCH 162/597] [Tests] result, display, trait and cloning (#19) --- src/errors.rs | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index afefef1..a647123 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,13 +1,12 @@ - //! The library's possible errors. use std::fmt; -use std::{fmt::Formatter, error::Error}; +use std::{error::Error, fmt::Formatter}; /// Result of Type and GDError. pub type GDResult = Result; /// GameDig Error. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum GDError { /// The received packet was bigger than the buffer size. PacketOverflow, @@ -48,3 +47,44 @@ impl fmt::Display for GDError { write!(f, "{:?}", self) } } + +#[cfg(test)] +mod tests { + use super::*; + + // Testing Ok variant of the GDResult type + #[test] + fn test_gdresult_ok() { + let result: GDResult = Ok(42); + assert_eq!(result.unwrap(), 42); + } + + // Testing Err variant of the GDResult type + #[test] + fn test_gdresult_err() { + let result: GDResult = Err(GDError::InvalidInput); + assert_eq!(result.is_err(), true); + } + + // Testing the Display trait for the GDError type + #[test] + fn test_display() { + let error = GDError::PacketOverflow; + assert_eq!(format!("{}", error), "PacketOverflow"); + } + + // Testing the Error trait for the GDError type + #[test] + fn test_error_trait() { + let error = GDError::PacketBad; + assert!(error.source().is_none()); + } + + // Testing cloning the GDError type + #[test] + fn test_cloning() { + let error = GDError::BadGame(String::from("MyGame")); + let cloned_error = error.clone(); + assert_eq!(error, cloned_error); + } +} From 7352c595e9e55484f9b6f50a40b75dccf82e875b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 11 Mar 2023 00:00:31 +0200 Subject: [PATCH 163/597] [Socket] Replace static with const for DEFAULT_PACKET_SIZE value --- src/socket.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/socket.rs b/src/socket.rs index 94ceb1b..0784c8e 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -5,7 +5,7 @@ use crate::GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}; use crate::protocols::types::TimeoutSettings; use crate::utils::address_and_port_as_string; -static DEFAULT_PACKET_SIZE: usize = 1024; +const DEFAULT_PACKET_SIZE: usize = 1024; pub trait Socket { fn new(address: &str, port: u16) -> GDResult where Self: Sized; @@ -23,10 +23,9 @@ pub struct TcpSocket { impl Socket for TcpSocket { fn new(address: &str, port: u16) -> GDResult { let complete_address = address_and_port_as_string(address, port); - let socket = net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)?; Ok(Self { - socket + socket: net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)? }) } From 3dacc09173331917dc83c9c2f2fcc25636cf56d7 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 11 Mar 2023 00:20:09 +0200 Subject: [PATCH 164/597] [Utils] Replace address and port as string string and additions to format! --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 0db5e31..e55b052 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,7 +11,7 @@ pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { } pub fn address_and_port_as_string(address: &str, port: u16) -> String { - address.to_string() + ":" + &*port.to_string() + format!("{}:{}", address, port) } pub fn u8_lower_upper(n: u8) -> (u8, u8) { From 927d56b1ee55426d7f029f906e6faae61aed5b0e Mon Sep 17 00:00:00 2001 From: Cain <75994858+cainthebest@users.noreply.github.com> Date: Sat, 11 Mar 2023 10:17:36 +0100 Subject: [PATCH 165/597] [Tests]: Timeout settings (#18) --- src/protocols/types.rs | 68 +++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/src/protocols/types.rs b/src/protocols/types.rs index 5e0bac6..b0047fc 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -1,12 +1,12 @@ -use std::time::Duration; -use crate::GDResult; use crate::GDError::InvalidInput; +use crate::GDResult; +use std::time::Duration; /// Timeout settings for socket operations -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TimeoutSettings { read: Option, - write: Option + write: Option, } impl TimeoutSettings { @@ -14,20 +14,17 @@ impl TimeoutSettings { pub fn new(read: Option, write: Option) -> GDResult { if let Some(read_duration) = read { if read_duration == Duration::new(0, 0) { - return Err(InvalidInput) + return Err(InvalidInput); } } if let Some(write_duration) = write { if write_duration == Duration::new(0, 0) { - return Err(InvalidInput) + return Err(InvalidInput); } } - Ok(Self { - read, - write - }) + Ok(Self { read, write }) } /// Get the read timeout. @@ -46,7 +43,56 @@ impl Default for TimeoutSettings { fn default() -> Self { Self { read: Some(Duration::from_secs(4)), - write: Some(Duration::from_secs(4)) + write: Some(Duration::from_secs(4)), } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + // Test creating new TimeoutSettings with valid durations + #[test] + fn test_new_with_valid_durations() -> GDResult<()> { + // Define valid read and write durations + let read_duration = Duration::from_secs(1); + let write_duration = Duration::from_secs(2); + + // Create new TimeoutSettings with the valid durations + let timeout_settings = TimeoutSettings::new(Some(read_duration), Some(write_duration))?; + + // Verify that the get_read and get_write methods return the expected values + assert_eq!(timeout_settings.get_read(), Some(read_duration)); + assert_eq!(timeout_settings.get_write(), Some(write_duration)); + + Ok(()) + } + + // Test creating new TimeoutSettings with a zero duration + #[test] + fn test_new_with_zero_duration() { + // Define a zero read duration and a valid write duration + let read_duration = Duration::new(0, 0); + let write_duration = Duration::from_secs(2); + + // Try to create new TimeoutSettings with the zero read duration (this should fail) + let result = TimeoutSettings::new(Some(read_duration), Some(write_duration)); + + // Verify that the function returned an error and that the error type is InvalidInput + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), InvalidInput); + } + + // Test that the default TimeoutSettings values are correct + #[test] + fn test_default_values() { + // Get the default TimeoutSettings values + let default_settings = TimeoutSettings::default(); + + // Verify that the get_read and get_write methods return the expected default values + assert_eq!(default_settings.get_read(), Some(Duration::from_secs(4))); + assert_eq!(default_settings.get_write(), Some(Duration::from_secs(4))); + } +} From 568c53f129a9c517a31ffd82aa5679fb19b50702 Mon Sep 17 00:00:00 2001 From: Cain <75994858+cainthebest@users.noreply.github.com> Date: Sun, 12 Mar 2023 00:07:36 +0100 Subject: [PATCH 166/597] [Tests] udp and tcp socket test (#17) * impl(test): udp and tcp socket test * fix(test): try and fix possable thread hang * fix(test): move socket to thread * [test/socket] Move listener outside of thread scope to make sure it is binded * [test/socket] Let the OS to bind to an available port --------- Co-authored-by: CosminPerRam --- src/socket.rs | 105 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/src/socket.rs b/src/socket.rs index 0784c8e..493b3e3 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -1,14 +1,16 @@ -use std::io::{Read, Write}; -use std::net; -use crate::GDResult; -use crate::GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}; use crate::protocols::types::TimeoutSettings; use crate::utils::address_and_port_as_string; +use crate::GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}; +use crate::GDResult; +use std::io::{Read, Write}; +use std::net; const DEFAULT_PACKET_SIZE: usize = 1024; pub trait Socket { - fn new(address: &str, port: u16) -> GDResult where Self: Sized; + fn new(address: &str, port: u16) -> GDResult + where + Self: Sized; fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()>; @@ -17,7 +19,7 @@ pub trait Socket { } pub struct TcpSocket { - socket: net::TcpStream + socket: net::TcpStream, } impl Socket for TcpSocket { @@ -25,13 +27,13 @@ impl Socket for TcpSocket { let complete_address = address_and_port_as_string(address, port); Ok(Self { - socket: net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)? + socket: net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)?, }) } fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { let settings = timeout_settings.unwrap_or_default(); - self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new + self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error Ok(()) @@ -44,7 +46,9 @@ impl Socket for TcpSocket { fn receive(&mut self, size: Option) -> GDResult> { let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); - self.socket.read_to_end(&mut buf).map_err(|_| PacketReceive)?; + self.socket + .read_to_end(&mut buf) + .map_err(|_| PacketReceive)?; Ok(buf) } @@ -52,7 +56,7 @@ impl Socket for TcpSocket { pub struct UdpSocket { socket: net::UdpSocket, - complete_address: String + complete_address: String, } impl Socket for UdpSocket { @@ -62,27 +66,100 @@ impl Socket for UdpSocket { Ok(Self { socket, - complete_address + complete_address, }) } fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { let settings = timeout_settings.unwrap_or_default(); - self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new + self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error Ok(()) } fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket.send_to(data, &self.complete_address).map_err(|_| PacketSend)?; + self.socket + .send_to(data, &self.complete_address) + .map_err(|_| PacketSend)?; Ok(()) } fn receive(&mut self, size: Option) -> GDResult> { let mut buf: Vec = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; - let (number_of_bytes_received, _) = self.socket.recv_from(&mut buf).map_err(|_| PacketReceive)?; + let (number_of_bytes_received, _) = + self.socket.recv_from(&mut buf).map_err(|_| PacketReceive)?; Ok(buf[..number_of_bytes_received].to_vec()) } } + +#[cfg(test)] +mod tests { + use std::thread; + + use super::*; + + #[test] + fn test_tcp_socket_send_and_receive() { + // Spawn a thread to run the server + let listener = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let bound_address = listener.local_addr().unwrap(); + let server_thread = thread::spawn(move || { + let (mut stream, _) = listener.accept().unwrap(); + let mut buf = [0; 1024]; + stream.read(&mut buf).unwrap(); + stream.write(&buf).unwrap(); + }); + + // Create a TCP socket and send a message to the server + let mut socket = TcpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); + let message = b"hello, world!"; + socket.send(message).unwrap(); + + // Receive the response from the server + let received_message: Vec = socket + .receive(None) + .unwrap() + // Iterate over the buffer and remove 0s that are alone in the buffer + // just added to pass default size + .into_iter() + .filter(|&x| x != 0) + .collect(); + + server_thread.join().expect("server thread panicked"); + + assert_eq!(message, &received_message[..]); + } + + #[test] + fn test_udp_socket_send_and_receive() { + // Spawn a thread to run the server + let socket = net::UdpSocket::bind("127.0.0.1:0").unwrap(); + let bound_address = socket.local_addr().unwrap(); + let server_thread = thread::spawn(move || { + let mut buf = [0; 1024]; + let (_, src_addr) = socket.recv_from(&mut buf).unwrap(); + socket.send_to(&buf, src_addr).unwrap(); + }); + + // Create a UDP socket and send a message to the server + let mut socket = UdpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); + let message = b"hello, world!"; + socket.send(message).unwrap(); + + // Receive the response from the server + let received_message: Vec = socket + .receive(None) + .unwrap() + // Iterate over the buffer and remove 0s that are alone in the buffer + // just added to pass default size + .into_iter() + .filter(|&x| x != 0) + .collect(); + + server_thread.join().expect("server thread panicked"); + + assert_eq!(message, &received_message[..]); + } +} From 7500b09b4d2166afd30ff1d4a020a27e0beb85ca Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 12 Mar 2023 22:53:26 +0200 Subject: [PATCH 167/597] [Bufferer] Use struct functions internally too --- src/bufferer.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/bufferer.rs b/src/bufferer.rs index 5597084..5b24aae 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -31,7 +31,7 @@ impl Bufferer { } let value = self.data[self.position]; - self.position += 1; + self.move_position_ahead(1); Ok(value) } @@ -45,10 +45,10 @@ impl Bufferer { Endianess::Big => BigEndian::read_u16(self.remaining_data()), }; - self.position += 2; + self.move_position_ahead(2); Ok(value) } - + pub fn get_u32(&mut self) -> GDResult { if self.check_size(4) { return Err(PacketUnderflow); @@ -59,7 +59,7 @@ impl Bufferer { Endianess::Big => BigEndian::read_u32(self.remaining_data()), }; - self.position += 4; + self.move_position_ahead(4); Ok(value) } @@ -73,7 +73,7 @@ impl Bufferer { Endianess::Big => BigEndian::read_f32(self.remaining_data()) }; - self.position += 4; + self.move_position_ahead(4); Ok(value) } @@ -87,12 +87,12 @@ impl Bufferer { Endianess::Big => BigEndian::read_u64(self.remaining_data()) }; - self.position += 8; + self.move_position_ahead(8); Ok(value) } pub fn get_string_utf8(&mut self) -> GDResult { - let sub_buf = &self.data[self.position..]; + let sub_buf = self.remaining_data(); if sub_buf.is_empty() { return Err(PacketUnderflow); } @@ -102,12 +102,12 @@ impl Bufferer { let value = std::str::from_utf8(&sub_buf[..first_null_position]) .map_err(|_| PacketBad)?.to_string(); - self.position += value.len() + 1; + self.move_position_ahead(value.len() + 1); Ok(value) } pub fn get_string_utf8_unended(&mut self) -> GDResult { - let sub_buf = &self.data[self.position..]; + let sub_buf = self.remaining_data(); if sub_buf.is_empty() { return Err(PacketUnderflow); } @@ -115,12 +115,12 @@ impl Bufferer { let value = std::str::from_utf8(sub_buf) .map_err(|_| PacketBad)?.to_string(); - self.position += value.len(); + self.move_position_ahead(value.len()); Ok(value) } pub fn get_string_utf16(&mut self) -> GDResult { - let sub_buf = &self.data[self.position..]; + let sub_buf = self.remaining_data(); if sub_buf.is_empty() { return Err(PacketUnderflow); } @@ -133,10 +133,10 @@ impl Bufferer { let value = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?; - self.position += value.len() * 2; + self.move_position_ahead(value.len() * 2); Ok(value) } - + pub fn move_position_ahead(&mut self, by: usize) { self.position += by; } @@ -154,11 +154,11 @@ impl Bufferer { } pub fn remaining_data_vec(&self) -> Vec { - self.data[self.position..].to_vec() + self.remaining_data().to_vec() } pub fn remaining_length(&self) -> usize { - self.data.len() - self.position + self.data_length() - self.position } pub fn as_endianess(&self, endianess: Endianess) -> Self { From 9f6b3bae189e266739be8badab844c343e05242a Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 13 Mar 2023 00:43:03 +0200 Subject: [PATCH 168/597] [Crate] Reorganize README. --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a930d68..a35e905 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,17 @@ **rust-GameDig** is a game servers/services query library that fetches the availability and details of those, this library brings what **[node-GameDig](https://github.com/gamedig/node-gamedig)** does, to pure Rust! -Minimum Supported Rust Version is `1.56.1` and the code is cross-platform. - -Check out the GameDig Community Discord Server [here](https://discord.gg/NVCMn3tnxH). - -## Games/Services/Protocols List -To see the supported (or the planned to support) games/services/protocols, see [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) respectively. +## Community +Checkout the GameDig Community Discord Server [here](https://discord.gg/NVCMn3tnxH). +Note that it isn't be a replacement for GitHub issues, if you have found a problem +within the library or want to request a feature, it's better to do so here rather than +on Discord. ## Usage -Just pick a game/service/protocol, provide the ip and the port (be aware that some game servers use a separate port for the info queries, the port can also be optional if the server is running the default ports) then query on it. +Minimum Supported Rust Version is `1.56.1` and the code is cross-platform. + +Pick a game/service/protocol (check the [GAMES](GAMES.md), [SERVICES](SERVICES.md) and [PROTOCOLS](PROTOCOLS.md) files to see the currently supported ones), provide the ip and the port (be aware that some game servers use a separate port for the info queries, the port can also be optional if the server is running the default ports) then query on it. + [Team Fortress 2](https://store.steampowered.com/app/440/Team_Fortress_2/) query example: ```rust use gamedig::games::tf2; @@ -54,11 +56,11 @@ Response (note that some games have a different structure): } ``` -To see more examples, see the [examples](examples) folder. +Want to see more examples? Checkout the [examples](examples) folder. ## Documentation The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). -Curious about the history and what changed between versions? Check out the [CHANGELOG](CHANGELOG.md) file. +Curious about the history and what changed between versions? Everything is in the [CHANGELOG](CHANGELOG.md) file. ## Contributing If you want see your favorite game/service being supported here, open an issue, and I'll prioritize it (or do a pull request if you want to implement it yourself)! From 7f73eb582df7615423188fa25f4ff4690239afea Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 13 Mar 2023 00:45:54 +0200 Subject: [PATCH 169/597] [Crate] Update CHANGELOG to add optimizations thanks --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f74845..62fc68a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ Who knows what the future holds... # 0.X.Y - DD/MM/2023 ### Changes: +Crate: +- General optimizations thanks to [cargo clippy](https://github.com/rust-lang/rust-clippy) and [@cainthebest](https://github.com/cainthebest). + Protocols: - GameSpy 1: Add key `admin` as a possible variable for `admin_name`. From bd2e373d6644cb7a05d615e807ff39f5292b9396 Mon Sep 17 00:00:00 2001 From: Cain <75994858+cainthebest@users.noreply.github.com> Date: Mon, 13 Mar 2023 10:28:49 +0100 Subject: [PATCH 170/597] [Crate] Make clippy happy (#23) * fix: clippy::type_complexity * fix: clippy::needless_doctest_main * fix: clippy::read_zero_byte_vec * fix: clippy::useless_conversion * fix: clippy::slow_vector_initialization --- src/bufferer.rs | 12 +++++++----- src/lib.rs | 23 ++++++++++------------- src/protocols/valve/protocol.rs | 3 ++- src/protocols/valve/types.rs | 14 ++++++++++++-- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/bufferer.rs b/src/bufferer.rs index 5b24aae..30c01de 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -125,11 +125,13 @@ impl Bufferer { return Err(PacketUnderflow); } - let paired_buf: Vec = sub_buf.chunks_exact(2) - .into_iter().map(|pair| match self.endianess { - Endianess::Little => LittleEndian::read_u16(pair), - Endianess::Big => BigEndian::read_u16(pair) - }).collect(); + let paired_buf: Vec = sub_buf + .chunks_exact(2) + .map(|pair| match self.endianess { + Endianess::Little => LittleEndian::read_u16(pair), + Endianess::Big => BigEndian::read_u16(pair), + }) + .collect(); let value = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?; diff --git a/src/lib.rs b/src/lib.rs index de0ae9e..9ac3aa3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,15 @@ - //! Game Server Query Library. //! //! # Usage example: //! -//! ```no_run -//!use gamedig::games::tf2; +//! ``` +//! use gamedig::games::tf2; //! -//!fn main() { -//! let response = tf2::query("127.0.0.1", None); // None is the default port (which is 27015), could also be Some(27015) -//! match response { // Result type, must check what it is... -//! Err(error) => println!("Couldn't query, error: {}", error), -//! Ok(r) => println!("{:#?}", r) -//! } -//!} +//! let response = tf2::query("127.0.0.1", None); // None is the default port (which is 27015), could also be Some(27015) +//! match response { // Result type, must check what it is... +//! Err(error) => println!("Couldn't query, error: {}", error), +//! Ok(r) => println!("{:#?}", r) +//! } //! ``` //! //! # Crate features: @@ -21,13 +18,13 @@ //! `no_games` - disables the included games support. pub mod errors; -pub mod protocols; #[cfg(not(feature = "no_games"))] pub mod games; +pub mod protocols; -mod utils; -mod socket; mod bufferer; +mod socket; +mod utils; pub use errors::*; #[cfg(not(feature = "no_games"))] diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index ee3b675..a1eefd3 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -121,7 +121,8 @@ impl SplitPacket { let decompressed_size = self.decompressed_size.unwrap() as usize; - let mut decompressed_payload = Vec::with_capacity(decompressed_size); + let mut decompressed_payload = vec![0; decompressed_size]; + decoder.read(&mut decompressed_payload).map_err(|_| Decompress)?; if decompressed_payload.len() != decompressed_size diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index bb45ed0..4601251 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -116,10 +116,20 @@ pub struct ModData { pub has_own_dll: bool } -pub(crate) fn get_optional_extracted_data(data: Option) -> (Option, Option, Option, Option, Option) { +pub(crate) type ExtractedData = ( + Option, + Option, + Option, + Option, + Option, +); + +pub(crate) fn get_optional_extracted_data( + data: Option, +) -> ExtractedData { match data { None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords), } } From 84af4230f7af7479ea955048964f867e00fedb1e Mon Sep 17 00:00:00 2001 From: Cain <75994858+cainthebest@users.noreply.github.com> Date: Mon, 13 Mar 2023 15:51:33 +0100 Subject: [PATCH 171/597] [Crate] Add feature: serde (#21) * feat(serde): add additional derives * fix: remove attr on internal enum * fix add missing derive --- Cargo.toml | 13 +++-- src/games/ts.rs | 9 +++- src/protocols/gamespy/types.rs | 13 +++-- src/protocols/minecraft/types.rs | 48 ++++++++++------- src/protocols/valve/types.rs | 92 ++++++++++++++++++++------------ 5 files changed, 113 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 60ba981..88a247c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,10 @@ name = "gamedig" version = "0.2.1" edition = "2021" -authors = ["CosminPerRam [https://github.com/CosminPerRam]", "node-GameDig contributors [https://github.com/gamedig/node-gamedig/contributors]"] +authors = [ + "CosminPerRam [https://github.com/CosminPerRam]", + "node-GameDig contributors [https://github.com/gamedig/node-gamedig/contributors]", +] license = "MIT" description = "Check out servers with this." homepage = "https://github.com/gamedig/rust-gamedig" @@ -13,12 +16,14 @@ keywords = ["server", "query", "game", "check", "status"] rust-version = "1.56.1" [features] +default = [] no_games = [] +serde = ["dep:serde", "serde/derive"] [dependencies] byteorder = "1.4.3" - -bzip2-rs = "0.1.2" # for compression +bzip2-rs = "0.1.2" crc32fast = "1.3.2" +serde_json = "1.0.91" -serde_json = "1.0.91" # json to structs +serde = { version = "1.0.155", optional = true } diff --git a/src/games/ts.rs b/src/games/ts.rs index 3d72811..3a6daa7 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -3,7 +3,11 @@ use crate::GDResult; use crate::protocols::valve; use crate::protocols::valve::{Server, ServerPlayer, get_optional_extracted_data, SteamApp}; -#[derive(Debug)] +#[cfg (feature = "serde")] +use serde::{Serialize, Deserialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct TheShipPlayer { pub name: String, pub score: u32, @@ -24,7 +28,8 @@ impl TheShipPlayer { } } -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] pub struct Response { pub protocol: u8, pub name: String, diff --git a/src/protocols/gamespy/types.rs b/src/protocols/gamespy/types.rs index db4ed3e..8fcefeb 100644 --- a/src/protocols/gamespy/types.rs +++ b/src/protocols/gamespy/types.rs @@ -1,7 +1,11 @@ use std::collections::HashMap; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// A player’s details. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Player { pub name: String, pub team: u8, @@ -13,11 +17,12 @@ pub struct Player { pub frags: u32, pub deaths: Option, pub health: Option, - pub secret: bool + pub secret: bool, } /// A query response. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Response { pub name: String, pub map: String, @@ -32,5 +37,5 @@ pub struct Response { pub players_minimum: u8, pub players: Vec, pub tournament: bool, - pub unused_entries: HashMap + pub unused_entries: HashMap, } diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index a51f6d7..d596339 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -1,45 +1,51 @@ - /* Although its a lightly modified version, this file contains code by Jaiden Bernard (2021-2022 - MIT) from https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs */ -use crate::GDResult; use crate::bufferer::Bufferer; use crate::GDError::{PacketBad, UnknownEnumCast}; +use crate::GDResult; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; /// The type of Minecraft Server you want to query. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Server { /// Java Edition. Java, /// Legacy Java. Legacy(LegacyGroup), /// Bedrock Edition. - Bedrock + Bedrock, } /// Legacy Java (Versions) Groups. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum LegacyGroup { /// 1.6 V1_6, /// 1.4 - 1.5 V1_4, /// Beta 1.8 - 1.3 - VB1_8 + VB1_8, } /// Information about a player. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Player { pub name: String, - pub id: String + pub id: String, } /// A Java query response. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct JavaResponse { /// Version name, example: "1.19.2". pub version_name: String, @@ -60,11 +66,12 @@ pub struct JavaResponse { /// Tells if secure chat is enforced (can be missing). pub enforces_secure_chat: Option, /// Tell's the server type. - pub server_type: Server + pub server_type: Server, } /// A Bedrock Edition query response. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct BedrockResponse { /// Server's edition. pub edition: String, @@ -85,7 +92,7 @@ pub struct BedrockResponse { /// Current game mode. pub game_mode: Option, /// Tells the server type. - pub server_type: Server + pub server_type: Server, } impl JavaResponse { @@ -100,15 +107,20 @@ impl JavaResponse { favicon: None, previews_chat: None, enforces_secure_chat: None, - server_type: Server::Bedrock + server_type: Server::Bedrock, } } } -/// A server's game mode (used only by Bedrock servers). -#[derive(Debug)] +/// A server's game mode (used only by Bedrock servers. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum GameMode { - Survival, Creative, Hardcore, Spectator, Adventure + Survival, + Creative, + Hardcore, + Spectator, + Adventure, } impl GameMode { @@ -119,7 +131,7 @@ impl GameMode { "Hardcore" => Ok(GameMode::Hardcore), "Spectator" => Ok(GameMode::Spectator), "Adventure" => Ok(GameMode::Adventure), - _ => Err(UnknownEnumCast) + _ => Err(UnknownEnumCast), } } } @@ -137,7 +149,7 @@ pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult { // The 5th byte is only allowed to have the 4 smallest bits set if i == 4 && (current_byte & 0xf0 != 0) { - return Err(PacketBad) + return Err(PacketBad); } if (current_byte & msb) == 0 { diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 4601251..681de23 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,31 +1,38 @@ use std::collections::HashMap; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The type of the server. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Server { Dedicated, NonDedicated, - TV + TV, } /// The Operating System that the server is on. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Environment { Linux, Windows, - Mac + Mac, } /// A query response. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] pub struct Response { pub info: ServerInfo, pub players: Option>, - pub rules: Option> + pub rules: Option>, } /// General server information's. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ServerInfo { /// Protocol used by the server. pub protocol: u8, @@ -62,11 +69,12 @@ pub struct ServerInfo { /// GoldSrc only: Indicates whether the hosted game is a mod. pub is_mod: bool, /// GoldSrc only: If the game is a mod, provide additional data. - pub mod_data: Option + pub mod_data: Option, } /// A server player. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct ServerPlayer { /// Player's name. pub name: String, @@ -81,15 +89,17 @@ pub struct ServerPlayer { } /// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TheShip { pub mode: u8, pub witnesses: u8, - pub duration: u8 + pub duration: u8, } /// Some extra data that the server might provide or not. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ExtraData { /// The server's game port number. pub port: Option, @@ -102,18 +112,19 @@ pub struct ExtraData { /// Keywords that describe the server according to it. pub keywords: Option, /// The server's 64-bit GameID. - pub game_id: Option + pub game_id: Option, } /// Data related to GoldSrc Mod response. -#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ModData { pub link: String, pub download_link: String, pub version: u32, pub size: u32, pub multiplayer_only: bool, - pub has_own_dll: bool + pub has_own_dll: bool, } pub(crate) type ExtractedData = ( @@ -142,11 +153,12 @@ pub(crate) enum Request { /// Known as `A2S_PLAYERS` Players = 0x55, /// Known as `A2S_RULES` - Rules = 0x56 + Rules = 0x56, } /// Supported steam apps -#[derive(Eq, PartialEq, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum SteamApp { /// Counter-Strike CS, @@ -236,9 +248,9 @@ impl SteamApp { /// Get the specified app as engine. pub fn as_engine(&self) -> Engine { match self { - SteamApp::CS => Engine::GoldSrc(false), //10 - SteamApp::TFC => Engine::GoldSrc(false), //20 - SteamApp::DOD => Engine::GoldSrc(false), //30 + SteamApp::CS => Engine::GoldSrc(false), //10 + SteamApp::TFC => Engine::GoldSrc(false), //20 + SteamApp::DOD => Engine::GoldSrc(false), //30 SteamApp::CSCZ => Engine::GoldSrc(false), //80 SteamApp::CSS => Engine::new_source(240), SteamApp::DODS => Engine::new_source(300), @@ -282,7 +294,8 @@ impl SteamApp { } /// Engine type. -#[derive(Eq, PartialEq, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Engine { /// A Source game, the argument represents the possible steam app ids, if its **None**, let /// the query find it, if its **Some**, the query fails if the response id is not the first @@ -290,7 +303,7 @@ pub enum Engine { Source(Option<(u32, Option)>), /// A GoldSrc game, the argument indicates whether to enforce /// requesting the obsolete A2S_INFO response or not. - GoldSrc(bool) + GoldSrc(bool), } impl Engine { @@ -306,7 +319,7 @@ impl Engine { /// What data to gather, purely used only with the query function. pub struct GatheringSettings { pub players: bool, - pub rules: bool + pub rules: bool, } impl Default for GatheringSettings { @@ -314,7 +327,7 @@ impl Default for GatheringSettings { fn default() -> Self { Self { players: true, - rules: true + rules: true, } } } @@ -322,19 +335,23 @@ impl Default for GatheringSettings { /// Generic response types that are used by many games, they are the protocol ones, but without the /// unnecessary bits (example: the **The Ship**-only fields). pub mod game { - use std::collections::HashMap; - use crate::protocols::valve::types::get_optional_extracted_data; use super::{Server, ServerPlayer}; + use crate::protocols::valve::types::get_optional_extracted_data; + use std::collections::HashMap; + + #[cfg(feature = "serde")] + use serde::{Deserialize, Serialize}; /// A player's details. - #[derive(Debug)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct Player { /// Player's name. pub name: String, /// Player's score. pub score: u32, /// How long a player has been in the server (seconds). - pub duration: f32 + pub duration: f32, } impl Player { @@ -342,13 +359,14 @@ pub mod game { Self { name: player.name.clone(), score: player.score, - duration: player.duration + duration: player.duration, } } } /// The query response. - #[derive(Debug)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq)] pub struct Response { /// Protocol used by the server. pub protocol: u8, @@ -387,12 +405,13 @@ pub mod game { /// Keywords that describe the server according to it. pub keywords: Option, /// Server's rules. - pub rules: HashMap + pub rules: HashMap, } impl Response { pub fn new_from_valve_response(response: super::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); + let (port, steam_id, tv_port, tv_name, keywords) = + get_optional_extracted_data(response.info.extra_data); Self { protocol: response.info.protocol, @@ -401,7 +420,12 @@ pub mod game { game: response.info.game, appid: response.info.appid, players_online: response.info.players_online, - players_details: response.players.unwrap_or_default().iter().map(Player::from_valve_response).collect(), + players_details: response + .players + .unwrap_or_default() + .iter() + .map(Player::from_valve_response) + .collect(), players_maximum: response.info.players_maximum, players_bots: response.info.players_bots, server_type: response.info.server_type, @@ -413,7 +437,7 @@ pub mod game { tv_port, tv_name, keywords, - rules: response.rules.unwrap_or_default() + rules: response.rules.unwrap_or_default(), } } } From e023e13236c89d515b519b792c65fee7046c546a Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 13 Mar 2023 17:02:51 +0200 Subject: [PATCH 172/597] [Crate] Add serde feature to changelog and lib doc --- CHANGELOG.md | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62fc68a..6ad27d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Who knows what the future holds... ### Changes: Crate: - General optimizations thanks to [cargo clippy](https://github.com/rust-lang/rust-clippy) and [@cainthebest](https://github.com/cainthebest). +- Added feature `serde` which enables json serialization/deserialization for all types (by [@cainthebest](https://github.com/cainthebest)). Protocols: - GameSpy 1: Add key `admin` as a possible variable for `admin_name`. diff --git a/src/lib.rs b/src/lib.rs index 9ac3aa3..0dbd300 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ //! Enabled by default: None //! //! `no_games` - disables the included games support. +//! `serde` - enables json serialization/deserialization for all types pub mod errors; #[cfg(not(feature = "no_games"))] From 1b13d398567636f8f779f3d6d4b58fde0827bbd1 Mon Sep 17 00:00:00 2001 From: Cain <75994858+cainthebest@users.noreply.github.com> Date: Tue, 14 Mar 2023 09:31:37 +0100 Subject: [PATCH 173/597] [Crate] Add formatting (#22) * chore: add standard for formatting * chore: manually tidy up imports and format * chore: remove vscode and add to gitignore * chore: alphabetically order and fix * chore: format * chore: fix format issue with payload * chore: format as merge had unformatted code * [format] Fix comments, change max width and binop operator --------- Co-authored-by: CosminPerRam --- .gitignore | 1 + .rustfmt.toml | 72 ++ examples/master_querant.rs | 268 +++-- examples/minecraft.rs | 24 +- examples/tf2.rs | 20 +- src/bufferer.rs | 77 +- src/errors.rs | 11 +- src/games/aliens.rs | 25 +- src/games/aoc.rs | 15 +- src/games/arma2oa.rs | 25 +- src/games/ase.rs | 25 +- src/games/asrd.rs | 25 +- src/games/avorion.rs | 15 +- src/games/bat1944.rs | 17 +- src/games/bb2.rs | 15 +- src/games/bf1942.rs | 7 +- src/games/bm.rs | 15 +- src/games/bo.rs | 15 +- src/games/ccure.rs | 15 +- src/games/cosu.rs | 15 +- src/games/cs.rs | 25 +- src/games/cscz.rs | 25 +- src/games/csgo.rs | 25 +- src/games/css.rs | 15 +- src/games/dod.rs | 25 +- src/games/dods.rs | 15 +- src/games/doi.rs | 25 +- src/games/dst.rs | 15 +- src/games/gm.rs | 15 +- src/games/hl2dm.rs | 15 +- src/games/hldms.rs | 25 +- src/games/ins.rs | 25 +- src/games/insmic.rs | 25 +- src/games/inss.rs | 25 +- src/games/l4d.rs | 15 +- src/games/l4d2.rs | 15 +- src/games/mc.rs | 95 +- src/games/mod.rs | 185 ++-- src/games/ohd.rs | 15 +- src/games/onset.rs | 15 +- src/games/pz.rs | 15 +- src/games/ror2.rs | 15 +- src/games/rust.rs | 25 +- src/games/sc.rs | 25 +- src/games/sdtd.rs | 25 +- src/games/ss.rs | 7 +- src/games/tf.rs | 25 +- src/games/tf2.rs | 25 +- src/games/tfc.rs | 25 +- src/games/ts.rs | 199 ++-- src/games/unturned.rs | 25 +- src/games/ut.rs | 7 +- src/games/vr.rs | 15 +- src/protocols/gamespy/mod.rs | 3 +- src/protocols/gamespy/protocol/mod.rs | 1 - src/protocols/gamespy/protocol/one.rs | 128 ++- src/protocols/minecraft/mod.rs | 17 +- src/protocols/minecraft/protocol/bedrock.rs | 199 ++-- src/protocols/minecraft/protocol/java.rs | 271 ++--- .../minecraft/protocol/legacy_bv1_8.rs | 133 ++- .../minecraft/protocol/legacy_v1_4.rs | 142 ++- .../minecraft/protocol/legacy_v1_6.rs | 198 ++-- src/protocols/minecraft/protocol/mod.rs | 150 +-- src/protocols/minecraft/types.rs | 409 ++++---- src/protocols/mod.rs | 28 +- src/protocols/types.rs | 24 +- src/protocols/valve/mod.rs | 15 +- src/protocols/valve/protocol.rs | 967 ++++++++++-------- src/protocols/valve/types.rs | 885 ++++++++-------- src/socket.rs | 333 +++--- src/utils.rs | 80 +- 71 files changed, 3165 insertions(+), 2593 deletions(-) create mode 100644 .rustfmt.toml diff --git a/.gitignore b/.gitignore index 23f7216..e285a7a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,6 @@ Cargo.lock # Others .idea/ .venv/ +.vscode/ test_everything.py diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..df854d3 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,72 @@ +attr_fn_like_width = 70 +array_width = 60 +binop_separator = "Front" +blank_lines_lower_bound = 0 +blank_lines_upper_bound = 1 +brace_style = "PreferSameLine" +chain_width = 60 +color = "Auto" +combine_control_expr = false +comment_width = 80 +condense_wildcard_suffixes = true +control_brace_style = "AlwaysSameLine" +disable_all_formatting = false +doc_comment_code_block_width = 100 +edition = "2021" +emit_mode = "Files" +empty_item_single_line = true +error_on_line_overflow = false +error_on_unformatted = false +fn_call_width = 60 +fn_params_layout = "Tall" +fn_single_line = true +force_explicit_abi = true +force_multiline_blocks = true +format_generated_files = true +format_macro_bodies = true +format_strings = true +group_imports = "Preserve" +hard_tabs = false +hide_parse_errors = false +hex_literal_case = "Preserve" +ignore = [] +indent_style = "Block" +imports_granularity = "Preserve" +imports_indent = "Block" +imports_layout = "HorizontalVertical" +inline_attribute_width = 0 +make_backup = false +match_arm_blocks = true +match_arm_leading_pipes = "Never" +match_block_trailing_comma = false +max_width = 120 +merge_derives = true +newline_style = "Auto" +normalize_comments = true +normalize_doc_attributes = false +overflow_delimited_expr = false +reorder_impl_items = false +reorder_imports = true +reorder_modules = true +required_version = "1.5.2" +short_array_element_width_threshold = 10 +single_line_if_else_max_width = 50 +skip_children = false +space_after_colon = true +space_before_colon = false +spaces_around_ranges = true +struct_field_align_threshold = 0 +struct_lit_single_line = true +struct_lit_width = 18 +struct_variant_width = 35 +tab_spaces = 4 +trailing_comma = "Vertical" +trailing_semicolon = true +type_punctuation_density = "Wide" +unstable_features = false +use_field_init_shorthand = false +use_small_heuristics = "Default" +use_try_shorthand = true +version = "Two" +where_single_line = true +wrap_comments = true diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 501c584..a7052f5 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,96 +1,172 @@ - -use std::env; -use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bf1942, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, ss, tf, tf2, tfc, ts, unturned, ut, vr}; -use gamedig::protocols::minecraft::LegacyGroup; -use gamedig::protocols::valve; -use gamedig::protocols::valve::Engine; -use gamedig::protocols::gamespy; - -fn main() -> GDResult<()> { - let args: Vec = env::args().collect(); - - if args.len() == 1 || args[1] == "help".to_string() { - println!("Usage: "); - println!(" - any game, example: tf2"); - println!(" - an ip, example: 192.168.0.0"); - println!(" - an port, optional, example: 27015"); - return Ok(()); - } else if args.len() < 3 { - println!("Minimum number of arguments: 3, try 'help' to see the details."); - return Ok(()); - } - - let ip = args[2].as_str(); - let port = match args.len() == 4 { - false => { - if args[1].starts_with("_") { - panic!("The port must be specified with an anonymous query.") - } - - None - }, - true => Some(args[3].parse::().expect("Invalid port!")) - }; - - match args[1].as_str() { - "aliens" => println!("{:#?}", aliens::query(ip, port)?), - "asrd" => println!("{:#?}", asrd::query(ip, port)?), - "csgo" => println!("{:#?}", csgo::query(ip, port)?), - "css" => println!("{:#?}", css::query(ip, port)?), - "dods" => println!("{:#?}", dods::query(ip, port)?), - "gm" => println!("{:#?}", gm::query(ip, port)?), - "hl2dm" => println!("{:#?}", hl2dm::query(ip, port)?), - "tf2" => println!("{:#?}", tf2::query(ip, port)?), - "insmic" => println!("{:#?}", insmic::query(ip, port)?), - "ins" => println!("{:#?}", ins::query(ip, port)?), - "inss" => println!("{:#?}", inss::query(ip, port)?), - "l4d" => println!("{:#?}", l4d::query(ip, port)?), - "l4d2" => println!("{:#?}", l4d2::query(ip, port)?), - "ts" => println!("{:#?}", ts::query(ip, port)?), - "cscz" => println!("{:#?}", cscz::query(ip, port)?), - "dod" => println!("{:#?}", dod::query(ip, port)?), - "_src" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::Source(None), None, None)?), - "_gld" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::GoldSrc(false), None, None)?), - "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), Engine::GoldSrc(true), None, None)?), - "mc" => println!("{:#?}", mc::query(ip, port)?), - "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), - "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), - "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), - "mc_legacy_vb1_8" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::VB1_8, ip, port)?), - "mc_legacy_v1_4" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::V1_4, ip, port)?), - "mc_legacy_v1_6" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::V1_6, ip, port)?), - "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), - "ase" => println!("{:#?}", ase::query(ip, port)?), - "unturned" => println!("{:#?}", unturned::query(ip, port)?), - "tf" => println!("{:#?}", tf::query(ip, port)?), - "tfc" => println!("{:#?}", tfc::query(ip, port)?), - "sc" => println!("{:#?}", sc::query(ip, port)?), - "rust" => println!("{:#?}", rust::query(ip, port)?), - "cs" => println!("{:#?}", cs::query(ip, port)?), - "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), - "doi" => println!("{:#?}", doi::query(ip, port)?), - "hldms" => println!("{:#?}", hldms::query(ip, port)?), - "ror2" => println!("{:#?}", ror2::query(ip, port)?), - "bat1944" => println!("{:#?}", bat1944::query(ip, port)?), - "bm" => println!("{:#?}", bm::query(ip, port)?), - "pz" => println!("{:#?}", pz::query(ip, port)?), - "aoc" => println!("{:#?}", aoc::query(ip, port)?), - "dst" => println!("{:#?}", dst::query(ip, port)?), - "cosu" => println!("{:#?}", cosu::query(ip, port)?), - "onset" => println!("{:#?}", onset::query(ip, port)?), - "ccure" => println!("{:#?}", ccure::query(ip, port)?), - "bo" => println!("{:#?}", bo::query(ip, port)?), - "bb2" => println!("{:#?}", bb2::query(ip, port)?), - "avorion" => println!("{:#?}", avorion::query(ip, port)?), - "ohd" => println!("{:#?}", ohd::query(ip, port)?), - "vr" => println!("{:#?}", vr::query(ip, port)?), - "_gamespy1" => println!("{:#?}", gamespy::one::query(ip, port.unwrap(), None)), - "_gamespy1_vars" => println!("{:#?}", gamespy::one::query_vars(ip, port.unwrap(), None)), - "ut" => println!("{:#?}", ut::query(ip, port)), - "bf1942" => println!("{:#?}", bf1942::query(ip, port)), - "ss" => println!("{:#?}", ss::query(ip, port)), - _ => panic!("Undefined game: {}", args[1]) - }; - - Ok(()) -} +use gamedig::protocols::gamespy; +use gamedig::protocols::minecraft::LegacyGroup; +use gamedig::protocols::valve; +use gamedig::protocols::valve::Engine; +use gamedig::{ + aliens, + aoc, + arma2oa, + ase, + asrd, + avorion, + bat1944, + bb2, + bf1942, + bm, + bo, + ccure, + cosu, + cs, + cscz, + csgo, + css, + dod, + dods, + doi, + dst, + gm, + hl2dm, + hldms, + ins, + insmic, + inss, + l4d, + l4d2, + mc, + ohd, + onset, + pz, + ror2, + rust, + sc, + sdtd, + ss, + tf, + tf2, + tfc, + ts, + unturned, + ut, + vr, + GDResult, +}; +use std::env; + +fn main() -> GDResult<()> { + let args: Vec = env::args().collect(); + + if args.len() == 1 || args[1] == "help".to_string() { + println!("Usage: "); + println!(" - any game, example: tf2"); + println!(" - an ip, example: 192.168.0.0"); + println!(" - an port, optional, example: 27015"); + return Ok(()); + } else if args.len() < 3 { + println!("Minimum number of arguments: 3, try 'help' to see the details."); + return Ok(()); + } + + let ip = args[2].as_str(); + let port = match args.len() == 4 { + false => { + if args[1].starts_with("_") { + panic!("The port must be specified with an anonymous query.") + } + + None + } + true => Some(args[3].parse::().expect("Invalid port!")), + }; + + match args[1].as_str() { + "aliens" => println!("{:#?}", aliens::query(ip, port)?), + "asrd" => println!("{:#?}", asrd::query(ip, port)?), + "csgo" => println!("{:#?}", csgo::query(ip, port)?), + "css" => println!("{:#?}", css::query(ip, port)?), + "dods" => println!("{:#?}", dods::query(ip, port)?), + "gm" => println!("{:#?}", gm::query(ip, port)?), + "hl2dm" => println!("{:#?}", hl2dm::query(ip, port)?), + "tf2" => println!("{:#?}", tf2::query(ip, port)?), + "insmic" => println!("{:#?}", insmic::query(ip, port)?), + "ins" => println!("{:#?}", ins::query(ip, port)?), + "inss" => println!("{:#?}", inss::query(ip, port)?), + "l4d" => println!("{:#?}", l4d::query(ip, port)?), + "l4d2" => println!("{:#?}", l4d2::query(ip, port)?), + "ts" => println!("{:#?}", ts::query(ip, port)?), + "cscz" => println!("{:#?}", cscz::query(ip, port)?), + "dod" => println!("{:#?}", dod::query(ip, port)?), + "_src" => { + println!( + "{:#?}", + valve::query(ip, port.unwrap(), Engine::Source(None), None, None)? + ) + } + "_gld" => { + println!( + "{:#?}", + valve::query(ip, port.unwrap(), Engine::GoldSrc(false), None, None)? + ) + } + "_gld_f" => { + println!( + "{:#?}", + valve::query(ip, port.unwrap(), Engine::GoldSrc(true), None, None)? + ) + } + "mc" => println!("{:#?}", mc::query(ip, port)?), + "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), + "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), + "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), + "mc_legacy_vb1_8" => { + println!( + "{:#?}", + mc::query_legacy_specific(LegacyGroup::VB1_8, ip, port)? + ) + } + "mc_legacy_v1_4" => { + println!( + "{:#?}", + mc::query_legacy_specific(LegacyGroup::V1_4, ip, port)? + ) + } + "mc_legacy_v1_6" => { + println!( + "{:#?}", + mc::query_legacy_specific(LegacyGroup::V1_6, ip, port)? + ) + } + "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), + "ase" => println!("{:#?}", ase::query(ip, port)?), + "unturned" => println!("{:#?}", unturned::query(ip, port)?), + "tf" => println!("{:#?}", tf::query(ip, port)?), + "tfc" => println!("{:#?}", tfc::query(ip, port)?), + "sc" => println!("{:#?}", sc::query(ip, port)?), + "rust" => println!("{:#?}", rust::query(ip, port)?), + "cs" => println!("{:#?}", cs::query(ip, port)?), + "arma2oa" => println!("{:#?}", arma2oa::query(ip, port)?), + "doi" => println!("{:#?}", doi::query(ip, port)?), + "hldms" => println!("{:#?}", hldms::query(ip, port)?), + "ror2" => println!("{:#?}", ror2::query(ip, port)?), + "bat1944" => println!("{:#?}", bat1944::query(ip, port)?), + "bm" => println!("{:#?}", bm::query(ip, port)?), + "pz" => println!("{:#?}", pz::query(ip, port)?), + "aoc" => println!("{:#?}", aoc::query(ip, port)?), + "dst" => println!("{:#?}", dst::query(ip, port)?), + "cosu" => println!("{:#?}", cosu::query(ip, port)?), + "onset" => println!("{:#?}", onset::query(ip, port)?), + "ccure" => println!("{:#?}", ccure::query(ip, port)?), + "bo" => println!("{:#?}", bo::query(ip, port)?), + "bb2" => println!("{:#?}", bb2::query(ip, port)?), + "avorion" => println!("{:#?}", avorion::query(ip, port)?), + "ohd" => println!("{:#?}", ohd::query(ip, port)?), + "vr" => println!("{:#?}", vr::query(ip, port)?), + "_gamespy1" => println!("{:#?}", gamespy::one::query(ip, port.unwrap(), None)), + "_gamespy1_vars" => println!("{:#?}", gamespy::one::query_vars(ip, port.unwrap(), None)), + "ut" => println!("{:#?}", ut::query(ip, port)), + "bf1942" => println!("{:#?}", bf1942::query(ip, port)), + "ss" => println!("{:#?}", ss::query(ip, port)), + _ => panic!("Undefined game: {}", args[1]), + }; + + Ok(()) +} diff --git a/examples/minecraft.rs b/examples/minecraft.rs index ad1b98e..a61809a 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -1,12 +1,12 @@ - -use gamedig::games::mc; - -fn main() { - //or Some(), None is the default protocol port (which is 25565 for java and 19132 for bedrock) - let response = mc::query("127.0.0.1", None); - - match response { - Err(error) => println!("Couldn't query, error: {}", error), - Ok(r) => println!("{:#?}", r) - } -} +use gamedig::games::mc; + +fn main() { + // or Some(), None is the default protocol port (which is 25565 for java + // and 19132 for bedrock) + let response = mc::query("127.0.0.1", None); + + match response { + Err(error) => println!("Couldn't query, error: {}", error), + Ok(r) => println!("{:#?}", r), + } +} diff --git a/examples/tf2.rs b/examples/tf2.rs index 058fde8..a4167d0 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -1,10 +1,10 @@ - -use gamedig::games::tf2; - -fn main() { - let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port (which is 27015) - match response { // Result type, must check what it is... - Err(error) => println!("Couldn't query, error: {}", error), - Ok(r) => println!("{:#?}", r) - } -} +use gamedig::games::tf2; + +fn main() { + let response = tf2::query("127.0.0.1", None); // or Some(27015), None is the default protocol port (which is 27015) + match response { + // Result type, must check what it is... + Err(error) => println!("Couldn't query, error: {}", error), + Ok(r) => println!("{:#?}", r), + } +} diff --git a/src/bufferer.rs b/src/bufferer.rs index 30c01de..00fae6b 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -1,15 +1,19 @@ -use crate::GDResult; -use crate::GDError::{PacketBad, PacketUnderflow}; -use byteorder::{ByteOrder, LittleEndian, BigEndian}; +use crate::{ + GDError::{PacketBad, PacketUnderflow}, + GDResult, +}; + +use byteorder::{BigEndian, ByteOrder, LittleEndian}; pub enum Endianess { - Little, Big + Little, + Big, } pub struct Bufferer { data: Vec, endianess: Endianess, - position: usize + position: usize, } impl Bufferer { @@ -17,13 +21,11 @@ impl Bufferer { Bufferer { data: data.to_vec(), endianess, - position: 0 + position: 0, } } - fn check_size(&self, by: usize) -> bool { - by > self.remaining_length() - } + fn check_size(&self, by: usize) -> bool { by > self.remaining_length() } pub fn get_u8(&mut self) -> GDResult { if self.check_size(1) { @@ -70,7 +72,7 @@ impl Bufferer { let value = match self.endianess { Endianess::Little => LittleEndian::read_f32(self.remaining_data()), - Endianess::Big => BigEndian::read_f32(self.remaining_data()) + Endianess::Big => BigEndian::read_f32(self.remaining_data()), }; self.move_position_ahead(4); @@ -84,7 +86,7 @@ impl Bufferer { let value = match self.endianess { Endianess::Little => LittleEndian::read_u64(self.remaining_data()), - Endianess::Big => BigEndian::read_u64(self.remaining_data()) + Endianess::Big => BigEndian::read_u64(self.remaining_data()), }; self.move_position_ahead(8); @@ -97,10 +99,10 @@ impl Bufferer { return Err(PacketUnderflow); } - let first_null_position = sub_buf.iter().position(|&x| x == 0) - .ok_or(PacketBad)?; - let value = std::str::from_utf8(&sub_buf[..first_null_position]) - .map_err(|_| PacketBad)?.to_string(); + let first_null_position = sub_buf.iter().position(|&x| x == 0).ok_or(PacketBad)?; + let value = std::str::from_utf8(&sub_buf[.. first_null_position]) + .map_err(|_| PacketBad)? + .to_string(); self.move_position_ahead(value.len() + 1); Ok(value) @@ -113,7 +115,8 @@ impl Bufferer { } let value = std::str::from_utf8(sub_buf) - .map_err(|_| PacketBad)?.to_string(); + .map_err(|_| PacketBad)? + .to_string(); self.move_position_ahead(value.len()); Ok(value) @@ -127,9 +130,11 @@ impl Bufferer { let paired_buf: Vec = sub_buf .chunks_exact(2) - .map(|pair| match self.endianess { - Endianess::Little => LittleEndian::read_u16(pair), - Endianess::Big => BigEndian::read_u16(pair), + .map(|pair| { + match self.endianess { + Endianess::Little => LittleEndian::read_u16(pair), + Endianess::Big => BigEndian::read_u16(pair), + } }) .collect(); @@ -139,29 +144,17 @@ impl Bufferer { Ok(value) } - pub fn move_position_ahead(&mut self, by: usize) { - self.position += by; - } + pub fn move_position_ahead(&mut self, by: usize) { self.position += by; } - pub fn move_position_backward(&mut self, by: usize) { - self.position -= by; - } + pub fn move_position_backward(&mut self, by: usize) { self.position -= by; } - pub fn data_length(&self) -> usize { - self.data.len() - } + pub fn data_length(&self) -> usize { self.data.len() } - pub fn remaining_data(&self) -> &[u8] { - &self.data[self.position..] - } + pub fn remaining_data(&self) -> &[u8] { &self.data[self.position ..] } - pub fn remaining_data_vec(&self) -> Vec { - self.remaining_data().to_vec() - } + pub fn remaining_data_vec(&self) -> Vec { self.remaining_data().to_vec() } - pub fn remaining_length(&self) -> usize { - self.data_length() - self.position - } + pub fn remaining_length(&self) -> usize { self.data_length() - self.position } pub fn as_endianess(&self, endianess: Endianess) -> Self { Bufferer { @@ -277,7 +270,10 @@ mod tests { #[test] fn get_string_utf16_le() { - let mut buffer = Bufferer::new_with_data(Endianess::Little, &[0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00]); + let mut buffer = Bufferer::new_with_data( + Endianess::Little, + &[0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00], + ); assert_eq!(buffer.get_string_utf16().unwrap(), "Hello"); assert_eq!(buffer.remaining_length(), 0); @@ -286,7 +282,10 @@ mod tests { #[test] fn get_string_utf16_be() { - let mut buffer = Bufferer::new_with_data(Endianess::Big, &[0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f]); + let mut buffer = Bufferer::new_with_data( + Endianess::Big, + &[0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f], + ); assert_eq!(buffer.get_string_utf16().unwrap(), "Hello"); assert_eq!(buffer.remaining_length(), 0); diff --git a/src/errors.rs b/src/errors.rs index a647123..80f04e6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,7 @@ -//! The library's possible errors. -use std::fmt; -use std::{error::Error, fmt::Formatter}; +use std::{ + error::Error, + fmt::{self, Formatter}, +}; /// Result of Type and GDError. pub type GDResult = Result; @@ -43,9 +44,7 @@ pub enum GDError { impl Error for GDError {} impl fmt::Display for GDError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } #[cfg(test)] diff --git a/src/games/aliens.rs b/src/games/aliens.rs index dafc7b9..2144186 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ALIENS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::ALIENS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/aoc.rs b/src/games/aoc.rs index 29d913b..c02d8e6 100644 --- a/src/games/aoc.rs +++ b/src/games/aoc.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::AOC.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::AOC.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/arma2oa.rs b/src/games/arma2oa.rs index f54123c..4c01c06 100644 --- a/src/games/arma2oa.rs +++ b/src/games/arma2oa.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(2304), SteamApp::ARMA2OA.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(2304), + SteamApp::ARMA2OA.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ase.rs b/src/games/ase.rs index 09ff37f..7b05fa3 100644 --- a/src/games/ase.rs +++ b/src/games/ase.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ASE.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::ASE.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 8f2808e..57d6a70 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::ASRD.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::ASRD.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/avorion.rs b/src/games/avorion.rs index 804389a..b0c5ee2 100644 --- a/src/games/avorion.rs +++ b/src/games/avorion.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27020), SteamApp::AVORION.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27020), + SteamApp::AVORION.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs index a401819..1336176 100644 --- a/src/games/bat1944.rs +++ b/src/games/bat1944.rs @@ -1,10 +1,17 @@ -use crate::GDError::TypeParse; -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDError::TypeParse, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let mut valve_response = valve::query(address, port.unwrap_or(7780), SteamApp::BAT1944.as_engine(), None, None)?; + let mut valve_response = valve::query( + address, + port.unwrap_or(7780), + SteamApp::BAT1944.as_engine(), + None, + None, + )?; if let Some(rules) = &mut valve_response.rules { if let Some(bat_max_players) = rules.get("bat_max_players_i") { diff --git a/src/games/bb2.rs b/src/games/bb2.rs index 97bec4b..7ccb040 100644 --- a/src/games/bb2.rs +++ b/src/games/bb2.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::BB2.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::BB2.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bf1942.rs b/src/games/bf1942.rs index 12d530b..b813694 100644 --- a/src/games/bf1942.rs +++ b/src/games/bf1942.rs @@ -1,6 +1,7 @@ -use crate::GDResult; -use crate::protocols::gamespy; -use crate::protocols::gamespy::Response; +use crate::{ + protocols::gamespy::{self, Response}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(23000), None) diff --git a/src/games/bm.rs b/src/games/bm.rs index 3423c40..7f99a96 100644 --- a/src/games/bm.rs +++ b/src/games/bm.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::BM.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::BM.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/bo.rs b/src/games/bo.rs index 88dcfc4..7576f81 100644 --- a/src/games/bo.rs +++ b/src/games/bo.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::BO.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::BO.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ccure.rs b/src/games/ccure.rs index 357f5e3..23713bf 100644 --- a/src/games/ccure.rs +++ b/src/games/ccure.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CCURE.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CCURE.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cosu.rs b/src/games/cosu.rs index 48e1595..079e7c1 100644 --- a/src/games/cosu.rs +++ b/src/games/cosu.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27004), SteamApp::COSU.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27004), + SteamApp::COSU.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/cs.rs b/src/games/cs.rs index 81a18a0..1a926bc 100644 --- a/src/games/cs.rs +++ b/src/games/cs.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/cscz.rs b/src/games/cscz.rs index ac8b199..19b83dc 100644 --- a/src/games/cscz.rs +++ b/src/games/cscz.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSCZ.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CSCZ.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/csgo.rs b/src/games/csgo.rs index f65944a..68dfef2 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSGO.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CSGO.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/css.rs b/src/games/css.rs index 01a0ae3..8d29825 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::CSS.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::CSS.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/dod.rs b/src/games/dod.rs index 56687bf..1eeb355 100644 --- a/src/games/dod.rs +++ b/src/games/dod.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DOD.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::DOD.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/dods.rs b/src/games/dods.rs index 96be872..f0f6394 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DODS.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::DODS.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/doi.rs b/src/games/doi.rs index bfee0f3..f314a5c 100644 --- a/src/games/doi.rs +++ b/src/games/doi.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::DOI.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::DOI.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/dst.rs b/src/games/dst.rs index 256a1b7..218a461 100644 --- a/src/games/dst.rs +++ b/src/games/dst.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::DST.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::DST.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/gm.rs b/src/games/gm.rs index 6fec055..7fe0e70 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::GM.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::GM.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index c885128..ecbf631 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::HL2DM.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::HL2DM.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/hldms.rs b/src/games/hldms.rs index 325fd95..5cbad74 100644 --- a/src/games/hldms.rs +++ b/src/games/hldms.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::HLDMS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::HLDMS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ins.rs b/src/games/ins.rs index 61f7759..bf4e9c4 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::INS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::INS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 82ac53a..8f3bb33 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::INSMIC.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::INSMIC.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/inss.rs b/src/games/inss.rs index ee53a45..ad7f73e 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27131), SteamApp::INSS.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27131), + SteamApp::INSS.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 168f678..3e2d56d 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::L4D.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::L4D.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 09120cf..524b496 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::L4D2.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::L4D2.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/mc.rs b/src/games/mc.rs index 6172220..84f6f3e 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,48 +1,47 @@ -use crate::{GDError, GDResult}; -use crate::protocols::minecraft; -use crate::protocols::minecraft::{JavaResponse, LegacyGroup, BedrockResponse}; - -/// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &str, port: Option) -> GDResult { - if let Ok(response) = query_java(address, port) { - return Ok(response); - } - - if let Ok(response) = query_bedrock(address, port) { - return Ok(JavaResponse::from_bedrock_response(response)); - } - - if let Ok(response) = query_legacy(address, port) { - return Ok(response); - } - - Err(GDError::AutoQuery) -} - -/// Query a Java Server. -pub fn query_java(address: &str, port: Option) -> GDResult { - minecraft::query_java(address, port_or_java_default(port), None) -} - -/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: Option) -> GDResult { - minecraft::query_legacy(address, port_or_java_default(port), None) -} - -/// Query a specific (Java) Legacy Server. -pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { - minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) -} - -/// Query a Bedrock Server. -pub fn query_bedrock(address: &str, port: Option) -> GDResult { - minecraft::query_bedrock(address, port_or_bedrock_default(port), None) -} - -fn port_or_java_default(port: Option) -> u16 { - port.unwrap_or(25565) -} - -fn port_or_bedrock_default(port: Option) -> u16 { - port.unwrap_or(19132) -} +use crate::{ + protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup}, + GDError, + GDResult, +}; + +/// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy +/// (1.6 -> 1.4 -> Beta 1.8)). +pub fn query(address: &str, port: Option) -> GDResult { + if let Ok(response) = query_java(address, port) { + return Ok(response); + } + + if let Ok(response) = query_bedrock(address, port) { + return Ok(JavaResponse::from_bedrock_response(response)); + } + + if let Ok(response) = query_legacy(address, port) { + return Ok(response); + } + + Err(GDError::AutoQuery) +} + +/// Query a Java Server. +pub fn query_java(address: &str, port: Option) -> GDResult { + minecraft::query_java(address, port_or_java_default(port), None) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: Option) -> GDResult { + minecraft::query_legacy(address, port_or_java_default(port), None) +} + +/// Query a specific (Java) Legacy Server. +pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { + minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) +} + +/// Query a Bedrock Server. +pub fn query_bedrock(address: &str, port: Option) -> GDResult { + minecraft::query_bedrock(address, port_or_bedrock_default(port), None) +} + +fn port_or_java_default(port: Option) -> u16 { port.unwrap_or(25565) } + +fn port_or_bedrock_default(port: Option) -> u16 { port.unwrap_or(19132) } diff --git a/src/games/mod.rs b/src/games/mod.rs index 6530c83..a5f76a4 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1,93 +1,92 @@ - -//! Currently supported games. - -/// Team Fortress 2 -pub mod tf2; -/// The Ship -pub mod ts; -/// Counter-Strike: Global Offensive -pub mod csgo; -/// Counter-Strike: Source -pub mod css; -/// Day of Defeat: Source -pub mod dods; -/// Garry's Mod -pub mod gm; -/// Left 4 Dead -pub mod l4d; -/// Left 4 Dead 2 -pub mod l4d2; -/// Half-Life 2 Deathmatch -pub mod hl2dm; -/// Alien Swarm -pub mod aliens; -/// Alien Swarm: Reactive Drop -pub mod asrd; -/// Insurgency -pub mod ins; -/// Insurgency: Sandstorm -pub mod inss; -/// Insurgency: Modern Infantry Combat -pub mod insmic; -/// Counter Strike: Condition Zero -pub mod cscz; -/// Day of Defeat -pub mod dod; -/// Minecraft -pub mod mc; -/// 7 Days To Die -pub mod sdtd; -/// ARK: Survival Evolved -pub mod ase; -/// Unturned -pub mod unturned; -/// The Forest -pub mod tf; -/// Team Fortress Classic -pub mod tfc; -/// Sven Co-op -pub mod sc; -/// Rust -pub mod rust; -/// Counter-Strike -pub mod cs; -/// ARMA 2: Operation Arrowhead -pub mod arma2oa; -/// Day of Infamy -pub mod doi; -/// Half-Life Deathmatch: Source -pub mod hldms; -/// Risk of Rain 2 -pub mod ror2; -/// Battalion 1944 -pub mod bat1944; -/// Black Mesa -pub mod bm; -/// Project Zomboid -pub mod pz; -/// Age of Chivalry -pub mod aoc; -/// Don't Starve Together -pub mod dst; -/// Colony Survival -pub mod cosu; -/// Onset -pub mod onset; -/// Codename CURE -pub mod ccure; -/// Ballistic Overkill -pub mod bo; -/// BrainBread 2 -pub mod bb2; -/// Avorion -pub mod avorion; -/// Operation: Harsh Doorstop -pub mod ohd; -/// V Rising -pub mod vr; -/// Unreal Tournament -pub mod ut; -/// Battlefield 1942 -pub mod bf1942; -/// Serious Sam -pub mod ss; +//! Currently supported games. + +/// Alien Swarm +pub mod aliens; +/// Age of Chivalry +pub mod aoc; +/// ARMA 2: Operation Arrowhead +pub mod arma2oa; +/// ARK: Survival Evolved +pub mod ase; +/// Alien Swarm: Reactive Drop +pub mod asrd; +/// Avorion +pub mod avorion; +/// Battalion 1944 +pub mod bat1944; +/// BrainBread 2 +pub mod bb2; +/// Battlefield 1942 +pub mod bf1942; +/// Black Mesa +pub mod bm; +/// Ballistic Overkill +pub mod bo; +/// Codename CURE +pub mod ccure; +/// Colony Survival +pub mod cosu; +/// Counter-Strike +pub mod cs; +/// Counter Strike: Condition Zero +pub mod cscz; +/// Counter-Strike: Global Offensive +pub mod csgo; +/// Counter-Strike: Source +pub mod css; +/// Day of Defeat +pub mod dod; +/// Day of Defeat: Source +pub mod dods; +/// Day of Infamy +pub mod doi; +/// Don't Starve Together +pub mod dst; +/// Garry's Mod +pub mod gm; +/// Half-Life 2 Deathmatch +pub mod hl2dm; +/// Half-Life Deathmatch: Source +pub mod hldms; +/// Insurgency +pub mod ins; +/// Insurgency: Modern Infantry Combat +pub mod insmic; +/// Insurgency: Sandstorm +pub mod inss; +/// Left 4 Dead +pub mod l4d; +/// Left 4 Dead 2 +pub mod l4d2; +/// Minecraft +pub mod mc; +/// Operation: Harsh Doorstop +pub mod ohd; +/// Onset +pub mod onset; +/// Project Zomboid +pub mod pz; +/// Risk of Rain 2 +pub mod ror2; +/// Rust +pub mod rust; +/// Sven Co-op +pub mod sc; +/// 7 Days To Die +pub mod sdtd; +/// Serious Sam +pub mod ss; +/// The Forest +pub mod tf; +/// Team Fortress 2 +pub mod tf2; +/// Team Fortress Classic +pub mod tfc; +/// The Ship +pub mod ts; +/// Unturned +pub mod unturned; +/// Unreal Tournament +pub mod ut; +/// V Rising +pub mod vr; diff --git a/src/games/ohd.rs b/src/games/ohd.rs index b024917..f12234e 100644 --- a/src/games/ohd.rs +++ b/src/games/ohd.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27005), SteamApp::OHD.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27005), + SteamApp::OHD.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/onset.rs b/src/games/onset.rs index ce58ba9..f73c073 100644 --- a/src/games/onset.rs +++ b/src/games/onset.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(7776), SteamApp::ONSET.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(7776), + SteamApp::ONSET.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/pz.rs b/src/games/pz.rs index 8fda807..14cdcfe 100644 --- a/src/games/pz.rs +++ b/src/games/pz.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(16261), SteamApp::PZ.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(16261), + SteamApp::PZ.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/ror2.rs b/src/games/ror2.rs index 019010a..e219b92 100644 --- a/src/games/ror2.rs +++ b/src/games/ror2.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::ROR2.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::ROR2.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/games/rust.rs b/src/games/rust.rs index cdd1c02..1482bfd 100644 --- a/src/games/rust.rs +++ b/src/games/rust.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::RUST.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::RUST.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/sc.rs b/src/games/sc.rs index ea8b029..a7b2215 100644 --- a/src/games/sc.rs +++ b/src/games/sc.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::SC.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::SC.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/sdtd.rs b/src/games/sdtd.rs index c618034..7dd5cc6 100644 --- a/src/games/sdtd.rs +++ b/src/games/sdtd.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(26900), SteamApp::SDTD.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(26900), + SteamApp::SDTD.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ss.rs b/src/games/ss.rs index 40e132f..77d5cd9 100644 --- a/src/games/ss.rs +++ b/src/games/ss.rs @@ -1,6 +1,7 @@ -use crate::GDResult; -use crate::protocols::gamespy; -use crate::protocols::gamespy::Response; +use crate::{ + protocols::gamespy::{self, Response}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(25601), None) diff --git a/src/games/tf.rs b/src/games/tf.rs index 5605c5f..dbf9b93 100644 --- a/src/games/tf.rs +++ b/src/games/tf.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::TF.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::TF.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/tf2.rs b/src/games/tf2.rs index ca83dc4..4920bd2 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TF2.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::TF2.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/tfc.rs b/src/games/tfc.rs index 69229cd..a85f97c 100644 --- a/src/games/tfc.rs +++ b/src/games/tfc.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TFC.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::TFC.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ts.rs b/src/games/ts.rs index 3a6daa7..4e6eb3c 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,93 +1,106 @@ -use std::collections::HashMap; -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{Server, ServerPlayer, get_optional_extracted_data, SteamApp}; - -#[cfg (feature = "serde")] -use serde::{Serialize, Deserialize}; - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct TheShipPlayer { - pub name: String, - pub score: u32, - pub duration: f32, - pub deaths: u32, - pub money: u32 -} - -impl TheShipPlayer { - pub fn new_from_valve_player(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration, - deaths: player.deaths.unwrap(), - money: player.money.unwrap() - } - } -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] -pub struct Response { - pub protocol: u8, - pub name: String, - pub map: String, - pub game: String, - pub players: u8, - pub players_details: Vec, - pub max_players: u8, - pub bots: u8, - pub server_type: Server, - pub has_password: bool, - pub vac_secured: bool, - pub version: String, - pub port: Option, - pub steam_id: Option, - pub tv_port: Option, - pub tv_name: Option, - pub keywords: Option, - pub rules: HashMap, - pub mode: u8, - pub witnesses: u8, - pub duration: u8 -} - -impl Response { - pub fn new_from_valve_response(response: valve::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); - - let the_unwrapped_ship = response.info.the_ship.unwrap(); - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - players: response.info.players_online, - players_details: response.players.unwrap().iter().map(TheShipPlayer::new_from_valve_player).collect(), - max_players: response.info.players_maximum, - bots: response.info.players_bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap(), - mode: the_unwrapped_ship.mode, - witnesses: the_unwrapped_ship.witnesses, - duration: the_unwrapped_ship.duration - } - } -} - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::TS.as_engine(), None, None)?; - - Ok(Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, get_optional_extracted_data, Server, ServerPlayer, SteamApp}, + GDResult, +}; + +use std::collections::HashMap; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct TheShipPlayer { + pub name: String, + pub score: u32, + pub duration: f32, + pub deaths: u32, + pub money: u32, +} + +impl TheShipPlayer { + pub fn new_from_valve_player(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration, + deaths: player.deaths.unwrap(), + money: player.money.unwrap(), + } + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: HashMap, + pub mode: u8, + pub witnesses: u8, + pub duration: u8, +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); + + let the_unwrapped_ship = response.info.the_ship.unwrap(); + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players_online, + players_details: response + .players + .unwrap() + .iter() + .map(TheShipPlayer::new_from_valve_player) + .collect(), + max_players: response.info.players_maximum, + bots: response.info.players_bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap(), + mode: the_unwrapped_ship.mode, + witnesses: the_unwrapped_ship.witnesses, + duration: the_unwrapped_ship.duration, + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::TS.as_engine(), + None, + None, + )?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/unturned.rs b/src/games/unturned.rs index b971dbf..c6e071f 100644 --- a/src/games/unturned.rs +++ b/src/games/unturned.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; - -pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27015), SteamApp::UNTURNED.as_engine(), None, None)?; - - Ok(game::Response::new_from_valve_response(valve_response)) -} +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = valve::query( + address, + port.unwrap_or(27015), + SteamApp::UNTURNED.as_engine(), + None, + None, + )?; + + Ok(game::Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/ut.rs b/src/games/ut.rs index a22fd79..a7f4ff1 100644 --- a/src/games/ut.rs +++ b/src/games/ut.rs @@ -1,6 +1,7 @@ -use crate::GDResult; -use crate::protocols::gamespy; -use crate::protocols::gamespy::Response; +use crate::{ + protocols::gamespy::{self, Response}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(7778), None) diff --git a/src/games/vr.rs b/src/games/vr.rs index 91d2105..b391f32 100644 --- a/src/games/vr.rs +++ b/src/games/vr.rs @@ -1,9 +1,16 @@ -use crate::GDResult; -use crate::protocols::valve; -use crate::protocols::valve::{game, SteamApp}; +use crate::{ + protocols::valve::{self, game, SteamApp}, + GDResult, +}; pub fn query(address: &str, port: Option) -> GDResult { - let valve_response = valve::query(address, port.unwrap_or(27016), SteamApp::VR.as_engine(), None, None)?; + let valve_response = valve::query( + address, + port.unwrap_or(27016), + SteamApp::VR.as_engine(), + None, + None, + )?; Ok(game::Response::new_from_valve_response(valve_response)) } diff --git a/src/protocols/gamespy/mod.rs b/src/protocols/gamespy/mod.rs index 8ed7a9b..3d98ab9 100644 --- a/src/protocols/gamespy/mod.rs +++ b/src/protocols/gamespy/mod.rs @@ -1,8 +1,7 @@ - /// The implementation. pub mod protocol; /// All types used by the implementation. pub mod types; -pub use types::*; pub use protocol::*; +pub use types::*; diff --git a/src/protocols/gamespy/protocol/mod.rs b/src/protocols/gamespy/protocol/mod.rs index afcffc5..9f32c0a 100644 --- a/src/protocols/gamespy/protocol/mod.rs +++ b/src/protocols/gamespy/protocol/mod.rs @@ -1,3 +1,2 @@ - /// GameSpy 1 pub mod one; diff --git a/src/protocols/gamespy/protocol/one.rs b/src/protocols/gamespy/protocol/one.rs index c553804..e348cec 100644 --- a/src/protocols/gamespy/protocol/one.rs +++ b/src/protocols/gamespy/protocol/one.rs @@ -1,11 +1,21 @@ -use std::collections::HashMap; -use crate::bufferer::{Bufferer, Endianess}; -use crate::{GDError, GDResult}; -use crate::protocols::gamespy::{Player, Response}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, UdpSocket}; +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + gamespy::{Player, Response}, + types::TimeoutSettings, + }, + socket::{Socket, UdpSocket}, + GDError, + GDResult, +}; -fn get_server_values(address: &str, port: u16, timeout_settings: Option) -> GDResult> { +use std::collections::HashMap; + +fn get_server_values( + address: &str, + port: u16, + timeout_settings: Option, +) -> GDResult> { let mut socket = UdpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -26,12 +36,12 @@ fn get_server_values(address: &str, port: u16, timeout_settings: Option = as_string.split('\\').map(str::to_string).collect(); - for i in 0..splited.len() / 2 { + for i in 0 .. splited.len() / 2 { let position = i * 2; let key = splited[position].clone(); let value = match splited.get(position + 1) { None => "".to_string(), - Some(v) => v.clone() + Some(v) => v.clone(), }; server_values.insert(key, value); @@ -42,7 +52,7 @@ fn get_server_values(address: &str, port: u16, timeout_settings: Option = qid.split('.').collect(); @@ -51,22 +61,22 @@ fn get_server_values(address: &str, port: u16, timeout_settings: Option (), 2 => part = split[1].parse().map_err(|_| GDError::TypeParse)?, - _ => Err(GDError::PacketBad)? //the queryid can't be splitted in more than 2 elements + _ => Err(GDError::PacketBad)?, /* the queryid can't be splitted in more than 2 + * elements */ }; } server_values.remove("queryid"); if received_query_id.is_some() && received_query_id != query_id { - return Err(GDError::PacketBad); //wrong query id! - } - else { + return Err(GDError::PacketBad); // wrong query id! + } else { received_query_id = query_id; } match parts.contains(&part) { true => Err(GDError::PacketBad)?, - false => parts.push(part) + false => parts.push(part), } } @@ -86,15 +96,12 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u let kind = split[0]; let id: usize = match split[1].parse() { Ok(v) => v, - Err(_) => return true + Err(_) => return true, }; let early_return = match kind { "team" | "player" | "ping" | "face" | "skin" | "mesh" | "frags" | "ngsecret" | "deaths" | "health" => false, - _x => { - //println!("UNKNOWN {id} {x} {value}"); - true - } + _x => true, // println!("UNKNOWN {id} {x} {value}"); }; if early_return { @@ -116,23 +123,48 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u let new_player = Player { name: match player_data.get("player") { Some(v) => v.clone(), - None => player_data.get("playername").ok_or(GDError::PacketBad)?.clone() + None => { + player_data + .get("playername") + .ok_or(GDError::PacketBad)? + .clone() + } }, - team: player_data.get("team").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, - ping: player_data.get("ping").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, + team: player_data + .get("team") + .ok_or(GDError::PacketBad)? + .trim() + .parse() + .map_err(|_| GDError::TypeParse)?, + ping: player_data + .get("ping") + .ok_or(GDError::PacketBad)? + .trim() + .parse() + .map_err(|_| GDError::TypeParse)?, face: player_data.get("face").ok_or(GDError::PacketBad)?.clone(), skin: player_data.get("skin").ok_or(GDError::PacketBad)?.clone(), mesh: player_data.get("mesh").ok_or(GDError::PacketBad)?.clone(), - frags: player_data.get("frags").ok_or(GDError::PacketBad)?.trim().parse().map_err(|_| GDError::TypeParse)?, + frags: player_data + .get("frags") + .ok_or(GDError::PacketBad)? + .trim() + .parse() + .map_err(|_| GDError::TypeParse)?, deaths: match player_data.get("deaths") { Some(v) => Some(v.trim().parse().map_err(|_| GDError::TypeParse)?), - None => None + None => None, }, health: match player_data.get("health") { Some(v) => Some(v.trim().parse().map_err(|_| GDError::TypeParse)?), - None => None + None => None, }, - secret: player_data.get("ngsecret").ok_or(GDError::PacketBad)?.to_lowercase().parse().map_err(|_| GDError::TypeParse)?, + secret: player_data + .get("ngsecret") + .ok_or(GDError::PacketBad)? + .to_lowercase() + .parse() + .map_err(|_| GDError::TypeParse)?, }; players.push(new_player); @@ -142,7 +174,10 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u } fn has_password(server_vars: &mut HashMap) -> GDResult { - let password_value = server_vars.remove("password").ok_or(GDError::PacketBad)?.to_lowercase(); + let password_value = server_vars + .remove("password") + .ok_or(GDError::PacketBad)? + .to_lowercase(); if let Ok(has) = password_value.parse::() { return Ok(has); @@ -153,17 +188,27 @@ fn has_password(server_vars: &mut HashMap) -> GDResult { Ok(as_numeral != 0) } -/// If there are parsing problems using the `query` function, you can directly get the server's values using this function. -pub fn query_vars(address: &str, port: u16, timeout_settings: Option) -> GDResult> { +/// If there are parsing problems using the `query` function, you can directly +/// get the server's values using this function. +pub fn query_vars( + address: &str, + port: u16, + timeout_settings: Option, +) -> GDResult> { get_server_values(address, port, timeout_settings) } /// Query a server by providing the address, the port and timeout settings. -/// Providing None to the timeout settings results in using the default values. (TimeoutSettings::[default](TimeoutSettings::default)). +/// Providing None to the timeout settings results in using the default values. +/// (TimeoutSettings::[default](TimeoutSettings::default)). pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { let mut server_vars = query_vars(address, port, timeout_settings)?; - let players_maximum = server_vars.remove("maxplayers").ok_or(GDError::PacketBad)?.parse().map_err(|_| GDError::TypeParse)?; + let players_maximum = server_vars + .remove("maxplayers") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::TypeParse)?; let players = extract_players(&mut server_vars, players_maximum)?; @@ -172,15 +217,26 @@ pub fn query(address: &str, port: u16, timeout_settings: Option map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, map_title: server_vars.remove("maptitle"), admin_contact: server_vars.remove("AdminEMail"), - admin_name: server_vars.remove("AdminName").or_else(|| server_vars.remove("admin")), + admin_name: server_vars + .remove("AdminName") + .or_else(|| server_vars.remove("admin")), has_password: has_password(&mut server_vars)?, game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, players_maximum, players_online: players.len(), - players_minimum: server_vars.remove("minplayers").unwrap_or_else(|| "0".to_string()).parse().map_err(|_| GDError::TypeParse)?, + players_minimum: server_vars + .remove("minplayers") + .unwrap_or_else(|| "0".to_string()) + .parse() + .map_err(|_| GDError::TypeParse)?, players, - tournament: server_vars.remove("tournament").unwrap_or_else(|| "true".to_string()).to_lowercase().parse().map_err(|_| GDError::TypeParse)?, - unused_entries: server_vars + tournament: server_vars + .remove("tournament") + .unwrap_or_else(|| "true".to_string()) + .to_lowercase() + .parse() + .map_err(|_| GDError::TypeParse)?, + unused_entries: server_vars, }) } diff --git a/src/protocols/minecraft/mod.rs b/src/protocols/minecraft/mod.rs index 8e0718c..da17668 100644 --- a/src/protocols/minecraft/mod.rs +++ b/src/protocols/minecraft/mod.rs @@ -1,9 +1,8 @@ - -/// The implementation. -pub mod protocol; -/// All types used by the implementation. -pub mod types; - -pub use protocol::*; -pub use types::*; -pub use protocol::*; +/// The implementation. +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use protocol::*; +pub use types::*; diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index c37c29e..51899f0 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -1,101 +1,98 @@ - -/* -This file has code that has been documented by the NodeJS GameDig library (MIT) from -https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js -*/ - -use crate::GDResult; -use crate::bufferer::{Bufferer, Endianess}; -use crate::GDError::{PacketBad, TypeParse}; -use crate::protocols::minecraft::{BedrockResponse, GameMode, Server}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, UdpSocket}; -use crate::utils::error_by_expected_size; - -pub struct Bedrock { - socket: UdpSocket -} - -impl Bedrock { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = UdpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send_status_request(&mut self) -> GDResult<()> { - self.socket.send(&[ - // Message ID, ID_UNCONNECTED_PING - 0x01, - // Nonce / timestamp - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - // Magic - 0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78, - // Client GUID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])?; - - Ok(()) - } - - fn get_info(&mut self) -> GDResult { - self.send_status_request()?; - - let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); - - if buffer.get_u8()? != 0x1c { - return Err(PacketBad); - } - - // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). - if buffer.get_u64()? != 9833440827789222417 { - return Err(PacketBad); - } - - // These 8 bytes are identical to the serverId string we receive in decimal below - buffer.move_position_ahead(8); - - // Verifying the magic value (as we need 16 bytes, cast to two u64 values) - if buffer.get_u64()? != 18374403896610127616 { - return Err(PacketBad); - } - - if buffer.get_u64()? != 8671175388723805693 { - return Err(PacketBad); - } - - let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize; - buffer.move_position_ahead(2); - error_by_expected_size(remaining_length, buffer.remaining_length())?; - - let binding = buffer.get_string_utf8_unended()?; - let status: Vec<&str> = binding.split(';').collect(); - - // We must have at least 6 values - if status.len() < 6 { - return Err(PacketBad); - } - - Ok(BedrockResponse { - edition: status[0].to_string(), - name: status[1].to_string(), - version_name: status[3].to_string(), - version_protocol: status[2].to_string(), - players_maximum: status[5].parse().map_err(|_| TypeParse)?, - players_online: status[4].parse().map_err(|_| TypeParse)?, - id: status.get(6).map(|v| v.to_string()), - map: status.get(7).map(|v| v.to_string()), - game_mode: match status.get(8) { - None => None, - Some(v) => Some(GameMode::from_bedrock(v)?) - }, - server_type: Server::Bedrock - }) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - Bedrock::new(address, port, timeout_settings)?.get_info() - } -} +// This file has code that has been documented by the NodeJS GameDig library +// (MIT) from https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js + +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{BedrockResponse, GameMode, Server}, + types::TimeoutSettings, + }, + socket::{Socket, UdpSocket}, + utils::error_by_expected_size, + GDError::{PacketBad, TypeParse}, + GDResult, +}; + +pub struct Bedrock { + socket: UdpSocket, +} + +impl Bedrock { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send_status_request(&mut self) -> GDResult<()> { + self.socket.send(&[ + 0x01, // Message ID: ID_UNCONNECTED_PING + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Nonce / timestamp + 0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, // Magic + 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Client GUID + ])?; + + Ok(()) + } + + fn get_info(&mut self) -> GDResult { + self.send_status_request()?; + + let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); + + if buffer.get_u8()? != 0x1c { + return Err(PacketBad); + } + + // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). + if buffer.get_u64()? != 9833440827789222417 { + return Err(PacketBad); + } + + // These 8 bytes are identical to the serverId string we receive in decimal + // below + buffer.move_position_ahead(8); + + // Verifying the magic value (as we need 16 bytes, cast to two u64 values) + if buffer.get_u64()? != 18374403896610127616 { + return Err(PacketBad); + } + + if buffer.get_u64()? != 8671175388723805693 { + return Err(PacketBad); + } + + let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize; + buffer.move_position_ahead(2); + error_by_expected_size(remaining_length, buffer.remaining_length())?; + + let binding = buffer.get_string_utf8_unended()?; + let status: Vec<&str> = binding.split(';').collect(); + + // We must have at least 6 values + if status.len() < 6 { + return Err(PacketBad); + } + + Ok(BedrockResponse { + edition: status[0].to_string(), + name: status[1].to_string(), + version_name: status[3].to_string(), + version_protocol: status[2].to_string(), + players_maximum: status[5].parse().map_err(|_| TypeParse)?, + players_online: status[4].parse().map_err(|_| TypeParse)?, + id: status.get(6).map(|v| v.to_string()), + map: status.get(7).map(|v| v.to_string()), + game_mode: match status.get(8) { + None => None, + Some(v) => Some(GameMode::from_bedrock(v)?), + }, + server_type: Server::Bedrock, + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Bedrock::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index e71ecb1..8d55d02 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -1,127 +1,144 @@ -use serde_json::Value; -use crate::GDResult; -use crate::GDError::{JsonParse, PacketBad}; -use crate::bufferer::{Bufferer, Endianess}; -use crate::protocols::minecraft::{as_varint, get_string, get_varint, Player, JavaResponse, Server}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, TcpSocket}; - -pub struct Java { - socket: TcpSocket -} - -impl Java { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = TcpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send(&mut self, data: Vec) -> GDResult<()> { - self.socket.send(&[as_varint(data.len() as i32), data].concat()) - } - - fn receive(&mut self) -> GDResult { - let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); - - let _packet_length = get_varint(&mut buffer)? as usize; - //this declared 'packet length' from within the packet might be wrong (?), not checking with it... - - Ok(buffer) - } - - fn send_handshake(&mut self) -> GDResult<()> { - self.send([ - //Packet ID (0) - 0x00, - //Protocol Version (-1 to determine version) - 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, - //Server address (can be anything) - 0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67, - //Server port (can be anything) - 0x00, 0x00, - //Next state (1 for status) - 0x01].to_vec())?; - - Ok(()) - } - - fn send_status_request(&mut self) -> GDResult<()> { - self.send([ - //Packet ID (0) - 0x00].to_vec())?; - - Ok(()) - } - - fn send_ping_request(&mut self) -> GDResult<()> { - self.send([ - //Packet ID (1) - 0x01].to_vec())?; - - Ok(()) - } - - fn get_info(&mut self) -> GDResult { - self.send_handshake()?; - self.send_status_request()?; - self.send_ping_request()?; - - let mut buffer = self.receive()?; - - if get_varint(&mut buffer)? != 0 { //first var int is the packet id - return Err(PacketBad); - } - - let json_response = get_string(&mut buffer)?; - let value_response: Value = serde_json::from_str(&json_response) - .map_err(|_|JsonParse)?; - - let version_name = value_response["version"]["name"].as_str() - .ok_or(PacketBad)?.to_string(); - let version_protocol = value_response["version"]["protocol"].as_i64() - .ok_or(PacketBad)? as i32; - - let max_players = value_response["players"]["max"].as_u64() - .ok_or(PacketBad)? as u32; - let online_players = value_response["players"]["online"].as_u64() - .ok_or(PacketBad)? as u32; - let sample_players: Option> = match value_response["players"]["sample"].is_null() { - true => None, - false => Some({ - let players_values = value_response["players"]["sample"].as_array() - .ok_or(PacketBad)?; - - let mut players = Vec::with_capacity(players_values.len()); - for player in players_values { - players.push(Player { - name: player["name"].as_str().ok_or(PacketBad)?.to_string(), - id: player["id"].as_str().ok_or(PacketBad)?.to_string() - }) - } - - players - }) - }; - - Ok(JavaResponse { - version_name, - version_protocol, - players_maximum: max_players, - players_online: online_players, - players_sample: sample_players, - description: value_response["description"].to_string(), - favicon: value_response["favicon"].as_str().map(str::to_string), - previews_chat: value_response["previewsChat"].as_bool(), - enforces_secure_chat: value_response["enforcesSecureChat"].as_bool(), - server_type: Server::Java - }) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - Java::new(address, port, timeout_settings)?.get_info() - } -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{as_varint, get_string, get_varint, JavaResponse, Player, Server}, + types::TimeoutSettings, + }, + socket::{Socket, TcpSocket}, + GDError::{JsonParse, PacketBad}, + GDResult, +}; + +use serde_json::Value; + +#[rustfmt::skip] +const PAYLOAD: [u8; 17] = [ + //Packet ID (0) + 0x00, + //Protocol Version (-1 to determine version) + 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, + //Server address (can be anything) + 0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67, + //Server port (can be anything) + 0x00, 0x00, + //Next state (1 for status) + 0x01 +]; + +pub struct Java { + socket: TcpSocket, +} + +impl Java { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send(&mut self, data: Vec) -> GDResult<()> { + self.socket + .send(&[as_varint(data.len() as i32), data].concat()) + } + + fn receive(&mut self) -> GDResult { + let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?); + + let _packet_length = get_varint(&mut buffer)? as usize; + // this declared 'packet length' from within the packet might be wrong (?), not + // checking with it... + + Ok(buffer) + } + + fn send_handshake(&mut self) -> GDResult<()> { + self.send(PAYLOAD.to_vec())?; + + Ok(()) + } + + fn send_status_request(&mut self) -> GDResult<()> { + self.send( + [0x00] // Packet ID (0) + .to_vec(), + )?; + + Ok(()) + } + + fn send_ping_request(&mut self) -> GDResult<()> { + self.send( + [0x01] // Packet ID (1) + .to_vec(), + )?; + + Ok(()) + } + + fn get_info(&mut self) -> GDResult { + self.send_handshake()?; + self.send_status_request()?; + self.send_ping_request()?; + + let mut buffer = self.receive()?; + + if get_varint(&mut buffer)? != 0 { + // first var int is the packet id + return Err(PacketBad); + } + + let json_response = get_string(&mut buffer)?; + let value_response: Value = serde_json::from_str(&json_response).map_err(|_| JsonParse)?; + + let version_name = value_response["version"]["name"] + .as_str() + .ok_or(PacketBad)? + .to_string(); + let version_protocol = value_response["version"]["protocol"] + .as_i64() + .ok_or(PacketBad)? as i32; + + let max_players = value_response["players"]["max"].as_u64().ok_or(PacketBad)? as u32; + let online_players = value_response["players"]["online"] + .as_u64() + .ok_or(PacketBad)? as u32; + let sample_players: Option> = match value_response["players"]["sample"].is_null() { + true => None, + false => { + Some({ + let players_values = value_response["players"]["sample"] + .as_array() + .ok_or(PacketBad)?; + + let mut players = Vec::with_capacity(players_values.len()); + for player in players_values { + players.push(Player { + name: player["name"].as_str().ok_or(PacketBad)?.to_string(), + id: player["id"].as_str().ok_or(PacketBad)?.to_string(), + }) + } + + players + }) + } + }; + + Ok(JavaResponse { + version_name, + version_protocol, + players_maximum: max_players, + players_online: online_players, + players_sample: sample_players, + description: value_response["description"].to_string(), + favicon: value_response["favicon"].as_str().map(str::to_string), + previews_chat: value_response["previewsChat"].as_bool(), + enforces_secure_chat: value_response["enforcesSecureChat"].as_bool(), + server_type: Server::Java, + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Java::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index 2423996..e977e44 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -1,68 +1,65 @@ - -use crate::GDResult; -use crate::bufferer::{Bufferer, Endianess}; -use crate::GDError::{PacketBad, ProtocolFormat}; -use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, TcpSocket}; -use crate::utils::error_by_expected_size; - -pub struct LegacyBV1_8 { - socket: TcpSocket -} - -impl LegacyBV1_8 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = TcpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send_initial_request(&mut self) -> GDResult<()> { - self.socket.send(&[0xFE]) - } - - fn get_info(&mut self) -> GDResult { - self.send_initial_request()?; - - let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - - if buffer.get_u8()? != 0xFF { - return Err(ProtocolFormat); - } - - let length = buffer.get_u16()? * 2; - error_by_expected_size((length + 3) as usize, buffer.data_length())?; - - let packet_string = buffer.get_string_utf16()?; - - let split: Vec<&str> = packet_string.split('§').collect(); - error_by_expected_size(3, split.len())?; - - let description = split[0].to_string(); - let online_players = split[1].parse() - .map_err(|_| PacketBad)?; - let max_players = split[2].parse() - .map_err(|_| PacketBad)?; - - Ok(JavaResponse { - version_name: "Beta 1.8+".to_string(), - version_protocol: -1, - players_maximum: max_players, - players_online: online_players, - players_sample: None, - description, - favicon: None, - previews_chat: None, - enforces_secure_chat: None, - server_type: Server::Legacy(LegacyGroup::VB1_8) - }) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - LegacyBV1_8::new(address, port, timeout_settings)?.get_info() - } -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{JavaResponse, LegacyGroup, Server}, + types::TimeoutSettings, + }, + socket::{Socket, TcpSocket}, + utils::error_by_expected_size, + GDError::{PacketBad, ProtocolFormat}, + GDResult, +}; + +pub struct LegacyBV1_8 { + socket: TcpSocket, +} + +impl LegacyBV1_8 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { self.socket.send(&[0xFE]) } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); + + if buffer.get_u8()? != 0xFF { + return Err(ProtocolFormat); + } + + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; + + let packet_string = buffer.get_string_utf16()?; + + let split: Vec<&str> = packet_string.split('§').collect(); + error_by_expected_size(3, split.len())?; + + let description = split[0].to_string(); + let online_players = split[1].parse().map_err(|_| PacketBad)?; + let max_players = split[2].parse().map_err(|_| PacketBad)?; + + Ok(JavaResponse { + version_name: "Beta 1.8+".to_string(), + version_protocol: -1, + players_maximum: max_players, + players_online: online_players, + players_sample: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::VB1_8), + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyBV1_8::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index 5c5451c..aed055d 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -1,73 +1,69 @@ - -use crate::GDResult; -use crate::bufferer::{Bufferer, Endianess}; -use crate::GDError::{PacketBad, ProtocolFormat}; -use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; -use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, TcpSocket}; -use crate::utils::error_by_expected_size; - -pub struct LegacyV1_4 { - socket: TcpSocket -} - -impl LegacyV1_4 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = TcpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send_initial_request(&mut self) -> GDResult<()> { - self.socket.send(&[0xFE, 0x01]) - } - - fn get_info(&mut self) -> GDResult { - self.send_initial_request()?; - - let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - - if buffer.get_u8()? != 0xFF { - return Err(ProtocolFormat); - } - - let length = buffer.get_u16()? * 2; - error_by_expected_size((length + 3) as usize, buffer.data_length())?; - - if LegacyV1_6::is_protocol(&mut buffer)? { - return LegacyV1_6::get_response(&mut buffer); - } - - let packet_string = buffer.get_string_utf16()?; - - let split: Vec<&str> = packet_string.split('§').collect(); - error_by_expected_size(3, split.len())?; - - let description = split[0].to_string(); - let online_players = split[1].parse() - .map_err(|_| PacketBad)?; - let max_players = split[2].parse() - .map_err(|_| PacketBad)?; - - Ok(JavaResponse { - version_name: "1.4+".to_string(), - version_protocol: -1, - players_maximum: max_players, - players_online: online_players, - players_sample: None, - description, - favicon: None, - previews_chat: None, - enforces_secure_chat: None, - server_type: Server::Legacy(LegacyGroup::V1_4) - }) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - LegacyV1_4::new(address, port, timeout_settings)?.get_info() - } -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{protocol::legacy_v1_6::LegacyV1_6, JavaResponse, LegacyGroup, Server}, + types::TimeoutSettings, + }, + socket::{Socket, TcpSocket}, + utils::error_by_expected_size, + GDError::{PacketBad, ProtocolFormat}, + GDResult, +}; + +pub struct LegacyV1_4 { + socket: TcpSocket, +} + +impl LegacyV1_4 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { self.socket.send(&[0xFE, 0x01]) } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); + + if buffer.get_u8()? != 0xFF { + return Err(ProtocolFormat); + } + + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; + + if LegacyV1_6::is_protocol(&mut buffer)? { + return LegacyV1_6::get_response(&mut buffer); + } + + let packet_string = buffer.get_string_utf16()?; + + let split: Vec<&str> = packet_string.split('§').collect(); + error_by_expected_size(3, split.len())?; + + let description = split[0].to_string(); + let online_players = split[1].parse().map_err(|_| PacketBad)?; + let max_players = split[2].parse().map_err(|_| PacketBad)?; + + Ok(JavaResponse { + version_name: "1.4+".to_string(), + version_protocol: -1, + players_maximum: max_players, + players_online: online_players, + players_sample: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::V1_4), + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyV1_4::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 4954298..f901b79 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -1,100 +1,98 @@ -use crate::GDResult; -use crate::GDError::{PacketBad, ProtocolFormat}; -use crate::bufferer::{Bufferer, Endianess}; -use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server}; -use crate::protocols::types::TimeoutSettings; -use crate::socket::{Socket, TcpSocket}; -use crate::utils::error_by_expected_size; - -pub struct LegacyV1_6 { - socket: TcpSocket -} - -impl LegacyV1_6 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = TcpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn send_initial_request(&mut self) -> GDResult<()> { - self.socket.send(&[ - // Packet ID (FE) - 0xfe, - // Ping payload (01) - 0x01, - // Packet identifier for plugin message - 0xfa, - // Length of 'GameDig' string (7) as unsigned short - 0x00, 0x07, - // 'GameDig' string as UTF-16BE - 0x00, 0x47, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67])?; - - Ok(()) - } - - pub fn is_protocol(buffer: &mut Bufferer) -> GDResult { - let state = buffer.remaining_data().starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]); - - if state { - buffer.move_position_ahead(6); - } - - Ok(state) - } - - pub fn get_response(buffer: &mut Bufferer) -> GDResult { - let packet_string = buffer.get_string_utf16()?; - - let split: Vec<&str> = packet_string.split('\x00').collect(); - error_by_expected_size(5, split.len())?; - - let version_protocol = split[0].parse() - .map_err(|_| PacketBad)?; - let version_name = split[1].to_string(); - let description = split[2].to_string(); - let online_players = split[3].parse() - .map_err(|_| PacketBad)?; - let max_players = split[4].parse() - .map_err(|_| PacketBad)?; - - Ok(JavaResponse { - version_name, - version_protocol, - players_maximum: max_players, - players_online: online_players, - players_sample: None, - description, - favicon: None, - previews_chat: None, - enforces_secure_chat: None, - server_type: Server::Legacy(LegacyGroup::V1_6) - }) - } - - fn get_info(&mut self) -> GDResult { - self.send_initial_request()?; - - let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); - - if buffer.get_u8()? != 0xFF { - return Err(ProtocolFormat); - } - - let length = buffer.get_u16()? * 2; - error_by_expected_size((length + 3) as usize, buffer.data_length())?; - - if !LegacyV1_6::is_protocol(&mut buffer)? { - return Err(ProtocolFormat); - } - - LegacyV1_6::get_response(&mut buffer) - } - - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - LegacyV1_6::new(address, port, timeout_settings)?.get_info() - } -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + minecraft::{JavaResponse, LegacyGroup, Server}, + types::TimeoutSettings, + }, + socket::{Socket, TcpSocket}, + utils::error_by_expected_size, + GDError::{PacketBad, ProtocolFormat}, + GDResult, +}; + +pub struct LegacyV1_6 { + socket: TcpSocket, +} + +impl LegacyV1_6 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = TcpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn send_initial_request(&mut self) -> GDResult<()> { + self.socket.send(&[ + 0xfe, // Packet ID (FE) + 0x01, // Ping payload (01) + 0xfa, // Packet identifier for plugin message + 0x00, 0x07, // Length of 'GameDig' string (7) as unsigned short + 0x00, 0x47, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x44, 0x00, 0x69, 0x00, + 0x67, // 'GameDig' string as UTF-16BE + ])?; + + Ok(()) + } + + pub fn is_protocol(buffer: &mut Bufferer) -> GDResult { + let state = buffer + .remaining_data() + .starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]); + + if state { + buffer.move_position_ahead(6); + } + + Ok(state) + } + + pub fn get_response(buffer: &mut Bufferer) -> GDResult { + let packet_string = buffer.get_string_utf16()?; + + let split: Vec<&str> = packet_string.split('\x00').collect(); + error_by_expected_size(5, split.len())?; + + let version_protocol = split[0].parse().map_err(|_| PacketBad)?; + let version_name = split[1].to_string(); + let description = split[2].to_string(); + let online_players = split[3].parse().map_err(|_| PacketBad)?; + let max_players = split[4].parse().map_err(|_| PacketBad)?; + + Ok(JavaResponse { + version_name, + version_protocol, + players_maximum: max_players, + players_online: online_players, + players_sample: None, + description, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Legacy(LegacyGroup::V1_6), + }) + } + + fn get_info(&mut self) -> GDResult { + self.send_initial_request()?; + + let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?); + + if buffer.get_u8()? != 0xFF { + return Err(ProtocolFormat); + } + + let length = buffer.get_u16()? * 2; + error_by_expected_size((length + 3) as usize, buffer.data_length())?; + + if !LegacyV1_6::is_protocol(&mut buffer)? { + return Err(ProtocolFormat); + } + + LegacyV1_6::get_response(&mut buffer) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + LegacyV1_6::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 73bcb90..293c739 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,68 +1,82 @@ -use crate::GDError::AutoQuery; -use crate::GDResult; -use crate::protocols::minecraft::{BedrockResponse, LegacyGroup, JavaResponse}; -use crate::protocols::minecraft::protocol::bedrock::Bedrock; -use crate::protocols::minecraft::protocol::java::Java; -use crate::protocols::minecraft::protocol::legacy_v1_4::LegacyV1_4; -use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; -use crate::protocols::minecraft::protocol::legacy_bv1_8::LegacyBV1_8; -use crate::protocols::types::TimeoutSettings; - -mod java; -mod legacy_v1_4; -mod legacy_v1_6; -mod legacy_bv1_8; -mod bedrock; - -/// Queries a Minecraft server with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - if let Ok(response) = query_java(address, port, timeout_settings.clone()) { - return Ok(response); - } - - if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) { - return Ok(JavaResponse::from_bedrock_response(response)); - } - - if let Ok(response) = query_legacy(address, port, timeout_settings) { - return Ok(response); - } - - Err(AutoQuery) -} - -/// Query a Java Server. -pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { - Java::query(address, port, timeout_settings) -} - -/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { - if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { - return Ok(response); - } - - if let Ok(response) = query_legacy_specific(LegacyGroup::V1_4, address, port, timeout_settings.clone()) { - return Ok(response); - } - - if let Ok(response) = query_legacy_specific(LegacyGroup::VB1_8, address, port, timeout_settings) { - return Ok(response); - } - - Err(AutoQuery) -} - -/// Query a specific (Java) Legacy Server. -pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: u16, timeout_settings: Option) -> GDResult { - match group { - LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), - LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), - LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings) - } -} - -/// Query a Bedrock Server. -pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option) -> GDResult { - Bedrock::query(address, port, timeout_settings) -} +use crate::{ + protocols::minecraft::{ + protocol::{ + bedrock::Bedrock, + java::Java, + legacy_bv1_8::LegacyBV1_8, + legacy_v1_4::LegacyV1_4, + legacy_v1_6::LegacyV1_6, + }, + BedrockResponse, + JavaResponse, + LegacyGroup, + }, + protocols::types::TimeoutSettings, + GDError::AutoQuery, + GDResult, +}; + +mod bedrock; +mod java; +mod legacy_bv1_8; +mod legacy_v1_4; +mod legacy_v1_6; + +/// Queries a Minecraft server with all the protocol variants one by one (Java +/// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). +pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + if let Ok(response) = query_java(address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) { + return Ok(JavaResponse::from_bedrock_response(response)); + } + + if let Ok(response) = query_legacy(address, port, timeout_settings) { + return Ok(response); + } + + Err(AutoQuery) +} + +/// Query a Java Server. +pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Java::query(address, port, timeout_settings) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { + if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_legacy_specific(LegacyGroup::V1_4, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_legacy_specific(LegacyGroup::VB1_8, address, port, timeout_settings) { + return Ok(response); + } + + Err(AutoQuery) +} + +/// Query a specific (Java) Legacy Server. +pub fn query_legacy_specific( + group: LegacyGroup, + address: &str, + port: u16, + timeout_settings: Option, +) -> GDResult { + match group { + LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), + LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), + LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings), + } +} + +/// Query a Bedrock Server. +pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Bedrock::query(address, port, timeout_settings) +} diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index d596339..8b22581 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -1,204 +1,205 @@ -/* -Although its a lightly modified version, this file contains code -by Jaiden Bernard (2021-2022 - MIT) from -https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs -*/ - -use crate::bufferer::Bufferer; -use crate::GDError::{PacketBad, UnknownEnumCast}; -use crate::GDResult; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// The type of Minecraft Server you want to query. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Server { - /// Java Edition. - Java, - /// Legacy Java. - Legacy(LegacyGroup), - /// Bedrock Edition. - Bedrock, -} - -/// Legacy Java (Versions) Groups. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum LegacyGroup { - /// 1.6 - V1_6, - /// 1.4 - 1.5 - V1_4, - /// Beta 1.8 - 1.3 - VB1_8, -} - -/// Information about a player. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Player { - pub name: String, - pub id: String, -} - -/// A Java query response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct JavaResponse { - /// Version name, example: "1.19.2". - pub version_name: String, - /// Version protocol, example: 760 (for 1.19.2). Note that for versions below 1.6 this field is always -1. - pub version_protocol: i32, - /// Number of server capacity. - pub players_maximum: u32, - /// Number of online players. - pub players_online: u32, - /// Some online players (can be missing). - pub players_sample: Option>, - /// Server's description or MOTD. - pub description: String, - /// The favicon (can be missing). - pub favicon: Option, - /// Tells if the chat preview is enabled (can be missing). - pub previews_chat: Option, - /// Tells if secure chat is enforced (can be missing). - pub enforces_secure_chat: Option, - /// Tell's the server type. - pub server_type: Server, -} - -/// A Bedrock Edition query response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct BedrockResponse { - /// Server's edition. - pub edition: String, - /// Server's name. - pub name: String, - /// Version name, example: "1.19.40". - pub version_name: String, - /// Version protocol, example: 760 (for 1.19.2). - pub version_protocol: String, - /// Maximum number of players the server reports it can hold. - pub players_maximum: u32, - /// Number of players on the server. - pub players_online: u32, - /// Server id. - pub id: Option, - /// Currently running map's name. - pub map: Option, - /// Current game mode. - pub game_mode: Option, - /// Tells the server type. - pub server_type: Server, -} - -impl JavaResponse { - pub fn from_bedrock_response(response: BedrockResponse) -> Self { - Self { - version_name: response.version_name, - version_protocol: 0, - players_maximum: response.players_maximum, - players_online: response.players_online, - players_sample: None, - description: response.name, - favicon: None, - previews_chat: None, - enforces_secure_chat: None, - server_type: Server::Bedrock, - } - } -} - -/// A server's game mode (used only by Bedrock servers. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum GameMode { - Survival, - Creative, - Hardcore, - Spectator, - Adventure, -} - -impl GameMode { - pub fn from_bedrock(value: &&str) -> GDResult { - match *value { - "Survival" => Ok(GameMode::Survival), - "Creative" => Ok(GameMode::Creative), - "Hardcore" => Ok(GameMode::Hardcore), - "Spectator" => Ok(GameMode::Spectator), - "Adventure" => Ok(GameMode::Adventure), - _ => Err(UnknownEnumCast), - } - } -} - -pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult { - let mut result = 0; - - let msb: u8 = 0b10000000; - let mask: u8 = !msb; - - for i in 0..5 { - let current_byte = buffer.get_u8()?; - - result |= ((current_byte & mask) as i32) << (7 * i); - - // The 5th byte is only allowed to have the 4 smallest bits set - if i == 4 && (current_byte & 0xf0 != 0) { - return Err(PacketBad); - } - - if (current_byte & msb) == 0 { - break; - } - } - - Ok(result) -} - -pub(crate) fn as_varint(value: i32) -> Vec { - let mut bytes = vec![]; - let mut reading_value = value; - - let msb: u8 = 0b10000000; - let mask: i32 = 0b01111111; - - for _ in 0..5 { - let tmp = (reading_value & mask) as u8; - - reading_value &= !mask; - reading_value = reading_value.rotate_right(7); - - if reading_value != 0 { - bytes.push(tmp | msb); - } else { - bytes.push(tmp); - break; - } - } - - bytes -} - -pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult { - let length = get_varint(buffer)? as usize; - let mut text = Vec::with_capacity(length); - - for _ in 0..length { - text.push(buffer.get_u8()?) - } - - String::from_utf8(text).map_err(|_| PacketBad) -} - -#[allow(dead_code)] -pub(crate) fn as_string(value: String) -> Vec { - let mut buf = as_varint(value.len() as i32); - buf.extend(value.as_bytes().to_vec()); - - buf -} +// Although its a lightly modified version, this file contains code +// by Jaiden Bernard (2021-2022 - MIT) from +// https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs + +use crate::{ + bufferer::Bufferer, + GDError::{PacketBad, UnknownEnumCast}, + GDResult, +}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The type of Minecraft Server you want to query. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Server { + /// Java Edition. + Java, + /// Legacy Java. + Legacy(LegacyGroup), + /// Bedrock Edition. + Bedrock, +} + +/// Legacy Java (Versions) Groups. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum LegacyGroup { + /// 1.6 + V1_6, + /// 1.4 - 1.5 + V1_4, + /// Beta 1.8 - 1.3 + VB1_8, +} + +/// Information about a player. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Player { + pub name: String, + pub id: String, +} + +/// A Java query response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct JavaResponse { + /// Version name, example: "1.19.2". + pub version_name: String, + /// Version protocol, example: 760 (for 1.19.2). Note that for versions + /// below 1.6 this field is always -1. + pub version_protocol: i32, + /// Number of server capacity. + pub players_maximum: u32, + /// Number of online players. + pub players_online: u32, + /// Some online players (can be missing). + pub players_sample: Option>, + /// Server's description or MOTD. + pub description: String, + /// The favicon (can be missing). + pub favicon: Option, + /// Tells if the chat preview is enabled (can be missing). + pub previews_chat: Option, + /// Tells if secure chat is enforced (can be missing). + pub enforces_secure_chat: Option, + /// Tell's the server type. + pub server_type: Server, +} + +/// A Bedrock Edition query response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct BedrockResponse { + /// Server's edition. + pub edition: String, + /// Server's name. + pub name: String, + /// Version name, example: "1.19.40". + pub version_name: String, + /// Version protocol, example: 760 (for 1.19.2). + pub version_protocol: String, + /// Maximum number of players the server reports it can hold. + pub players_maximum: u32, + /// Number of players on the server. + pub players_online: u32, + /// Server id. + pub id: Option, + /// Currently running map's name. + pub map: Option, + /// Current game mode. + pub game_mode: Option, + /// Tells the server type. + pub server_type: Server, +} + +impl JavaResponse { + pub fn from_bedrock_response(response: BedrockResponse) -> Self { + Self { + version_name: response.version_name, + version_protocol: 0, + players_maximum: response.players_maximum, + players_online: response.players_online, + players_sample: None, + description: response.name, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Bedrock, + } + } +} + +/// A server's game mode (used only by Bedrock servers. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GameMode { + Survival, + Creative, + Hardcore, + Spectator, + Adventure, +} + +impl GameMode { + pub fn from_bedrock(value: &&str) -> GDResult { + match *value { + "Survival" => Ok(GameMode::Survival), + "Creative" => Ok(GameMode::Creative), + "Hardcore" => Ok(GameMode::Hardcore), + "Spectator" => Ok(GameMode::Spectator), + "Adventure" => Ok(GameMode::Adventure), + _ => Err(UnknownEnumCast), + } + } +} + +pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult { + let mut result = 0; + + let msb: u8 = 0b10000000; + let mask: u8 = !msb; + + for i in 0 .. 5 { + let current_byte = buffer.get_u8()?; + + result |= ((current_byte & mask) as i32) << (7 * i); + + // The 5th byte is only allowed to have the 4 smallest bits set + if i == 4 && (current_byte & 0xf0 != 0) { + return Err(PacketBad); + } + + if (current_byte & msb) == 0 { + break; + } + } + + Ok(result) +} + +pub(crate) fn as_varint(value: i32) -> Vec { + let mut bytes = vec![]; + let mut reading_value = value; + + let msb: u8 = 0b10000000; + let mask: i32 = 0b01111111; + + for _ in 0 .. 5 { + let tmp = (reading_value & mask) as u8; + + reading_value &= !mask; + reading_value = reading_value.rotate_right(7); + + if reading_value != 0 { + bytes.push(tmp | msb); + } else { + bytes.push(tmp); + break; + } + } + + bytes +} + +pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult { + let length = get_varint(buffer)? as usize; + let mut text = Vec::with_capacity(length); + + for _ in 0 .. length { + text.push(buffer.get_u8()?) + } + + String::from_utf8(text).map_err(|_| PacketBad) +} + +#[allow(dead_code)] +pub(crate) fn as_string(value: String) -> Vec { + let mut buf = as_varint(value.len() as i32); + buf.extend(value.as_bytes().to_vec()); + + buf +} diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index e6e4fac..48a0e7e 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -1,14 +1,14 @@ - -//! Protocols that are currently implemented. -//! -//! A protocol will be here if it supports multiple entries, if not, its implementation will be -//! in that specific needed place, a protocol can be independently queried. - -/// General types that are used by all protocols. -pub mod types; -/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) -pub mod valve; -/// Reference: [Server List Ping](https://wiki.vg/Server_List_Ping) -pub mod minecraft; -/// Reference: [node-GameDig](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) -pub mod gamespy; +//! Protocols that are currently implemented. +//! +//! A protocol will be here if it supports multiple entries, if not, its +//! implementation will be in that specific needed place, a protocol can be +//! independently queried. + +/// Reference: [node-GameDig](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) +pub mod gamespy; +/// Reference: [Server List Ping](https://wiki.vg/Server_List_Ping) +pub mod minecraft; +/// General types that are used by all protocols. +pub mod types; +/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries) +pub mod valve; diff --git a/src/protocols/types.rs b/src/protocols/types.rs index b0047fc..0f98f46 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -1,5 +1,5 @@ -use crate::GDError::InvalidInput; -use crate::GDResult; +use crate::{GDError::InvalidInput, GDResult}; + use std::time::Duration; /// Timeout settings for socket operations @@ -10,7 +10,8 @@ pub struct TimeoutSettings { } impl TimeoutSettings { - /// Construct new settings, passing None will block indefinitely. Passing zero Duration throws GDError::[InvalidInput](InvalidInput). + /// Construct new settings, passing None will block indefinitely. Passing + /// zero Duration throws GDError::[InvalidInput](InvalidInput). pub fn new(read: Option, write: Option) -> GDResult { if let Some(read_duration) = read { if read_duration == Duration::new(0, 0) { @@ -28,14 +29,10 @@ impl TimeoutSettings { } /// Get the read timeout. - pub fn get_read(&self) -> Option { - self.read - } + pub fn get_read(&self) -> Option { self.read } /// Get the write timeout. - pub fn get_write(&self) -> Option { - self.write - } + pub fn get_write(&self) -> Option { self.write } } impl Default for TimeoutSettings { @@ -77,10 +74,12 @@ mod tests { let read_duration = Duration::new(0, 0); let write_duration = Duration::from_secs(2); - // Try to create new TimeoutSettings with the zero read duration (this should fail) + // Try to create new TimeoutSettings with the zero read duration (this should + // fail) let result = TimeoutSettings::new(Some(read_duration), Some(write_duration)); - // Verify that the function returned an error and that the error type is InvalidInput + // Verify that the function returned an error and that the error type is + // InvalidInput assert!(result.is_err()); assert_eq!(result.unwrap_err(), InvalidInput); } @@ -91,7 +90,8 @@ mod tests { // Get the default TimeoutSettings values let default_settings = TimeoutSettings::default(); - // Verify that the get_read and get_write methods return the expected default values + // Verify that the get_read and get_write methods return the expected default + // values assert_eq!(default_settings.get_read(), Some(Duration::from_secs(4))); assert_eq!(default_settings.get_write(), Some(Duration::from_secs(4))); } diff --git a/src/protocols/valve/mod.rs b/src/protocols/valve/mod.rs index 44527b4..3d98ab9 100644 --- a/src/protocols/valve/mod.rs +++ b/src/protocols/valve/mod.rs @@ -1,8 +1,7 @@ - -/// The implementation. -pub mod protocol; -/// All types used by the implementation. -pub mod types; - -pub use protocol::*; -pub use types::*; +/// The implementation. +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index a1eefd3..9e4944b 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -1,455 +1,512 @@ -use std::collections::HashMap; -use bzip2_rs::decoder::Decoder; -use crate::GDResult; -use crate::bufferer::{Bufferer, Endianess}; -use crate::GDError::{BadGame, Decompress, UnknownEnumCast}; -use crate::protocols::types::TimeoutSettings; -use crate::protocols::valve::{Engine, ModData, SteamApp}; -use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, TheShip}; -use crate::socket::{Socket, UdpSocket}; -use crate::utils::u8_lower_upper; - -#[derive(Debug, Clone)] -struct Packet { - pub header: u32, - pub kind: u8, - pub payload: Vec -} - -impl Packet { - fn new(buffer: &mut Bufferer) -> GDResult { - Ok(Self { - header: buffer.get_u32()?, - kind: buffer.get_u8()?, - payload: buffer.remaining_data_vec() - }) - } - - fn challenge(kind: Request, challenge: Vec) -> Self { - let mut initial = Packet::initial(kind); - - Self { - header: initial.header, - kind: initial.kind, - payload: match kind { - Request::Info => { - initial.payload.extend(challenge); - initial.payload - }, - _ => challenge - } - } - } - - fn initial(kind: Request) -> Self { - Self { - header: 4294967295, //FF FF FF FF - kind: kind as u8, - payload: match kind { - Request::Info => String::from("Source Engine Query\0").into_bytes(), - _ => vec![0xFF, 0xFF, 0xFF, 0xFF] - } - } - } - - fn to_bytes(&self) -> Vec { - let mut buf = Vec::from(self.header.to_be_bytes()); - - buf.push(self.kind); - buf.extend(&self.payload); - - buf - } -} - -#[derive(Debug)] -#[allow(dead_code)] //remove this later on -struct SplitPacket { - pub header: u32, - pub id: u32, - pub total: u8, - pub number: u8, - pub size: u16, - pub compressed: bool, - pub decompressed_size: Option, - pub uncompressed_crc32: Option, - payload: Vec -} - -impl SplitPacket { - fn new(engine: &Engine, protocol: u8, buffer: &mut Bufferer) -> GDResult { - let header = buffer.get_u32()?; - let id = buffer.get_u32()?; - let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match engine { - Engine::GoldSrc(_) => { - let (lower, upper) = u8_lower_upper(buffer.get_u8()?); - (lower, upper, 0, false, None, None) - } - Engine::Source(_) => { - let total = buffer.get_u8()?; - let number = buffer.get_u8()?; - let size = match protocol == 7 && (*engine == SteamApp::CSS.as_engine()) { //certain apps with protocol = 7 dont have this field - false => buffer.get_u16()?, - true => 1248 - }; - let compressed = ((id >> 31) & 1) == 1; - let (decompressed_size, uncompressed_crc32) = match compressed { - false => (None, None), - true => (Some(buffer.get_u32()?), Some(buffer.get_u32()?)) - }; - (total, number, size, compressed, decompressed_size, uncompressed_crc32) - } - }; - - Ok(Self { - header, - id, - total, - number, - size, - compressed, - decompressed_size, - uncompressed_crc32, - payload: buffer.remaining_data_vec() - }) - } - - fn get_payload(&self) -> GDResult> { - if self.compressed { - let mut decoder = Decoder::new(); - decoder.write(&self.payload).map_err(|_| Decompress)?; - - let decompressed_size = self.decompressed_size.unwrap() as usize; - - let mut decompressed_payload = vec![0; decompressed_size]; - - decoder.read(&mut decompressed_payload).map_err(|_| Decompress)?; - - if decompressed_payload.len() != decompressed_size - || crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { - Err(Decompress) - } - else { - Ok(decompressed_payload) - } - } else { - Ok(self.payload.clone()) - } - } -} - -struct ValveProtocol { - socket: UdpSocket -} - -static PACKET_SIZE: usize = 6144; - -impl ValveProtocol { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { - let socket = UdpSocket::new(address, port)?; - socket.apply_timeout(timeout_settings)?; - - Ok(Self { - socket - }) - } - - fn receive(&mut self, engine: &Engine, protocol: u8, buffer_size: usize) -> GDResult { - let data = self.socket.receive(Some(buffer_size))?; - let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); - - let header = buffer.get_u8()?; - buffer.move_position_backward(1); - if header == 0xFE { //the packet is split - let mut main_packet = SplitPacket::new(engine, protocol, &mut buffer)?; - let mut chunk_packets = Vec::with_capacity((main_packet.total - 1) as usize); - - for _ in 1..main_packet.total { - let new_data = self.socket.receive(Some(buffer_size))?; - buffer = Bufferer::new_with_data(Endianess::Little, &new_data); - let chunk_packet = SplitPacket::new(engine, protocol, &mut buffer)?; - chunk_packets.push(chunk_packet); - } - - chunk_packets.sort_by(|a, b| a.number.cmp(&b.number)); - - for chunk_packet in chunk_packets { - main_packet.payload.extend(chunk_packet.payload); - } - - let mut new_packet_buffer = Bufferer::new_with_data(Endianess::Little, &main_packet.get_payload()?); - Ok(Packet::new(&mut new_packet_buffer)?) - } - else { - Packet::new(&mut buffer) - } - } - - /// Ask for a specific request only. - fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult { - let request_initial_packet = Packet::initial(kind).to_bytes(); - self.socket.send(&request_initial_packet)?; - - let mut packet = self.receive(engine, protocol, PACKET_SIZE)?; - while packet.kind == 0x41 {// 'A' - let challenge = packet.payload.clone(); - let challenge_packet = Packet::challenge(kind, challenge).to_bytes(); - - self.socket.send(&challenge_packet)?; - - packet = self.receive(engine, protocol, PACKET_SIZE)?; - } - - let data = packet.payload; - Ok(Bufferer::new_with_data(Endianess::Little, &data)) - } - - fn get_goldsrc_server_info(buffer: &mut Bufferer) -> GDResult { - buffer.get_u8()?; //get the header (useless info) - buffer.get_string_utf8()?; //get the server address (useless info) - let name = buffer.get_string_utf8()?; - let map = buffer.get_string_utf8()?; - let folder = buffer.get_string_utf8()?; - let game = buffer.get_string_utf8()?; - let players = buffer.get_u8()?; - let max_players = buffer.get_u8()?; - let protocol = buffer.get_u8()?; - let server_type = match buffer.get_u8()? { - 68 => Server::Dedicated, //'D' - 76 => Server::NonDedicated, //'L' - 80 => Server::TV, //'P' - _ => Err(UnknownEnumCast)? - }; - let environment_type = match buffer.get_u8()? { - 76 => Environment::Linux, //'L' - 87 => Environment::Windows, //'W' - _ => Err(UnknownEnumCast)? - }; - let has_password = buffer.get_u8()? == 1; - let is_mod = buffer.get_u8()? == 1; - let mod_data = match is_mod { - false => None, - true => Some(ModData { - link: buffer.get_string_utf8()?, - download_link: buffer.get_string_utf8()?, - version: buffer.get_u32()?, - size: buffer.get_u32()?, - multiplayer_only: buffer.get_u8()? == 1, - has_own_dll: buffer.get_u8()? == 1 - }) - }; - let vac_secured = buffer.get_u8()? == 1; - let bots = buffer.get_u8()?; - - Ok(ServerInfo { - protocol, - name, - map, - folder, - game, - appid: 0, //not present in the obsolete response - players_online: players, - players_maximum: max_players, - players_bots: bots, - server_type, - environment_type, - has_password, - vac_secured, - the_ship: None, - version: "".to_string(), //a version field only for the mod - extra_data: None, - is_mod, - mod_data - }) - } - - /// Get the server information's. - fn get_server_info(&mut self, engine: &Engine) -> GDResult { - let mut buffer = self.get_request_data(engine, 0, Request::Info)?; - - if let Engine::GoldSrc(force) = engine { - if *force { - return ValveProtocol::get_goldsrc_server_info(&mut buffer); - } - } - - let protocol = buffer.get_u8()?; - let name = buffer.get_string_utf8()?; - let map = buffer.get_string_utf8()?; - let folder = buffer.get_string_utf8()?; - let game = buffer.get_string_utf8()?; - let mut appid = buffer.get_u16()? as u32; - let players = buffer.get_u8()?; - let max_players = buffer.get_u8()?; - let bots = buffer.get_u8()?; - let server_type = match buffer.get_u8()? { - 100 => Server::Dedicated, //'d' - 108 => Server::NonDedicated, //'l' - 112 => Server::TV, //'p' - _ => Err(UnknownEnumCast)? - }; - let environment_type = match buffer.get_u8()? { - 108 => Environment::Linux, //'l' - 119 => Environment::Windows, //'w' - 109 | 111 => Environment::Mac, //'m' or 'o' - _ => Err(UnknownEnumCast)? - }; - let has_password = buffer.get_u8()? == 1; - let vac_secured = buffer.get_u8()? == 1; - let the_ship = match *engine == SteamApp::TS.as_engine() { - false => None, - true => Some(TheShip { - mode: buffer.get_u8()?, - witnesses: buffer.get_u8()?, - duration: buffer.get_u8()? - }) - }; - let version = buffer.get_string_utf8()?; - let extra_data = match buffer.get_u8() { - Err(_) => None, - Ok(value) => Some(ExtraData { - port: match (value & 0x80) > 0 { - false => None, - true => Some(buffer.get_u16()?) - }, - steam_id: match (value & 0x10) > 0 { - false => None, - true => Some(buffer.get_u64()?) - }, - tv_port: match (value & 0x40) > 0 { - false => None, - true => Some(buffer.get_u16()?) - }, - tv_name: match (value & 0x40) > 0 { - false => None, - true => Some(buffer.get_string_utf8()?) - }, - keywords: match (value & 0x20) > 0 { - false => None, - true => Some(buffer.get_string_utf8()?) - }, - game_id: match (value & 0x01) > 0 { - false => None, - true => { - let gid = buffer.get_u64()?; - appid = (gid & ((1 << 24) - 1)) as u32; - - Some(gid) - } - } - }) - }; - - Ok(ServerInfo { - protocol, - name, - map, - folder, - game, - appid, - players_online: players, - players_maximum: max_players, - players_bots: bots, - server_type, - environment_type, - has_password, - vac_secured, - the_ship, - version, - extra_data, - is_mod: false, - mod_data: None - }) - } - - /// Get the server player's. - fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(engine, protocol, Request::Players)?; - - let count = buffer.get_u8()? as usize; - let mut players: Vec = Vec::with_capacity(count); - - for _ in 0..count { - buffer.move_position_ahead(1); //skip the index byte - - players.push(ServerPlayer { - name: buffer.get_string_utf8()?, - score: buffer.get_u32()?, - duration: buffer.get_f32()?, - deaths: match *engine == SteamApp::TS.as_engine() { - false => None, - true => Some(buffer.get_u32()?) - }, - money: match *engine == SteamApp::TS.as_engine() { - false => None, - true => Some(buffer.get_u32()?) - }, - }); - } - - Ok(players) - } - - /// Get the server's rules. - fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(engine, protocol, Request::Rules)?; - - let count = buffer.get_u16()? as usize; - let mut rules: HashMap = HashMap::with_capacity(count); - - for _ in 0..count { - let name = buffer.get_string_utf8()?; - let value = buffer.get_string_utf8()?; - - rules.insert(name, value); - } - - if *engine == SteamApp::ROR2.as_engine() { - rules.remove("Test"); - } - - Ok(rules) - } -} - -/// Query a server by providing the address, the port, the app, gather and timeout settings. -/// Providing None to the settings results in using the default values for them (GatherSettings::[default](GatheringSettings::default), TimeoutSettings::[default](TimeoutSettings::default)). -pub fn query(address: &str, port: u16, engine: Engine, gather_settings: Option, timeout_settings: Option) -> GDResult { - let response_gather_settings = gather_settings.unwrap_or_default(); - get_response(address, port, engine, response_gather_settings, timeout_settings) -} - -fn get_response(address: &str, port: u16, engine: Engine, gather_settings: GatheringSettings, timeout_settings: Option) -> GDResult { - let mut client = ValveProtocol::new(address, port, timeout_settings)?; - - let info = client.get_server_info(&engine)?; - let protocol = info.protocol; - - if let Engine::Source(Some(appids)) = &engine { - let mut is_specified_id = false; - - if appids.0 == info.appid { - is_specified_id = true; - } else if let Some(dedicated_appid) = appids.1 { - if dedicated_appid == info.appid { - is_specified_id = true; - } - } - - if !is_specified_id { - return Err(BadGame(format!("AppId: {}", info.appid))); - } - } - - Ok(Response { - info, - players: match gather_settings.players { - false => None, - true => Some(client.get_server_players(&engine, protocol)?) - }, - rules: match gather_settings.rules { - false => None, - true => Some(client.get_server_rules(&engine, protocol)?) - } - }) -} +use crate::{ + bufferer::{Bufferer, Endianess}, + protocols::{ + types::TimeoutSettings, + valve::{ + types::{ + Environment, + ExtraData, + GatheringSettings, + Request, + Response, + Server, + ServerInfo, + ServerPlayer, + TheShip, + }, + Engine, + ModData, + SteamApp, + }, + }, + + socket::{Socket, UdpSocket}, + utils::u8_lower_upper, + GDError::{BadGame, Decompress, UnknownEnumCast}, + GDResult, +}; + +use bzip2_rs::decoder::Decoder; + +use std::collections::HashMap; + +#[derive(Debug, Clone)] +struct Packet { + pub header: u32, + pub kind: u8, + pub payload: Vec, +} + +impl Packet { + fn new(buffer: &mut Bufferer) -> GDResult { + Ok(Self { + header: buffer.get_u32()?, + kind: buffer.get_u8()?, + payload: buffer.remaining_data_vec(), + }) + } + + fn challenge(kind: Request, challenge: Vec) -> Self { + let mut initial = Packet::initial(kind); + + Self { + header: initial.header, + kind: initial.kind, + payload: match kind { + Request::Info => { + initial.payload.extend(challenge); + initial.payload + } + _ => challenge, + }, + } + } + + fn initial(kind: Request) -> Self { + Self { + header: 4294967295, // FF FF FF FF + kind: kind as u8, + payload: match kind { + Request::Info => String::from("Source Engine Query\0").into_bytes(), + _ => vec![0xFF, 0xFF, 0xFF, 0xFF], + }, + } + } + + fn to_bytes(&self) -> Vec { + let mut buf = Vec::from(self.header.to_be_bytes()); + + buf.push(self.kind); + buf.extend(&self.payload); + + buf + } +} + +#[derive(Debug)] +#[allow(dead_code)] //remove this later on +struct SplitPacket { + pub header: u32, + pub id: u32, + pub total: u8, + pub number: u8, + pub size: u16, + pub compressed: bool, + pub decompressed_size: Option, + pub uncompressed_crc32: Option, + payload: Vec, +} + +impl SplitPacket { + fn new(engine: &Engine, protocol: u8, buffer: &mut Bufferer) -> GDResult { + let header = buffer.get_u32()?; + let id = buffer.get_u32()?; + let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match engine { + Engine::GoldSrc(_) => { + let (lower, upper) = u8_lower_upper(buffer.get_u8()?); + (lower, upper, 0, false, None, None) + } + Engine::Source(_) => { + let total = buffer.get_u8()?; + let number = buffer.get_u8()?; + let size = match protocol == 7 && (*engine == SteamApp::CSS.as_engine()) { + // certain apps with protocol = 7 dont have this field + false => buffer.get_u16()?, + true => 1248, + }; + let compressed = ((id >> 31) & 1) == 1; + let (decompressed_size, uncompressed_crc32) = match compressed { + false => (None, None), + true => (Some(buffer.get_u32()?), Some(buffer.get_u32()?)), + }; + ( + total, + number, + size, + compressed, + decompressed_size, + uncompressed_crc32, + ) + } + }; + + Ok(Self { + header, + id, + total, + number, + size, + compressed, + decompressed_size, + uncompressed_crc32, + payload: buffer.remaining_data_vec(), + }) + } + + fn get_payload(&self) -> GDResult> { + if self.compressed { + let mut decoder = Decoder::new(); + decoder.write(&self.payload).map_err(|_| Decompress)?; + + let decompressed_size = self.decompressed_size.unwrap() as usize; + + let mut decompressed_payload = vec![0; decompressed_size]; + + decoder + .read(&mut decompressed_payload) + .map_err(|_| Decompress)?; + + if decompressed_payload.len() != decompressed_size + || crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() + { + Err(Decompress) + } else { + Ok(decompressed_payload) + } + } else { + Ok(self.payload.clone()) + } + } +} + +struct ValveProtocol { + socket: UdpSocket, +} + +static PACKET_SIZE: usize = 6144; + +impl ValveProtocol { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn receive(&mut self, engine: &Engine, protocol: u8, buffer_size: usize) -> GDResult { + let data = self.socket.receive(Some(buffer_size))?; + let mut buffer = Bufferer::new_with_data(Endianess::Little, &data); + + let header = buffer.get_u8()?; + buffer.move_position_backward(1); + if header == 0xFE { + // the packet is split + let mut main_packet = SplitPacket::new(engine, protocol, &mut buffer)?; + let mut chunk_packets = Vec::with_capacity((main_packet.total - 1) as usize); + + for _ in 1 .. main_packet.total { + let new_data = self.socket.receive(Some(buffer_size))?; + buffer = Bufferer::new_with_data(Endianess::Little, &new_data); + let chunk_packet = SplitPacket::new(engine, protocol, &mut buffer)?; + chunk_packets.push(chunk_packet); + } + + chunk_packets.sort_by(|a, b| a.number.cmp(&b.number)); + + for chunk_packet in chunk_packets { + main_packet.payload.extend(chunk_packet.payload); + } + + let mut new_packet_buffer = Bufferer::new_with_data(Endianess::Little, &main_packet.get_payload()?); + Ok(Packet::new(&mut new_packet_buffer)?) + } else { + Packet::new(&mut buffer) + } + } + + /// Ask for a specific request only. + fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult { + let request_initial_packet = Packet::initial(kind).to_bytes(); + self.socket.send(&request_initial_packet)?; + + let mut packet = self.receive(engine, protocol, PACKET_SIZE)?; + while packet.kind == 0x41 { + // 'A' + let challenge = packet.payload.clone(); + let challenge_packet = Packet::challenge(kind, challenge).to_bytes(); + + self.socket.send(&challenge_packet)?; + + packet = self.receive(engine, protocol, PACKET_SIZE)?; + } + + let data = packet.payload; + Ok(Bufferer::new_with_data(Endianess::Little, &data)) + } + + fn get_goldsrc_server_info(buffer: &mut Bufferer) -> GDResult { + buffer.get_u8()?; //get the header (useless info) + buffer.get_string_utf8()?; //get the server address (useless info) + let name = buffer.get_string_utf8()?; + let map = buffer.get_string_utf8()?; + let folder = buffer.get_string_utf8()?; + let game = buffer.get_string_utf8()?; + let players = buffer.get_u8()?; + let max_players = buffer.get_u8()?; + let protocol = buffer.get_u8()?; + let server_type = match buffer.get_u8()? { + 68 => Server::Dedicated, //'D' + 76 => Server::NonDedicated, //'L' + 80 => Server::TV, //'P' + _ => Err(UnknownEnumCast)?, + }; + let environment_type = match buffer.get_u8()? { + 76 => Environment::Linux, //'L' + 87 => Environment::Windows, //'W' + _ => Err(UnknownEnumCast)?, + }; + let has_password = buffer.get_u8()? == 1; + let is_mod = buffer.get_u8()? == 1; + let mod_data = match is_mod { + false => None, + true => { + Some(ModData { + link: buffer.get_string_utf8()?, + download_link: buffer.get_string_utf8()?, + version: buffer.get_u32()?, + size: buffer.get_u32()?, + multiplayer_only: buffer.get_u8()? == 1, + has_own_dll: buffer.get_u8()? == 1, + }) + } + }; + let vac_secured = buffer.get_u8()? == 1; + let bots = buffer.get_u8()?; + + Ok(ServerInfo { + protocol, + name, + map, + folder, + game, + appid: 0, // not present in the obsolete response + players_online: players, + players_maximum: max_players, + players_bots: bots, + server_type, + environment_type, + has_password, + vac_secured, + the_ship: None, + version: "".to_string(), // a version field only for the mod + extra_data: None, + is_mod, + mod_data, + }) + } + + /// Get the server information's. + fn get_server_info(&mut self, engine: &Engine) -> GDResult { + let mut buffer = self.get_request_data(engine, 0, Request::Info)?; + + if let Engine::GoldSrc(force) = engine { + if *force { + return ValveProtocol::get_goldsrc_server_info(&mut buffer); + } + } + + let protocol = buffer.get_u8()?; + let name = buffer.get_string_utf8()?; + let map = buffer.get_string_utf8()?; + let folder = buffer.get_string_utf8()?; + let game = buffer.get_string_utf8()?; + let mut appid = buffer.get_u16()? as u32; + let players = buffer.get_u8()?; + let max_players = buffer.get_u8()?; + let bots = buffer.get_u8()?; + let server_type = match buffer.get_u8()? { + 100 => Server::Dedicated, //'d' + 108 => Server::NonDedicated, //'l' + 112 => Server::TV, //'p' + _ => Err(UnknownEnumCast)?, + }; + let environment_type = match buffer.get_u8()? { + 108 => Environment::Linux, //'l' + 119 => Environment::Windows, //'w' + 109 | 111 => Environment::Mac, //'m' or 'o' + _ => Err(UnknownEnumCast)?, + }; + let has_password = buffer.get_u8()? == 1; + let vac_secured = buffer.get_u8()? == 1; + let the_ship = match *engine == SteamApp::TS.as_engine() { + false => None, + true => { + Some(TheShip { + mode: buffer.get_u8()?, + witnesses: buffer.get_u8()?, + duration: buffer.get_u8()?, + }) + } + }; + let version = buffer.get_string_utf8()?; + let extra_data = match buffer.get_u8() { + Err(_) => None, + Ok(value) => { + Some(ExtraData { + port: match (value & 0x80) > 0 { + false => None, + true => Some(buffer.get_u16()?), + }, + steam_id: match (value & 0x10) > 0 { + false => None, + true => Some(buffer.get_u64()?), + }, + tv_port: match (value & 0x40) > 0 { + false => None, + true => Some(buffer.get_u16()?), + }, + tv_name: match (value & 0x40) > 0 { + false => None, + true => Some(buffer.get_string_utf8()?), + }, + keywords: match (value & 0x20) > 0 { + false => None, + true => Some(buffer.get_string_utf8()?), + }, + game_id: match (value & 0x01) > 0 { + false => None, + true => { + let gid = buffer.get_u64()?; + appid = (gid & ((1 << 24) - 1)) as u32; + + Some(gid) + } + }, + }) + } + }; + + Ok(ServerInfo { + protocol, + name, + map, + folder, + game, + appid, + players_online: players, + players_maximum: max_players, + players_bots: bots, + server_type, + environment_type, + has_password, + vac_secured, + the_ship, + version, + extra_data, + is_mod: false, + mod_data: None, + }) + } + + /// Get the server player's. + fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult> { + let mut buffer = self.get_request_data(engine, protocol, Request::Players)?; + + let count = buffer.get_u8()? as usize; + let mut players: Vec = Vec::with_capacity(count); + + for _ in 0 .. count { + buffer.move_position_ahead(1); //skip the index byte + + players.push(ServerPlayer { + name: buffer.get_string_utf8()?, + score: buffer.get_u32()?, + duration: buffer.get_f32()?, + deaths: match *engine == SteamApp::TS.as_engine() { + false => None, + true => Some(buffer.get_u32()?), + }, + money: match *engine == SteamApp::TS.as_engine() { + false => None, + true => Some(buffer.get_u32()?), + }, + }); + } + + Ok(players) + } + + /// Get the server's rules. + fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult> { + let mut buffer = self.get_request_data(engine, protocol, Request::Rules)?; + + let count = buffer.get_u16()? as usize; + let mut rules: HashMap = HashMap::with_capacity(count); + + for _ in 0 .. count { + let name = buffer.get_string_utf8()?; + let value = buffer.get_string_utf8()?; + + rules.insert(name, value); + } + + if *engine == SteamApp::ROR2.as_engine() { + rules.remove("Test"); + } + + Ok(rules) + } +} + +/// Query a server by providing the address, the port, the app, gather and +/// timeout settings. Providing None to the settings results in using the +/// default values for them +/// (GatherSettings::[default](GatheringSettings::default), +/// TimeoutSettings::[default](TimeoutSettings::default)). +pub fn query( + address: &str, + port: u16, + engine: Engine, + gather_settings: Option, + timeout_settings: Option, +) -> GDResult { + let response_gather_settings = gather_settings.unwrap_or_default(); + get_response( + address, + port, + engine, + response_gather_settings, + timeout_settings, + ) +} + +fn get_response( + address: &str, + port: u16, + engine: Engine, + gather_settings: GatheringSettings, + timeout_settings: Option, +) -> GDResult { + let mut client = ValveProtocol::new(address, port, timeout_settings)?; + + let info = client.get_server_info(&engine)?; + let protocol = info.protocol; + + if let Engine::Source(Some(appids)) = &engine { + let mut is_specified_id = false; + + if appids.0 == info.appid { + is_specified_id = true; + } else if let Some(dedicated_appid) = appids.1 { + if dedicated_appid == info.appid { + is_specified_id = true; + } + } + + if !is_specified_id { + return Err(BadGame(format!("AppId: {}", info.appid))); + } + } + + Ok(Response { + info, + players: match gather_settings.players { + false => None, + true => Some(client.get_server_players(&engine, protocol)?), + }, + rules: match gather_settings.rules { + false => None, + true => Some(client.get_server_rules(&engine, protocol)?), + }, + }) +} diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 681de23..5515ca6 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,444 +1,441 @@ -use std::collections::HashMap; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// The type of the server. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Server { - Dedicated, - NonDedicated, - TV, -} - -/// The Operating System that the server is on. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Environment { - Linux, - Windows, - Mac, -} - -/// A query response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] -pub struct Response { - pub info: ServerInfo, - pub players: Option>, - pub rules: Option>, -} - -/// General server information's. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ServerInfo { - /// Protocol used by the server. - pub protocol: u8, - /// Name of the server. - pub name: String, - /// Map name. - pub map: String, - /// Name of the folder containing the game files. - pub folder: String, - /// The name of the game. - pub game: String, - /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. - pub appid: u32, - /// Number of players on the server. - pub players_online: u8, - /// Maximum number of players the server reports it can hold. - pub players_maximum: u8, - /// Number of bots on the server. - pub players_bots: 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, - /// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data - pub the_ship: Option, - /// Version of the game installed on the server. - pub version: String, - /// Some extra data that the server might provide or not. - pub extra_data: Option, - /// GoldSrc only: Indicates whether the hosted game is a mod. - pub is_mod: bool, - /// GoldSrc only: If the game is a mod, provide additional data. - pub mod_data: Option, -} - -/// A server player. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct ServerPlayer { - /// Player's name. - pub name: String, - /// General score. - pub score: u32, - /// How long a player has been in the server (seconds). - pub duration: f32, - /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): deaths count - pub deaths: Option, //the_ship - /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): money amount - pub money: Option, //the_ship -} - -/// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct TheShip { - pub mode: u8, - pub witnesses: u8, - pub duration: u8, -} - -/// Some extra data that the server might provide or not. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ExtraData { - /// The server's game port number. - pub port: Option, - /// Server's SteamID. - pub steam_id: Option, - /// SourceTV's port. - pub tv_port: Option, - /// SourceTV's name. - pub tv_name: Option, - /// Keywords that describe the server according to it. - pub keywords: Option, - /// The server's 64-bit GameID. - pub game_id: Option, -} - -/// Data related to GoldSrc Mod response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ModData { - pub link: String, - pub download_link: String, - pub version: u32, - pub size: u32, - pub multiplayer_only: bool, - pub has_own_dll: bool, -} - -pub(crate) type ExtractedData = ( - Option, - Option, - Option, - Option, - Option, -); - -pub(crate) fn get_optional_extracted_data( - data: Option, -) -> ExtractedData { - match data { - None => (None, None, None, None, None), - Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords), - } -} - -/// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). -#[derive(Eq, PartialEq, Copy, Clone)] -#[repr(u8)] -pub(crate) enum Request { - /// Known as `A2S_INFO` - Info = 0x54, - /// Known as `A2S_PLAYERS` - Players = 0x55, - /// Known as `A2S_RULES` - Rules = 0x56, -} - -/// Supported steam apps -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum SteamApp { - /// Counter-Strike - CS, - /// Team Fortress Classic - TFC, - /// Day of Defeat - DOD, - /// Counter-Strike: Condition Zero - CSCZ, - /// Counter-Strike: Source - CSS, - /// Day of Defeat: Source - DODS, - /// Half-Life 2 Deathmatch - HL2DM, - /// Half-Life Deathmatch: Source - HLDMS, - /// Team Fortress 2 - TF2, - /// Left 4 Dead - L4D, - /// Left 4 Dead - L4D2, - /// Alien Swarm - ALIENS, - /// Counter-Strike: Global Offensive - CSGO, - /// The Ship - TS, - /// Garry's Mod - GM, - /// Age of Chivalry - AOC, - /// Insurgency: Modern Infantry Combat - INSMIC, - /// ARMA 2: Operation Arrowhead - ARMA2OA, - /// Project Zomboid - PZ, - /// Insurgency - INS, - /// Sven Co-op - SC, - /// 7 Days To Die - SDTD, - /// Rust - RUST, - /// Vallistic Overkill - BO, - /// Don't Starve Together - DST, - /// BrainBread 2 - BB2, - /// Codename CURE - CCURE, - /// Black Mesa - BM, - /// Colony Survival - COSU, - /// Avorion - AVORION, - /// Day of Infamy - DOI, - /// The Forest - TF, - /// Unturned - UNTURNED, - /// ARK: Survival Evolved - ASE, - /// Battalion 1944 - BAT1944, - /// Insurgency: Sandstorm - INSS, - /// Alien Swarm: Reactive Drop - ASRD, - /// Risk of Rain 2 - ROR2, - /// Operation: Harsh Doorstop - OHD, - /// Onset - ONSET, - /// V Rising - VR, -} - -impl SteamApp { - /// Get the specified app as engine. - pub fn as_engine(&self) -> Engine { - match self { - SteamApp::CS => Engine::GoldSrc(false), //10 - SteamApp::TFC => Engine::GoldSrc(false), //20 - SteamApp::DOD => Engine::GoldSrc(false), //30 - SteamApp::CSCZ => Engine::GoldSrc(false), //80 - SteamApp::CSS => Engine::new_source(240), - SteamApp::DODS => Engine::new_source(300), - SteamApp::HL2DM => Engine::new_source(320), - SteamApp::HLDMS => Engine::new_source(360), - SteamApp::TF2 => Engine::new_source(440), - SteamApp::L4D => Engine::new_source(500), - SteamApp::L4D2 => Engine::new_source(550), - SteamApp::ALIENS => Engine::new_source(630), - SteamApp::CSGO => Engine::new_source(730), - SteamApp::TS => Engine::new_source(2400), - SteamApp::GM => Engine::new_source(4000), - SteamApp::AOC => Engine::new_source(17510), - SteamApp::INSMIC => Engine::new_source(17700), - SteamApp::ARMA2OA => Engine::new_source(33930), - SteamApp::PZ => Engine::new_source(108600), - SteamApp::INS => Engine::new_source(222880), - SteamApp::SC => Engine::GoldSrc(false), //225840 - SteamApp::SDTD => Engine::new_source(251570), - SteamApp::RUST => Engine::new_source(252490), - SteamApp::BO => Engine::new_source(296300), - SteamApp::DST => Engine::new_source(322320), - SteamApp::BB2 => Engine::new_source(346330), - SteamApp::CCURE => Engine::new_source(355180), - SteamApp::BM => Engine::new_source(362890), - SteamApp::COSU => Engine::new_source(366090), - SteamApp::AVORION => Engine::new_source(445220), - SteamApp::DOI => Engine::new_source(447820), - SteamApp::TF => Engine::new_source(556450), - SteamApp::UNTURNED => Engine::new_source(304930), - SteamApp::ASE => Engine::new_source(346110), - SteamApp::BAT1944 => Engine::new_source(489940), - SteamApp::INSS => Engine::new_source(581320), - SteamApp::ASRD => Engine::new_source(563560), - SteamApp::ROR2 => Engine::new_source(632360), - SteamApp::OHD => Engine::new_source_with_dedicated(736590, 950900), - SteamApp::ONSET => Engine::new_source(1105810), - SteamApp::VR => Engine::new_source(1604030), - } - } -} - -/// Engine type. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Engine { - /// A Source game, the argument represents the possible steam app ids, if its **None**, let - /// the query find it, if its **Some**, the query fails if the response id is not the first - /// one, which is the game app id, or the other one, which is the dedicated server app id. - Source(Option<(u32, Option)>), - /// A GoldSrc game, the argument indicates whether to enforce - /// requesting the obsolete A2S_INFO response or not. - GoldSrc(bool), -} - -impl Engine { - pub fn new_source(appid: u32) -> Self { - Engine::Source(Some((appid, None))) - } - - pub fn new_source_with_dedicated(appid: u32, dedicated_appid: u32) -> Self { - Engine::Source(Some((appid, Some(dedicated_appid)))) - } -} - -/// What data to gather, purely used only with the query function. -pub struct GatheringSettings { - pub players: bool, - pub rules: bool, -} - -impl Default for GatheringSettings { - /// Default values are true for both the players and the rules. - fn default() -> Self { - Self { - players: true, - rules: true, - } - } -} - -/// Generic response types that are used by many games, they are the protocol ones, but without the -/// unnecessary bits (example: the **The Ship**-only fields). -pub mod game { - use super::{Server, ServerPlayer}; - use crate::protocols::valve::types::get_optional_extracted_data; - use std::collections::HashMap; - - #[cfg(feature = "serde")] - use serde::{Deserialize, Serialize}; - - /// A player's details. - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[derive(Debug, Clone, PartialEq, PartialOrd)] - pub struct Player { - /// Player's name. - pub name: String, - /// Player's score. - pub score: u32, - /// How long a player has been in the server (seconds). - pub duration: f32, - } - - impl Player { - pub fn from_valve_response(player: &ServerPlayer) -> Self { - Self { - name: player.name.clone(), - score: player.score, - duration: player.duration, - } - } - } - - /// The query response. - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[derive(Debug, Clone, PartialEq)] - pub struct Response { - /// Protocol used by the server. - pub protocol: u8, - /// Name of the server. - pub name: String, - /// Map name. - pub map: String, - /// The name of the game. - pub game: String, - /// Server's app id. - pub appid: u32, - /// Number of players on the server. - pub players_online: u8, - /// Details about the server's players (not all players necessarily). - pub players_details: Vec, - /// Maximum number of players the server reports it can hold. - pub players_maximum: u8, - /// Number of bots on the server. - pub players_bots: u8, - /// Dedicated, NonDedicated or SourceTV - pub server_type: Server, - /// Indicates whether the server requires a password. - pub has_password: bool, - /// Indicated whether the server uses VAC. - pub vac_secured: bool, - /// Version of the game installed on the server. - pub version: String, - /// The server's reported connection port. - pub port: Option, - /// Server's SteamID. - pub steam_id: Option, - /// SourceTV's connection port. - pub tv_port: Option, - /// SourceTV's name. - pub tv_name: Option, - /// Keywords that describe the server according to it. - pub keywords: Option, - /// Server's rules. - pub rules: HashMap, - } - - impl Response { - pub fn new_from_valve_response(response: super::Response) -> Self { - let (port, steam_id, tv_port, tv_name, keywords) = - get_optional_extracted_data(response.info.extra_data); - - Self { - protocol: response.info.protocol, - name: response.info.name, - map: response.info.map, - game: response.info.game, - appid: response.info.appid, - players_online: response.info.players_online, - players_details: response - .players - .unwrap_or_default() - .iter() - .map(Player::from_valve_response) - .collect(), - players_maximum: response.info.players_maximum, - players_bots: response.info.players_bots, - server_type: response.info.server_type, - has_password: response.info.has_password, - vac_secured: response.info.vac_secured, - version: response.info.version, - port, - steam_id, - tv_port, - tv_name, - keywords, - rules: response.rules.unwrap_or_default(), - } - } - } -} +use std::collections::HashMap; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The type of the server. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Server { + Dedicated, + NonDedicated, + TV, +} + +/// The Operating System that the server is on. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Environment { + Linux, + Windows, + Mac, +} + +/// A query response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] +pub struct Response { + pub info: ServerInfo, + pub players: Option>, + pub rules: Option>, +} + +/// General server information's. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ServerInfo { + /// Protocol used by the server. + pub protocol: u8, + /// Name of the server. + pub name: String, + /// Map name. + pub map: String, + /// Name of the folder containing the game files. + pub folder: String, + /// The name of the game. + pub game: String, + /// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game. + pub appid: u32, + /// Number of players on the server. + pub players_online: u8, + /// Maximum number of players the server reports it can hold. + pub players_maximum: u8, + /// Number of bots on the server. + pub players_bots: 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, + /// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data + pub the_ship: Option, + /// Version of the game installed on the server. + pub version: String, + /// Some extra data that the server might provide or not. + pub extra_data: Option, + /// GoldSrc only: Indicates whether the hosted game is a mod. + pub is_mod: bool, + /// GoldSrc only: If the game is a mod, provide additional data. + pub mod_data: Option, +} + +/// A server player. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct ServerPlayer { + /// Player's name. + pub name: String, + /// General score. + pub score: u32, + /// How long a player has been in the server (seconds). + pub duration: f32, + /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): deaths count + pub deaths: Option, // the_ship + /// Only for [the ship](https://developer.valvesoftware.com/wiki/The_Ship): money amount + pub money: Option, // the_ship +} + +/// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship). +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct TheShip { + pub mode: u8, + pub witnesses: u8, + pub duration: u8, +} + +/// Some extra data that the server might provide or not. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ExtraData { + /// The server's game port number. + pub port: Option, + /// Server's SteamID. + pub steam_id: Option, + /// SourceTV's port. + pub tv_port: Option, + /// SourceTV's name. + pub tv_name: Option, + /// Keywords that describe the server according to it. + pub keywords: Option, + /// The server's 64-bit GameID. + pub game_id: Option, +} + +/// Data related to GoldSrc Mod response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ModData { + pub link: String, + pub download_link: String, + pub version: u32, + pub size: u32, + pub multiplayer_only: bool, + pub has_own_dll: bool, +} + +pub(crate) type ExtractedData = ( + Option, + Option, + Option, + Option, + Option, +); + +pub(crate) fn get_optional_extracted_data(data: Option) -> ExtractedData { + match data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords), + } +} + +/// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). +#[derive(Eq, PartialEq, Copy, Clone)] +#[repr(u8)] +pub(crate) enum Request { + /// Known as `A2S_INFO` + Info = 0x54, + /// Known as `A2S_PLAYERS` + Players = 0x55, + /// Known as `A2S_RULES` + Rules = 0x56, +} + +/// Supported steam apps +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum SteamApp { + /// Counter-Strike + CS, + /// Team Fortress Classic + TFC, + /// Day of Defeat + DOD, + /// Counter-Strike: Condition Zero + CSCZ, + /// Counter-Strike: Source + CSS, + /// Day of Defeat: Source + DODS, + /// Half-Life 2 Deathmatch + HL2DM, + /// Half-Life Deathmatch: Source + HLDMS, + /// Team Fortress 2 + TF2, + /// Left 4 Dead + L4D, + /// Left 4 Dead + L4D2, + /// Alien Swarm + ALIENS, + /// Counter-Strike: Global Offensive + CSGO, + /// The Ship + TS, + /// Garry's Mod + GM, + /// Age of Chivalry + AOC, + /// Insurgency: Modern Infantry Combat + INSMIC, + /// ARMA 2: Operation Arrowhead + ARMA2OA, + /// Project Zomboid + PZ, + /// Insurgency + INS, + /// Sven Co-op + SC, + /// 7 Days To Die + SDTD, + /// Rust + RUST, + /// Vallistic Overkill + BO, + /// Don't Starve Together + DST, + /// BrainBread 2 + BB2, + /// Codename CURE + CCURE, + /// Black Mesa + BM, + /// Colony Survival + COSU, + /// Avorion + AVORION, + /// Day of Infamy + DOI, + /// The Forest + TF, + /// Unturned + UNTURNED, + /// ARK: Survival Evolved + ASE, + /// Battalion 1944 + BAT1944, + /// Insurgency: Sandstorm + INSS, + /// Alien Swarm: Reactive Drop + ASRD, + /// Risk of Rain 2 + ROR2, + /// Operation: Harsh Doorstop + OHD, + /// Onset + ONSET, + /// V Rising + VR, +} + +impl SteamApp { + /// Get the specified app as engine. + pub fn as_engine(&self) -> Engine { + match self { + SteamApp::CS => Engine::GoldSrc(false), // 10 + SteamApp::TFC => Engine::GoldSrc(false), // 20 + SteamApp::DOD => Engine::GoldSrc(false), // 30 + SteamApp::CSCZ => Engine::GoldSrc(false), // 80 + SteamApp::CSS => Engine::new_source(240), + SteamApp::DODS => Engine::new_source(300), + SteamApp::HL2DM => Engine::new_source(320), + SteamApp::HLDMS => Engine::new_source(360), + SteamApp::TF2 => Engine::new_source(440), + SteamApp::L4D => Engine::new_source(500), + SteamApp::L4D2 => Engine::new_source(550), + SteamApp::ALIENS => Engine::new_source(630), + SteamApp::CSGO => Engine::new_source(730), + SteamApp::TS => Engine::new_source(2400), + SteamApp::GM => Engine::new_source(4000), + SteamApp::AOC => Engine::new_source(17510), + SteamApp::INSMIC => Engine::new_source(17700), + SteamApp::ARMA2OA => Engine::new_source(33930), + SteamApp::PZ => Engine::new_source(108600), + SteamApp::INS => Engine::new_source(222880), + SteamApp::SC => Engine::GoldSrc(false), // 225840 + SteamApp::SDTD => Engine::new_source(251570), + SteamApp::RUST => Engine::new_source(252490), + SteamApp::BO => Engine::new_source(296300), + SteamApp::DST => Engine::new_source(322320), + SteamApp::BB2 => Engine::new_source(346330), + SteamApp::CCURE => Engine::new_source(355180), + SteamApp::BM => Engine::new_source(362890), + SteamApp::COSU => Engine::new_source(366090), + SteamApp::AVORION => Engine::new_source(445220), + SteamApp::DOI => Engine::new_source(447820), + SteamApp::TF => Engine::new_source(556450), + SteamApp::UNTURNED => Engine::new_source(304930), + SteamApp::ASE => Engine::new_source(346110), + SteamApp::BAT1944 => Engine::new_source(489940), + SteamApp::INSS => Engine::new_source(581320), + SteamApp::ASRD => Engine::new_source(563560), + SteamApp::ROR2 => Engine::new_source(632360), + SteamApp::OHD => Engine::new_source_with_dedicated(736590, 950900), + SteamApp::ONSET => Engine::new_source(1105810), + SteamApp::VR => Engine::new_source(1604030), + } + } +} + +/// Engine type. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Engine { + /// A Source game, the argument represents the possible steam app ids, if + /// its **None**, let the query find it, if its **Some**, the query + /// fails if the response id is not the first one, which is the game app + /// id, or the other one, which is the dedicated server app id. + Source(Option<(u32, Option)>), + /// A GoldSrc game, the argument indicates whether to enforce + /// requesting the obsolete A2S_INFO response or not. + GoldSrc(bool), +} + +impl Engine { + pub fn new_source(appid: u32) -> Self { Engine::Source(Some((appid, None))) } + + pub fn new_source_with_dedicated(appid: u32, dedicated_appid: u32) -> Self { + Engine::Source(Some((appid, Some(dedicated_appid)))) + } +} + +/// What data to gather, purely used only with the query function. +pub struct GatheringSettings { + pub players: bool, + pub rules: bool, +} + +impl Default for GatheringSettings { + /// Default values are true for both the players and the rules. + fn default() -> Self { + Self { + players: true, + rules: true, + } + } +} + +/// Generic response types that are used by many games, they are the protocol +/// ones, but without the unnecessary bits (example: the **The Ship**-only +/// fields). +pub mod game { + use super::{Server, ServerPlayer}; + use crate::protocols::valve::types::get_optional_extracted_data; + use std::collections::HashMap; + + #[cfg(feature = "serde")] + use serde::{Deserialize, Serialize}; + + /// A player's details. + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq, PartialOrd)] + pub struct Player { + /// Player's name. + pub name: String, + /// Player's score. + pub score: u32, + /// How long a player has been in the server (seconds). + pub duration: f32, + } + + impl Player { + pub fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration, + } + } + } + + /// The query response. + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq)] + pub struct Response { + /// Protocol used by the server. + pub protocol: u8, + /// Name of the server. + pub name: String, + /// Map name. + pub map: String, + /// The name of the game. + pub game: String, + /// Server's app id. + pub appid: u32, + /// Number of players on the server. + pub players_online: u8, + /// Details about the server's players (not all players necessarily). + pub players_details: Vec, + /// Maximum number of players the server reports it can hold. + pub players_maximum: u8, + /// Number of bots on the server. + pub players_bots: u8, + /// Dedicated, NonDedicated or SourceTV + pub server_type: Server, + /// Indicates whether the server requires a password. + pub has_password: bool, + /// Indicated whether the server uses VAC. + pub vac_secured: bool, + /// Version of the game installed on the server. + pub version: String, + /// The server's reported connection port. + pub port: Option, + /// Server's SteamID. + pub steam_id: Option, + /// SourceTV's connection port. + pub tv_port: Option, + /// SourceTV's name. + pub tv_name: Option, + /// Keywords that describe the server according to it. + pub keywords: Option, + /// Server's rules. + pub rules: HashMap, + } + + impl Response { + pub fn new_from_valve_response(response: super::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = get_optional_extracted_data(response.info.extra_data); + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + appid: response.info.appid, + players_online: response.info.players_online, + players_details: response + .players + .unwrap_or_default() + .iter() + .map(Player::from_valve_response) + .collect(), + players_maximum: response.info.players_maximum, + players_bots: response.info.players_bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap_or_default(), + } + } + } +} diff --git a/src/socket.rs b/src/socket.rs index 493b3e3..37e78b6 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -1,165 +1,168 @@ -use crate::protocols::types::TimeoutSettings; -use crate::utils::address_and_port_as_string; -use crate::GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}; -use crate::GDResult; -use std::io::{Read, Write}; -use std::net; - -const DEFAULT_PACKET_SIZE: usize = 1024; - -pub trait Socket { - fn new(address: &str, port: u16) -> GDResult - where - Self: Sized; - - fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()>; - - fn send(&mut self, data: &[u8]) -> GDResult<()>; - fn receive(&mut self, size: Option) -> GDResult>; -} - -pub struct TcpSocket { - socket: net::TcpStream, -} - -impl Socket for TcpSocket { - fn new(address: &str, port: u16) -> GDResult { - let complete_address = address_and_port_as_string(address, port); - - Ok(Self { - socket: net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)?, - }) - } - - fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { - let settings = timeout_settings.unwrap_or_default(); - self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new - self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error - - Ok(()) - } - - fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket.write(data).map_err(|_| PacketSend)?; - Ok(()) - } - - fn receive(&mut self, size: Option) -> GDResult> { - let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); - self.socket - .read_to_end(&mut buf) - .map_err(|_| PacketReceive)?; - - Ok(buf) - } -} - -pub struct UdpSocket { - socket: net::UdpSocket, - complete_address: String, -} - -impl Socket for UdpSocket { - fn new(address: &str, port: u16) -> GDResult { - let complete_address = address_and_port_as_string(address, port); - let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|_| SocketBind)?; - - Ok(Self { - socket, - complete_address, - }) - } - - fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { - let settings = timeout_settings.unwrap_or_default(); - self.socket.set_read_timeout(settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new - self.socket.set_write_timeout(settings.get_write()).unwrap(); //checks if these are 0 and throws an error - - Ok(()) - } - - fn send(&mut self, data: &[u8]) -> GDResult<()> { - self.socket - .send_to(data, &self.complete_address) - .map_err(|_| PacketSend)?; - Ok(()) - } - - fn receive(&mut self, size: Option) -> GDResult> { - let mut buf: Vec = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; - let (number_of_bytes_received, _) = - self.socket.recv_from(&mut buf).map_err(|_| PacketReceive)?; - - Ok(buf[..number_of_bytes_received].to_vec()) - } -} - -#[cfg(test)] -mod tests { - use std::thread; - - use super::*; - - #[test] - fn test_tcp_socket_send_and_receive() { - // Spawn a thread to run the server - let listener = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let bound_address = listener.local_addr().unwrap(); - let server_thread = thread::spawn(move || { - let (mut stream, _) = listener.accept().unwrap(); - let mut buf = [0; 1024]; - stream.read(&mut buf).unwrap(); - stream.write(&buf).unwrap(); - }); - - // Create a TCP socket and send a message to the server - let mut socket = TcpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); - let message = b"hello, world!"; - socket.send(message).unwrap(); - - // Receive the response from the server - let received_message: Vec = socket - .receive(None) - .unwrap() - // Iterate over the buffer and remove 0s that are alone in the buffer - // just added to pass default size - .into_iter() - .filter(|&x| x != 0) - .collect(); - - server_thread.join().expect("server thread panicked"); - - assert_eq!(message, &received_message[..]); - } - - #[test] - fn test_udp_socket_send_and_receive() { - // Spawn a thread to run the server - let socket = net::UdpSocket::bind("127.0.0.1:0").unwrap(); - let bound_address = socket.local_addr().unwrap(); - let server_thread = thread::spawn(move || { - let mut buf = [0; 1024]; - let (_, src_addr) = socket.recv_from(&mut buf).unwrap(); - socket.send_to(&buf, src_addr).unwrap(); - }); - - // Create a UDP socket and send a message to the server - let mut socket = UdpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); - let message = b"hello, world!"; - socket.send(message).unwrap(); - - // Receive the response from the server - let received_message: Vec = socket - .receive(None) - .unwrap() - // Iterate over the buffer and remove 0s that are alone in the buffer - // just added to pass default size - .into_iter() - .filter(|&x| x != 0) - .collect(); - - server_thread.join().expect("server thread panicked"); - - assert_eq!(message, &received_message[..]); - } -} +use crate::{ + protocols::types::TimeoutSettings, + utils::address_and_port_as_string, + GDError::{PacketReceive, PacketSend, SocketBind, SocketConnect}, + GDResult, +}; + +use std::{ + io::{Read, Write}, + net, +}; + +const DEFAULT_PACKET_SIZE: usize = 1024; + +pub trait Socket { + fn new(address: &str, port: u16) -> GDResult + where Self: Sized; + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()>; + + fn send(&mut self, data: &[u8]) -> GDResult<()>; + fn receive(&mut self, size: Option) -> GDResult>; +} + +pub struct TcpSocket { + socket: net::TcpStream, +} + +impl Socket for TcpSocket { + fn new(address: &str, port: u16) -> GDResult { + let complete_address = address_and_port_as_string(address, port); + + Ok(Self { + socket: net::TcpStream::connect(complete_address).map_err(|_| SocketConnect)?, + }) + } + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { + let settings = timeout_settings.unwrap_or_default(); + self.socket.set_read_timeout(settings.get_read()).unwrap(); // unwrapping because TimeoutSettings::new + self.socket.set_write_timeout(settings.get_write()).unwrap(); // checks if these are 0 and throws an error + + Ok(()) + } + + fn send(&mut self, data: &[u8]) -> GDResult<()> { + self.socket.write(data).map_err(|_| PacketSend)?; + Ok(()) + } + + fn receive(&mut self, size: Option) -> GDResult> { + let mut buf = Vec::with_capacity(size.unwrap_or(DEFAULT_PACKET_SIZE)); + self.socket + .read_to_end(&mut buf) + .map_err(|_| PacketReceive)?; + + Ok(buf) + } +} + +pub struct UdpSocket { + socket: net::UdpSocket, + complete_address: String, +} + +impl Socket for UdpSocket { + fn new(address: &str, port: u16) -> GDResult { + let complete_address = address_and_port_as_string(address, port); + let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|_| SocketBind)?; + + Ok(Self { + socket, + complete_address, + }) + } + + fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()> { + let settings = timeout_settings.unwrap_or_default(); + self.socket.set_read_timeout(settings.get_read()).unwrap(); // unwrapping because TimeoutSettings::new + self.socket.set_write_timeout(settings.get_write()).unwrap(); // checks if these are 0 and throws an error + + Ok(()) + } + + fn send(&mut self, data: &[u8]) -> GDResult<()> { + self.socket + .send_to(data, &self.complete_address) + .map_err(|_| PacketSend)?; + Ok(()) + } + + fn receive(&mut self, size: Option) -> GDResult> { + let mut buf: Vec = vec![0; size.unwrap_or(DEFAULT_PACKET_SIZE)]; + let (number_of_bytes_received, _) = self.socket.recv_from(&mut buf).map_err(|_| PacketReceive)?; + + Ok(buf[.. number_of_bytes_received].to_vec()) + } +} + +#[cfg(test)] +mod tests { + use std::thread; + + use super::*; + + #[test] + fn test_tcp_socket_send_and_receive() { + // Spawn a thread to run the server + let listener = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let bound_address = listener.local_addr().unwrap(); + let server_thread = thread::spawn(move || { + let (mut stream, _) = listener.accept().unwrap(); + let mut buf = [0; 1024]; + stream.read(&mut buf).unwrap(); + stream.write(&buf).unwrap(); + }); + + // Create a TCP socket and send a message to the server + let mut socket = TcpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); + let message = b"hello, world!"; + socket.send(message).unwrap(); + + // Receive the response from the server + let received_message: Vec = socket + .receive(None) + .unwrap() + // Iterate over the buffer and remove 0s that are alone in the buffer + // just added to pass default size + .into_iter() + .filter(|&x| x != 0) + .collect(); + + server_thread.join().expect("server thread panicked"); + + assert_eq!(message, &received_message[..]); + } + + #[test] + fn test_udp_socket_send_and_receive() { + // Spawn a thread to run the server + let socket = net::UdpSocket::bind("127.0.0.1:0").unwrap(); + let bound_address = socket.local_addr().unwrap(); + let server_thread = thread::spawn(move || { + let mut buf = [0; 1024]; + let (_, src_addr) = socket.recv_from(&mut buf).unwrap(); + socket.send_to(&buf, src_addr).unwrap(); + }); + + // Create a UDP socket and send a message to the server + let mut socket = UdpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); + let message = b"hello, world!"; + socket.send(message).unwrap(); + + // Receive the response from the server + let received_message: Vec = socket + .receive(None) + .unwrap() + // Iterate over the buffer and remove 0s that are alone in the buffer + // just added to pass default size + .into_iter() + .filter(|&x| x != 0) + .collect(); + + server_thread.join().expect("server thread panicked"); + + assert_eq!(message, &received_message[..]); + } +} diff --git a/src/utils.rs b/src/utils.rs index e55b052..42a0033 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,39 +1,41 @@ -use std::cmp::Ordering; -use crate::GDResult; -use crate::GDError::{PacketOverflow, PacketUnderflow}; - -pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { - match size.cmp(&expected) { - Ordering::Greater => Err(PacketOverflow), - Ordering::Less => Err(PacketUnderflow), - Ordering::Equal => Ok(()) - } -} - -pub fn address_and_port_as_string(address: &str, port: u16) -> String { - format!("{}:{}", address, port) -} - -pub fn u8_lower_upper(n: u8) -> (u8, u8) { - (n & 15, n >> 4) -} - -#[cfg(test)] -mod tests { - #[test] - fn address_and_port_as_string() { - assert_eq!(super::address_and_port_as_string("192.168.0.1", 27015), "192.168.0.1:27015"); - } - - #[test] - fn u8_lower_upper() { - assert_eq!(super::u8_lower_upper(171), (11, 10)); - } - - #[test] - fn error_by_expected_size() { - assert!(super::error_by_expected_size(69, 69).is_ok()); - assert!(super::error_by_expected_size(69, 68).is_err()); - assert!(super::error_by_expected_size(69, 70).is_err()); - } -} +use crate::{ + GDError::{PacketOverflow, PacketUnderflow}, + GDResult, +}; + +use std::cmp::Ordering; + +pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { + match size.cmp(&expected) { + Ordering::Greater => Err(PacketOverflow), + Ordering::Less => Err(PacketUnderflow), + Ordering::Equal => Ok(()), + } +} + +pub fn address_and_port_as_string(address: &str, port: u16) -> String { format!("{}:{}", address, port) } + +pub fn u8_lower_upper(n: u8) -> (u8, u8) { (n & 15, n >> 4) } + +#[cfg(test)] +mod tests { + #[test] + fn address_and_port_as_string() { + assert_eq!( + super::address_and_port_as_string("192.168.0.1", 27015), + "192.168.0.1:27015" + ); + } + + #[test] + fn u8_lower_upper() { + assert_eq!(super::u8_lower_upper(171), (11, 10)); + } + + #[test] + fn error_by_expected_size() { + assert!(super::error_by_expected_size(69, 69).is_ok()); + assert!(super::error_by_expected_size(69, 68).is_err()); + assert!(super::error_by_expected_size(69, 70).is_err()); + } +} From 786da81ea5a4a851c577281b852a9a417c1d04de Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 17 Apr 2023 15:10:51 +0300 Subject: [PATCH 174/597] [Protocol] Add GameSpy 3 support. (#25) * [Protocol] Gamespy3 initial code * [Protocol] Add rest of challenge solving * [Protocol] Remove unused stuff * [Protocol] Remove adding unused bytes * [Protocol] Clean up code * [Protocol] Make gs3 a struct * [Protocol] Add initial key-value parsing * [Protocol] Manage multiple packets * [Protocol] Split server vars and other vars * Revert "[Protocol] Split server vars and other vars" This reverts commit 9a930aeb68802fcf3d0908a2e031dfea054d37d0. * [Protocol] Proper packet management and initial response struct * [Protocol] Fix players_minimum * [Protocol] Fix server vars to parse only the first packet * [Protocol] Update CHANGELOG.md * [Protocol] Initial player parsing * [Protocol] Split GS one and three * [Protocol] Add common code file * [Protocol] Change static to const * [Protocol] Fix players_online and break on data to map on empty key * [Protocol] Remove unused types and printlns * [Protocol] Add teams parsing * [Protocol] Split key_values and parsing data * [Crate] Update PROTOCOLS.md --- CHANGELOG.md | 2 + PROTOCOLS.md | 10 +- examples/master_querant.rs | 2 + src/bufferer.rs | 12 + src/protocols/gamespy/common.rs | 17 + src/protocols/gamespy/mod.rs | 10 +- src/protocols/gamespy/protocol/mod.rs | 2 - src/protocols/gamespy/protocols/mod.rs | 5 + src/protocols/gamespy/protocols/one/mod.rs | 5 + .../one.rs => protocols/one/protocol.rs} | 30 +- .../gamespy/{ => protocols/one}/types.rs | 2 +- src/protocols/gamespy/protocols/three/mod.rs | 5 + .../gamespy/protocols/three/protocol.rs | 351 ++++++++++++++++++ .../gamespy/protocols/three/types.rs | 42 +++ 14 files changed, 459 insertions(+), 36 deletions(-) create mode 100644 src/protocols/gamespy/common.rs delete mode 100644 src/protocols/gamespy/protocol/mod.rs create mode 100644 src/protocols/gamespy/protocols/mod.rs create mode 100644 src/protocols/gamespy/protocols/one/mod.rs rename src/protocols/gamespy/{protocol/one.rs => protocols/one/protocol.rs} (91%) rename src/protocols/gamespy/{ => protocols/one}/types.rs (96%) create mode 100644 src/protocols/gamespy/protocols/three/mod.rs create mode 100644 src/protocols/gamespy/protocols/three/protocol.rs create mode 100644 src/protocols/gamespy/protocols/three/types.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ad27d7..9a8d49e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Crate: Protocols: - GameSpy 1: Add key `admin` as a possible variable for `admin_name`. +- GameSpy 3 support. Games: - [Serious Sam](https://www.gog.com/game/serious_sam_the_first_encounter) support. @@ -16,6 +17,7 @@ Games: ### Breaking: Protocols: - Valve: Request type enums have been renamed from all caps to starting-only uppercase, ex: `INFO` to `Info` +- GameSpy 1: `players_minimum` is now an `Option` instead of an `u8` # 0.2.1 - 03/03/2023 ### Changes: diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 53f5482..39b7de1 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,11 +1,11 @@ A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft). # Supported protocols: -| 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. | -| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
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) | 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. | +| 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. | +| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
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. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index a7052f5..94ee720 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -165,6 +165,8 @@ fn main() -> GDResult<()> { "ut" => println!("{:#?}", ut::query(ip, port)), "bf1942" => println!("{:#?}", bf1942::query(ip, port)), "ss" => println!("{:#?}", ss::query(ip, port)), + "_gamespy3" => println!("{:#?}", gamespy::three::query(ip, port.unwrap(), None)), + "_gamespy3_vars" => println!("{:#?}", gamespy::three::query_vars(ip, port.unwrap(), None)), _ => panic!("Undefined game: {}", args[1]), }; diff --git a/src/bufferer.rs b/src/bufferer.rs index 00fae6b..9daf84e 100644 --- a/src/bufferer.rs +++ b/src/bufferer.rs @@ -108,6 +108,18 @@ impl Bufferer { Ok(value) } + pub fn get_string_utf8_optional(&mut self) -> GDResult { + match self.get_string_utf8() { + Ok(data) => Ok(data), + Err(e) => { + match e { + PacketUnderflow => Ok(String::new()), + x => Err(x), + } + } + } + } + pub fn get_string_utf8_unended(&mut self) -> GDResult { let sub_buf = self.remaining_data(); if sub_buf.is_empty() { diff --git a/src/protocols/gamespy/common.rs b/src/protocols/gamespy/common.rs new file mode 100644 index 0000000..5c1950d --- /dev/null +++ b/src/protocols/gamespy/common.rs @@ -0,0 +1,17 @@ +use crate::{GDError, GDResult}; +use std::collections::HashMap; + +pub fn has_password(server_vars: &mut HashMap) -> GDResult { + let password_value = server_vars + .remove("password") + .ok_or(GDError::PacketBad)? + .to_lowercase(); + + if let Ok(has) = password_value.parse::() { + return Ok(has); + } + + let as_numeral: u8 = password_value.parse().map_err(|_| GDError::TypeParse)?; + + Ok(as_numeral != 0) +} diff --git a/src/protocols/gamespy/mod.rs b/src/protocols/gamespy/mod.rs index 3d98ab9..1f5150f 100644 --- a/src/protocols/gamespy/mod.rs +++ b/src/protocols/gamespy/mod.rs @@ -1,7 +1,5 @@ -/// The implementation. -pub mod protocol; -/// All types used by the implementation. -pub mod types; +mod common; +/// The implementations. +pub mod protocols; -pub use protocol::*; -pub use types::*; +pub use protocols::*; diff --git a/src/protocols/gamespy/protocol/mod.rs b/src/protocols/gamespy/protocol/mod.rs deleted file mode 100644 index 9f32c0a..0000000 --- a/src/protocols/gamespy/protocol/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -/// GameSpy 1 -pub mod one; diff --git a/src/protocols/gamespy/protocols/mod.rs b/src/protocols/gamespy/protocols/mod.rs new file mode 100644 index 0000000..c1d250c --- /dev/null +++ b/src/protocols/gamespy/protocols/mod.rs @@ -0,0 +1,5 @@ +pub mod one; +pub mod three; + +pub use one::*; +pub use three::*; diff --git a/src/protocols/gamespy/protocols/one/mod.rs b/src/protocols/gamespy/protocols/one/mod.rs new file mode 100644 index 0000000..14c6b48 --- /dev/null +++ b/src/protocols/gamespy/protocols/one/mod.rs @@ -0,0 +1,5 @@ +pub mod protocol; +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/src/protocols/gamespy/protocol/one.rs b/src/protocols/gamespy/protocols/one/protocol.rs similarity index 91% rename from src/protocols/gamespy/protocol/one.rs rename to src/protocols/gamespy/protocols/one/protocol.rs index e348cec..5a6d87e 100644 --- a/src/protocols/gamespy/protocol/one.rs +++ b/src/protocols/gamespy/protocols/one/protocol.rs @@ -1,7 +1,7 @@ use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ - gamespy::{Player, Response}, + gamespy::one::{Player, Response}, types::TimeoutSettings, }, socket::{Socket, UdpSocket}, @@ -9,6 +9,7 @@ use crate::{ GDResult, }; +use crate::protocols::gamespy::common::has_password; use std::collections::HashMap; fn get_server_values( @@ -173,21 +174,6 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u Ok(players) } -fn has_password(server_vars: &mut HashMap) -> GDResult { - let password_value = server_vars - .remove("password") - .ok_or(GDError::PacketBad)? - .to_lowercase(); - - if let Ok(has) = password_value.parse::() { - return Ok(has); - } - - let as_numeral: u8 = password_value.parse().map_err(|_| GDError::TypeParse)?; - - Ok(as_numeral != 0) -} - /// If there are parsing problems using the `query` function, you can directly /// get the server's values using this function. pub fn query_vars( @@ -209,6 +195,10 @@ pub fn query(address: &str, port: u16, timeout_settings: Option .ok_or(GDError::PacketBad)? .parse() .map_err(|_| GDError::TypeParse)?; + let players_minimum = match server_vars.remove("minplayers") { + None => None, + Some(v) => Some(v.parse::().map_err(|_| GDError::TypeParse)?), + }; let players = extract_players(&mut server_vars, players_maximum)?; @@ -225,15 +215,11 @@ pub fn query(address: &str, port: u16, timeout_settings: Option game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, players_maximum, players_online: players.len(), - players_minimum: server_vars - .remove("minplayers") - .unwrap_or_else(|| "0".to_string()) - .parse() - .map_err(|_| GDError::TypeParse)?, + players_minimum, players, tournament: server_vars .remove("tournament") - .unwrap_or_else(|| "true".to_string()) + .unwrap_or("true".to_string()) .to_lowercase() .parse() .map_err(|_| GDError::TypeParse)?, diff --git a/src/protocols/gamespy/types.rs b/src/protocols/gamespy/protocols/one/types.rs similarity index 96% rename from src/protocols/gamespy/types.rs rename to src/protocols/gamespy/protocols/one/types.rs index 8fcefeb..7f328dd 100644 --- a/src/protocols/gamespy/types.rs +++ b/src/protocols/gamespy/protocols/one/types.rs @@ -34,7 +34,7 @@ pub struct Response { pub game_version: String, pub players_maximum: usize, pub players_online: usize, - pub players_minimum: u8, + pub players_minimum: Option, pub players: Vec, pub tournament: bool, pub unused_entries: HashMap, diff --git a/src/protocols/gamespy/protocols/three/mod.rs b/src/protocols/gamespy/protocols/three/mod.rs new file mode 100644 index 0000000..14c6b48 --- /dev/null +++ b/src/protocols/gamespy/protocols/three/mod.rs @@ -0,0 +1,5 @@ +pub mod protocol; +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/src/protocols/gamespy/protocols/three/protocol.rs b/src/protocols/gamespy/protocols/three/protocol.rs new file mode 100644 index 0000000..c834d97 --- /dev/null +++ b/src/protocols/gamespy/protocols/three/protocol.rs @@ -0,0 +1,351 @@ +use crate::bufferer::{Bufferer, Endianess}; +use crate::protocols::gamespy::common::has_password; +use crate::protocols::gamespy::three::{Player, Response}; +use crate::protocols::gamespy::Team; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, UdpSocket}; +use crate::{GDError, GDResult}; +use std::collections::HashMap; + +const THIS_SESSION_ID: u32 = 1; + +struct RequestPacket { + header: u16, + kind: u8, + session_id: u32, + challenge: Option, + payload: Option<[u8; 4]>, +} + +impl RequestPacket { + fn to_bytes(self) -> Vec { + let mut packet: Vec = Vec::with_capacity(7); + packet.extend_from_slice(&self.header.to_be_bytes()); + packet.push(self.kind); + packet.extend_from_slice(&self.session_id.to_be_bytes()); + + if let Some(challenge) = self.challenge { + packet.extend_from_slice(&challenge.to_be_bytes()); + } + + if let Some(payload) = self.payload { + packet.extend_from_slice(&payload); + } + + packet + } +} + +struct GameSpy3 { + socket: UdpSocket, +} + +const PACKET_SIZE: usize = 2048; + +impl GameSpy3 { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { socket }) + } + + fn receive(&mut self, size: Option, kind: u8) -> GDResult { + let received = self.socket.receive(size.or(Some(PACKET_SIZE)))?; + let mut buf = Bufferer::new_with_data(Endianess::Big, &received); + + if buf.get_u8()? != kind { + return Err(GDError::PacketBad); + } + + if buf.get_u32()? != THIS_SESSION_ID { + return Err(GDError::PacketBad); + } + + Ok(buf) + } + + fn make_initial_handshake(&mut self) -> GDResult> { + self.socket.send( + &RequestPacket { + header: 65277, + kind: 9, + session_id: THIS_SESSION_ID, + challenge: None, + payload: None, + } + .to_bytes(), + )?; + + let mut buf = self.receive(Some(16), 9)?; + + let challenge_as_string = buf.get_string_utf8()?; + let challenge = challenge_as_string + .parse() + .map_err(|_| GDError::TypeParse)?; + + Ok(match challenge == 0 { + true => None, + false => Some(challenge), + }) + } + + fn send_data_request(&mut self, challenge: Option) -> GDResult<()> { + self.socket.send( + &RequestPacket { + header: 65277, + kind: 0, + session_id: THIS_SESSION_ID, + challenge, + payload: Some([0xff, 0xff, 0xff, 0x01]), + } + .to_bytes(), + ) + } +} + +fn get_server_packets(address: &str, port: u16, timeout_settings: Option) -> GDResult>> { + let mut gs3 = GameSpy3::new(address, port, timeout_settings)?; + + let challenge = gs3.make_initial_handshake()?; + gs3.send_data_request(challenge)?; + + let mut values: Vec> = Vec::new(); + + let mut expected_number_of_packets: Option = None; + + while expected_number_of_packets.is_none() || values.len() != expected_number_of_packets.unwrap() { + let mut buf = gs3.receive(None, 0)?; + + if buf.get_string_utf8()? != "splitnum" { + return Err(GDError::PacketBad); + } + + let id = buf.get_u8()?; + let is_last = (id & 0x80) > 0; + let packet_id = (id & 0x7f) as usize; + buf.move_position_ahead(1); //unknown byte regarding packet no. + + if is_last { + expected_number_of_packets = Some(packet_id + 1); + } + + while values.len() <= packet_id { + values.push(Vec::new()); + } + + values[packet_id] = buf.remaining_data_vec(); + } + + if values.iter().any(|v| v.is_empty()) { + return Err(GDError::PacketBad); + } + + Ok(values) +} + +fn data_to_map(packet: &Vec) -> GDResult<(HashMap, Vec)> { + let mut vars = HashMap::new(); + + let mut buf = Bufferer::new_with_data(Endianess::Big, &packet); + while buf.remaining_length() > 0 { + let key = buf.get_string_utf8()?; + if key.is_empty() { + break; + } + + let value = buf.get_string_utf8_optional()?; + + vars.insert(key, value); + } + + Ok((vars, buf.remaining_data_vec())) +} + +/// If there are parsing problems using the `query` function, you can directly +/// get the server's values using this function. +pub fn query_vars( + address: &str, + port: u16, + timeout_settings: Option, +) -> GDResult> { + let packets = get_server_packets(address, port, timeout_settings)?; + + let mut vars = HashMap::new(); + + for packet in &packets { + let (key_values, _remaining_data) = data_to_map(packet)?; + vars.extend(key_values); + } + + Ok(vars) +} + +fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec)> { + let mut players_data: Vec> = vec![HashMap::new()]; + let mut teams_data: Vec> = vec![HashMap::new()]; + + for packet in packets { + let mut buf = Bufferer::new_with_data(Endianess::Little, &packet); + + while buf.remaining_length() > 0 { + if buf.get_u8()? < 3 { + continue; + } + + buf.move_position_backward(1); + + let field = buf.get_string_utf8()?; + if field.is_empty() { + continue; + } + + let field_split: Vec<&str> = field.split('_').collect(); + let field_name = field_split.get(0).ok_or(GDError::PacketBad)?; + if !["player", "score", "ping", "team", "deaths", "pid", "skill"].contains(field_name) { + continue; + } + + let field_type = match field_split.get(1) { + None => None, + Some(v) => { + match v.is_empty() { + true => None, + false => { + if v != &"t" { + Err(GDError::PacketBad)? + } + + Some(v) + } + } + } + }; + + let mut offset = buf.get_u8()? as usize; + + let data = match field_type.is_none() { + true => &mut players_data, + false => &mut teams_data, + }; + + while buf.remaining_length() > 0 { + let item = buf.get_string_utf8()?; + if item.is_empty() { + break; + } + + while data.len() <= offset { + data.push(HashMap::new()) + } + + let entry_data = data.get_mut(offset).unwrap(); + entry_data.insert(field_name.to_string(), item); + + offset += 1; + } + } + } + + let mut players: Vec = Vec::new(); + for player_data in players_data { + players.push(Player { + name: player_data + .get("player") + .ok_or(GDError::PacketBad)? + .to_string(), + score: player_data + .get("score") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::PacketBad)?, + ping: player_data + .get("ping") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::PacketBad)?, + team: player_data + .get("team") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::PacketBad)?, + deaths: player_data + .get("deaths") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::PacketBad)?, + skill: player_data + .get("skill") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::PacketBad)?, + }) + } + + let mut teams: Vec = Vec::new(); + for team_data in teams_data { + teams.push(Team { + name: team_data.get("team").ok_or(GDError::PacketBad)?.to_string(), + score: team_data + .get("score") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::PacketBad)?, + }) + } + + Ok((players, teams)) +} + +/// Query a server by providing the address, the port and timeout settings. +/// Providing None to the timeout settings results in using the default values. +/// (TimeoutSettings::[default](TimeoutSettings::default)). +pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let packets = get_server_packets(address, port, timeout_settings)?; + + let (mut server_vars, remaining_data) = data_to_map(packets.get(0).ok_or(GDError::PacketBad)?)?; + + let mut remaining_data_packets = vec![remaining_data]; + remaining_data_packets.extend_from_slice(&packets[1 ..]); + let (players, teams) = parse_players_and_teams(remaining_data_packets)?; + + let players_maximum = server_vars + .remove("maxplayers") + .ok_or(GDError::PacketBad)? + .parse() + .map_err(|_| GDError::TypeParse)?; + let players_minimum = match server_vars.remove("minplayers") { + None => None, + Some(v) => Some(v.parse::().map_err(|_| GDError::TypeParse)?), + }; + 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, + } + } + }; + + Ok(Response { + name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?, + map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?, + has_password: has_password(&mut server_vars)?, + game_type: server_vars.remove("gametype").ok_or(GDError::PacketBad)?, + game_version: server_vars.remove("gamever").ok_or(GDError::PacketBad)?, + players_maximum, + players_online, + players_minimum, + players, + teams, + tournament: server_vars + .remove("tournament") + .unwrap_or("true".to_string()) + .to_lowercase() + .parse() + .map_err(|_| GDError::TypeParse)?, + unused_entries: server_vars, + }) +} diff --git a/src/protocols/gamespy/protocols/three/types.rs b/src/protocols/gamespy/protocols/three/types.rs new file mode 100644 index 0000000..164b19a --- /dev/null +++ b/src/protocols/gamespy/protocols/three/types.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// A player’s details. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Player { + pub name: String, + pub score: i32, + pub ping: u16, + pub team: u8, + pub deaths: u32, + pub skill: u32, +} + +/// A team's details +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Team { + pub name: String, + pub score: i32, +} + +/// A query response. +#[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 game_type: String, + pub game_version: String, + pub players_maximum: usize, + pub players_online: usize, + pub players_minimum: Option, + pub players: Vec, + pub teams: Vec, + pub tournament: bool, + pub unused_entries: HashMap, +} From 348147b415d8ea6b3fc6315481f4425805da20dc Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 22 Apr 2023 20:03:30 +0300 Subject: [PATCH 175/597] [Game] Add Frontlines: Fuel of War support. (#31) * [Game] Add initial files * [Game] Initial support * [Game] Add response struct * [Game] Add query_with_timeout * [Game] FFOW: Added some doc comments --- CHANGELOG.md | 1 + GAMES.md | 93 +++++++++++++-------------- examples/master_querant.rs | 2 + src/games/ffow.rs | 95 ++++++++++++++++++++++++++++ src/games/mod.rs | 2 + src/protocols/valve/protocol.rs | 109 ++++++++++---------------------- src/protocols/valve/types.rs | 68 ++++++++++++++++++++ 7 files changed, 248 insertions(+), 122 deletions(-) create mode 100644 src/games/ffow.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8d49e..7acb431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Protocols: Games: - [Serious Sam](https://www.gog.com/game/serious_sam_the_first_encounter) support. +- [Frontlines: Fuel of War](https://store.steampowered.com/app/9460/Frontlines_Fuel_of_War/) support. ### Breaking: Protocols: diff --git a/GAMES.md b/GAMES.md index 5b0105c..c0f6769 100644 --- a/GAMES.md +++ b/GAMES.md @@ -2,52 +2,53 @@ A supported game is defined as a game that has been successfully tested, other g Beware of the `Notes` column, as it contains information about query port offsets or other query requirements. # Supported games: -| Game | Use name | Protocol | Notes | -|------------------------------------|----------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Team Fortress 2 | TF2 | Valve Protocol | | -| The Ship | TS | Valve Protocol (*Altered) | | -| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server must have the cvar `host_players_show` set to `2` to get the full player list. | -| Counter-Strike: Source | CSS | Valve Protocol | | -| Day of Defeat: Source | DODS | Valve Protocol | | -| Left 4 Dead | L4D | Valve Protocol | | -| Left 4 Dead 2 | L4D2 | Valve Protocol | | -| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | -| Alien Swarm | ALIENS | Valve Protocol | | -| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | -| Insurgency | INS | Valve Protocol | | -| Insurgency: Sandstorm | INSS | Valve Protocol | Query port offset: 1. | -| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | -| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | -| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | -| Minecraft | MC | Proprietary | Bedrock edition provides a different response compared to the Java edition, query specifically for bedrock to get them, otherwise, only matching fields will be provided. | -| 7 Days To Die | SDTD | Valve Protocol | | -| ARK: Survival Evolved | ASE | Valve Protocol | | -| Unturned | UNTURNED | Valve Protocol | | -| The Forest | TF | Valve Protocol (GoldSrc) | Query port offset: 1. | -| Team Fortress Classic | TFC | Valve Protocol | | -| Sven Co-op | SC | Valve Protocol (GoldSrc) | | -| Rust | RUST | Valve Protocol | | -| Counter-Strike | CS | Valve Protocol (GoldSrc) | | -| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Query port offset: 1. | -| Day of Infamy | DOI | Valve Protocol | | -| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | -| Risk of Rain 2 | ROR2 | Valve Protocol | Query port offset: 1. | -| Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. It is strongly recommended to also query the rules, as it sends basic server info in them. | -| Black Mesa | BM | Valve Protocol | | -| Project Zomboid | PZ | Valve Protocol | | -| Age of Chivalry | AOC | Valve Protocol | | -| Don't Starve Together | DST | Valve Protocol | Query port is 27016. | -| Colony Survival | COLU | Valve Protocol | | -| Onset | ONSET | Valve Protocol | Query port is 7776. | -| Codename CURE | CCURE | Valve Protocol | | -| Ballistic Overkill | BO | Valve Protocol | Query port is 27016. | -| BrainBread 2 | BB2 | Valve Protocol | | -| Avorion | AVORION | Valve Protocol | Query port is 27020. | -| Operation: Harsh Doorstop | OHD | Valve Protocol | Query port is 27005. | -| V Rising | VR | Valve Protocol | Query port is 27016. | -| Unreal Tournament | UT | GameSpy 1 | Query Port offset: 1. | -| Battlefield 1942 | BF1942 | GameSpy 1 | Query port is 23000. | -| Serious Sam | SS | GameSpy 1 | Query Port offset: 1 | +| Game | Use name | Protocol | Notes | +|------------------------------------|----------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Team Fortress 2 | TF2 | Valve Protocol | | +| The Ship | TS | Valve Protocol (*Altered) | | +| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server must have the cvar `host_players_show` set to `2` to get the full player list. | +| Counter-Strike: Source | CSS | Valve Protocol | | +| Day of Defeat: Source | DODS | Valve Protocol | | +| Left 4 Dead | L4D | Valve Protocol | | +| Left 4 Dead 2 | L4D2 | Valve Protocol | | +| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | +| Alien Swarm | ALIENS | Valve Protocol | | +| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | +| Insurgency | INS | Valve Protocol | | +| Insurgency: Sandstorm | INSS | Valve Protocol | Query port offset: 1. | +| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | +| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | +| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | +| Minecraft | MC | Proprietary | Bedrock edition provides a different response compared to the Java edition, query specifically for bedrock to get them, otherwise, only matching fields will be provided. | +| 7 Days To Die | SDTD | Valve Protocol | | +| ARK: Survival Evolved | ASE | Valve Protocol | | +| Unturned | UNTURNED | Valve Protocol | | +| The Forest | TF | Valve Protocol (GoldSrc) | Query port offset: 1. | +| Team Fortress Classic | TFC | Valve Protocol | | +| Sven Co-op | SC | Valve Protocol (GoldSrc) | | +| Rust | RUST | Valve Protocol | | +| Counter-Strike | CS | Valve Protocol (GoldSrc) | | +| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Query port offset: 1. | +| Day of Infamy | DOI | Valve Protocol | | +| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | +| Risk of Rain 2 | ROR2 | Valve Protocol | Query port offset: 1. | +| Battalion 1944 | BAT1944 | Valve Protocol | Query port offset: 3. It is strongly recommended to also query the rules, as it sends basic server info in them. | +| Black Mesa | BM | Valve Protocol | | +| Project Zomboid | PZ | Valve Protocol | | +| Age of Chivalry | AOC | Valve Protocol | | +| Don't Starve Together | DST | Valve Protocol | Query port is 27016. | +| Colony Survival | COLU | Valve Protocol | | +| Onset | ONSET | Valve Protocol | Query port is 7776. | +| Codename CURE | CCURE | Valve Protocol | | +| Ballistic Overkill | BO | Valve Protocol | Query port is 27016. | +| BrainBread 2 | BB2 | Valve Protocol | | +| Avorion | AVORION | Valve Protocol | Query port is 27020. | +| Operation: Harsh Doorstop | OHD | Valve Protocol | Query port is 27005. | +| V Rising | VR | Valve Protocol | Query port is 27016. | +| Unreal Tournament | UT | GameSpy 1 | Query Port offset: 1. | +| Battlefield 1942 | BF1942 | GameSpy 1 | Query port is 23000. | +| Serious Sam | SS | GameSpy 1 | Query Port offset: 1. | +| Frontlines: Fuel of War | FFOW | Valve Protocol (Proprietary) | Query Port offset: 2. | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 94ee720..1bbb0a9 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -24,6 +24,7 @@ use gamedig::{ dods, doi, dst, + ffow, gm, hl2dm, hldms, @@ -167,6 +168,7 @@ fn main() -> GDResult<()> { "ss" => println!("{:#?}", ss::query(ip, port)), "_gamespy3" => println!("{:#?}", gamespy::three::query(ip, port.unwrap(), None)), "_gamespy3_vars" => println!("{:#?}", gamespy::three::query_vars(ip, port.unwrap(), None)), + "ffow" => println!("{:#?}", ffow::query(ip, port)), _ => panic!("Undefined game: {}", args[1]), }; diff --git a/src/games/ffow.rs b/src/games/ffow.rs new file mode 100644 index 0000000..3f7c1c2 --- /dev/null +++ b/src/games/ffow.rs @@ -0,0 +1,95 @@ +use crate::protocols::types::TimeoutSettings; +use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol}; +use crate::GDResult; +#[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: u8, + /// Name of the server. + pub name: String, + /// Map name. + pub active_mod: String, + /// Running game mode. + pub game_mode: String, + /// Description of the server. + pub description: String, + /// The version that the server is running on. + pub version: 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, +} + +pub fn query(address: &str, port: Option) -> GDResult { + query_with_timeout(address, port, TimeoutSettings::default()) +} + +pub fn query_with_timeout(address: &str, port: Option, timeout_settings: TimeoutSettings) -> GDResult { + let mut client = ValveProtocol::new(address, port.unwrap_or(5478), Some(timeout_settings))?; + let mut buffer = client.get_request_data( + &Engine::GoldSrc(true), + 0, + 0x46, + String::from("LSQ").into_bytes(), + )?; + + let protocol = buffer.get_u8()?; + let name = buffer.get_string_utf8()?; + let map = buffer.get_string_utf8()?; + let active_mod = buffer.get_string_utf8()?; + let game_mode = buffer.get_string_utf8()?; + let description = buffer.get_string_utf8()?; + let version = buffer.get_string_utf8()?; + buffer.move_position_ahead(2); + let players_online = buffer.get_u8()?; + let players_maximum = buffer.get_u8()?; + let server_type = Server::from_gldsrc(buffer.get_u8()?)?; + let environment_type = Environment::from_gldsrc(buffer.get_u8()?)?; + let has_password = buffer.get_u8()? == 1; + let vac_secured = buffer.get_u8()? == 1; + buffer.move_position_ahead(1); //average fps + let round = buffer.get_u8()?; + let rounds_maximum = buffer.get_u8()?; + let time_left = buffer.get_u16()?; + + Ok(Response { + protocol, + name, + active_mod, + game_mode, + description, + version, + map, + players_online, + players_maximum, + server_type, + environment_type, + has_password, + vac_secured, + round, + rounds_maximum, + time_left, + }) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index a5f76a4..338e89f 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -42,6 +42,8 @@ pub mod dods; pub mod doi; /// Don't Starve Together pub mod dst; +/// Frontlines: Fuel of War +pub mod ffow; /// Garry's Mod pub mod gm; /// Half-Life 2 Deathmatch diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 9e4944b..87847ed 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -28,61 +28,9 @@ use crate::{ use bzip2_rs::decoder::Decoder; +use crate::protocols::valve::Packet; use std::collections::HashMap; -#[derive(Debug, Clone)] -struct Packet { - pub header: u32, - pub kind: u8, - pub payload: Vec, -} - -impl Packet { - fn new(buffer: &mut Bufferer) -> GDResult { - Ok(Self { - header: buffer.get_u32()?, - kind: buffer.get_u8()?, - payload: buffer.remaining_data_vec(), - }) - } - - fn challenge(kind: Request, challenge: Vec) -> Self { - let mut initial = Packet::initial(kind); - - Self { - header: initial.header, - kind: initial.kind, - payload: match kind { - Request::Info => { - initial.payload.extend(challenge); - initial.payload - } - _ => challenge, - }, - } - } - - fn initial(kind: Request) -> Self { - Self { - header: 4294967295, // FF FF FF FF - kind: kind as u8, - payload: match kind { - Request::Info => String::from("Source Engine Query\0").into_bytes(), - _ => vec![0xFF, 0xFF, 0xFF, 0xFF], - }, - } - } - - fn to_bytes(&self) -> Vec { - let mut buf = Vec::from(self.header.to_be_bytes()); - - buf.push(self.kind); - buf.extend(&self.payload); - - buf - } -} - #[derive(Debug)] #[allow(dead_code)] //remove this later on struct SplitPacket { @@ -169,14 +117,14 @@ impl SplitPacket { } } -struct ValveProtocol { +pub(crate) struct ValveProtocol { socket: UdpSocket, } static PACKET_SIZE: usize = 6144; impl ValveProtocol { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { let socket = UdpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -208,22 +156,41 @@ impl ValveProtocol { } let mut new_packet_buffer = Bufferer::new_with_data(Endianess::Little, &main_packet.get_payload()?); - Ok(Packet::new(&mut new_packet_buffer)?) + Ok(Packet::new_from_bufferer(&mut new_packet_buffer)?) } else { - Packet::new(&mut buffer) + Packet::new_from_bufferer(&mut buffer) } } + pub fn get_kind_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult { + self.get_request_data(engine, protocol, kind as u8, kind.get_default_payload()) + } + /// Ask for a specific request only. - fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult { - let request_initial_packet = Packet::initial(kind).to_bytes(); + pub fn get_request_data( + &mut self, + engine: &Engine, + protocol: u8, + kind: u8, + payload: Vec, + ) -> GDResult { + let request_initial_packet = Packet::new(kind, payload).to_bytes(); self.socket.send(&request_initial_packet)?; let mut packet = self.receive(engine, protocol, PACKET_SIZE)?; while packet.kind == 0x41 { // 'A' - let challenge = packet.payload.clone(); - let challenge_packet = Packet::challenge(kind, challenge).to_bytes(); + let challenge = packet.payload; + + const INFO: u8 = Request::Info as u8; // hmm, this could be unwanted and problematic + let challenge_packet = Packet::new( + kind, + match kind { + INFO => [Request::Info.get_default_payload(), challenge].concat(), + _ => challenge, + }, + ) + .to_bytes(); self.socket.send(&challenge_packet)?; @@ -297,7 +264,7 @@ impl ValveProtocol { /// Get the server information's. fn get_server_info(&mut self, engine: &Engine) -> GDResult { - let mut buffer = self.get_request_data(engine, 0, Request::Info)?; + let mut buffer = self.get_kind_request_data(engine, 0, Request::Info)?; if let Engine::GoldSrc(force) = engine { if *force { @@ -314,18 +281,8 @@ impl ValveProtocol { let players = buffer.get_u8()?; let max_players = buffer.get_u8()?; let bots = buffer.get_u8()?; - let server_type = match buffer.get_u8()? { - 100 => Server::Dedicated, //'d' - 108 => Server::NonDedicated, //'l' - 112 => Server::TV, //'p' - _ => Err(UnknownEnumCast)?, - }; - let environment_type = match buffer.get_u8()? { - 108 => Environment::Linux, //'l' - 119 => Environment::Windows, //'w' - 109 | 111 => Environment::Mac, //'m' or 'o' - _ => Err(UnknownEnumCast)?, - }; + let server_type = Server::from_gldsrc(buffer.get_u8()?)?; + let environment_type = Environment::from_gldsrc(buffer.get_u8()?)?; let has_password = buffer.get_u8()? == 1; let vac_secured = buffer.get_u8()? == 1; let the_ship = match *engine == SteamApp::TS.as_engine() { @@ -400,7 +357,7 @@ impl ValveProtocol { /// Get the server player's. fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(engine, protocol, Request::Players)?; + let mut buffer = self.get_kind_request_data(engine, protocol, Request::Players)?; let count = buffer.get_u8()? as usize; let mut players: Vec = Vec::with_capacity(count); @@ -428,7 +385,7 @@ impl ValveProtocol { /// Get the server's rules. fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult> { - let mut buffer = self.get_request_data(engine, protocol, Request::Rules)?; + let mut buffer = self.get_kind_request_data(engine, protocol, Request::Rules)?; let count = buffer.get_u16()? as usize; let mut rules: HashMap = HashMap::with_capacity(count); diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 5515ca6..82edeb2 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -1,5 +1,8 @@ use std::collections::HashMap; +use crate::bufferer::Bufferer; +use crate::GDError::UnknownEnumCast; +use crate::GDResult; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -12,6 +15,17 @@ pub enum Server { TV, } +impl Server { + pub(crate) fn from_gldsrc(value: u8) -> GDResult { + Ok(match value { + 100 => Server::Dedicated, //'d' + 108 => Server::NonDedicated, //'l' + 112 => Server::TV, //'p' + _ => Err(UnknownEnumCast)?, + }) + } +} + /// The Operating System that the server is on. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -21,6 +35,17 @@ pub enum Environment { Mac, } +impl Environment { + pub(crate) fn from_gldsrc(value: u8) -> GDResult { + Ok(match value { + 108 => Environment::Linux, //'l' + 119 => Environment::Windows, //'w' + 109 | 111 => Environment::Mac, //'m' or 'o' + _ => Err(UnknownEnumCast)?, + }) + } +} + /// A query response. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] @@ -142,6 +167,40 @@ pub(crate) fn get_optional_extracted_data(data: Option) -> ExtractedD } } +#[derive(Debug, Clone)] +pub(crate) struct Packet { + pub header: u32, + pub kind: u8, + pub payload: Vec, +} + +impl Packet { + pub fn new(kind: u8, payload: Vec) -> Self { + Self { + header: 4294967295, // FF FF FF FF + kind, + payload, + } + } + + pub fn new_from_bufferer(buffer: &mut Bufferer) -> GDResult { + Ok(Self { + header: buffer.get_u32()?, + kind: buffer.get_u8()?, + payload: buffer.remaining_data_vec(), + }) + } + + pub fn to_bytes(&self) -> Vec { + let mut buf = Vec::from(self.header.to_be_bytes()); + + buf.push(self.kind); + buf.extend(&self.payload); + + buf + } +} + /// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). #[derive(Eq, PartialEq, Copy, Clone)] #[repr(u8)] @@ -154,6 +213,15 @@ pub(crate) enum Request { Rules = 0x56, } +impl Request { + pub fn get_default_payload(&self) -> Vec { + match self { + Request::Info => String::from("Source Engine Query\0").into_bytes(), + _ => vec![0xFF, 0xFF, 0xFF, 0xFF], + } + } +} + /// Supported steam apps #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] From 4122d34cfafb3a1826a4d6256f5eb0484a583919 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 28 Apr 2023 18:00:04 +0300 Subject: [PATCH 176/597] [Service] Add valve master server query service (#34) * [Service] Add initial files * [Service] Add initial request packet * [Service] Add filters * [Service] Some clippy improvements * [Service] Make query a vector of ipv4addr and port * [Service] Add complete and singular query * [Crate] Update md files * [Service] Add docs and clippy adjustments * [Service] Add hasTags and fix filters * [Service] Use let some instead of match * [Service] Add other filters * [Service] Add nor and nand filters * [Service] Remove 0.0.0.0:0 from query * [Service] Remove dev testing test * [Service] Add valve_master_server_query example --- CHANGELOG.md | 3 + SERVICES.md | 6 +- examples/valve_master_server_query.rs | 14 ++ src/lib.rs | 2 + src/services/mod.rs | 4 + src/services/valve_master_server/mod.rs | 7 + src/services/valve_master_server/service.rs | 145 ++++++++++++ src/services/valve_master_server/types.rs | 237 ++++++++++++++++++++ 8 files changed, 415 insertions(+), 3 deletions(-) create mode 100644 examples/valve_master_server_query.rs create mode 100644 src/services/mod.rs create mode 100644 src/services/valve_master_server/mod.rs create mode 100644 src/services/valve_master_server/service.rs create mode 100644 src/services/valve_master_server/types.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7acb431..1146981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Games: - [Serious Sam](https://www.gog.com/game/serious_sam_the_first_encounter) support. - [Frontlines: Fuel of War](https://store.steampowered.com/app/9460/Frontlines_Fuel_of_War/) support. +Services: +- [Valve Master Server Query](https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol) support. + ### Breaking: Protocols: - Valve: Request type enums have been renamed from all caps to starting-only uppercase, ex: `INFO` to `Info` diff --git a/SERVICES.md b/SERVICES.md index c1033d2..38d7cac 100644 --- a/SERVICES.md +++ b/SERVICES.md @@ -1,8 +1,8 @@ # Supported services: -| ID | Name | Notes | -|-----|------|-------| -| --- | ---- | ----- | +| Name | Documentation reference | +|---------------------|-------------------------------------------------------------------------------------------------------| +| Valve Master Server | [Master Server Query Protocol](https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol) | ## Planned to add support: TeamSpeak diff --git a/examples/valve_master_server_query.rs b/examples/valve_master_server_query.rs new file mode 100644 index 0000000..438f3bb --- /dev/null +++ b/examples/valve_master_server_query.rs @@ -0,0 +1,14 @@ +use gamedig::valve_master_server::{query, Filter, Region, SearchFilters}; + +fn main() { + let search_filters = SearchFilters::new() + .insert(Filter::RunsAppID(440)) + .insert(Filter::CanBeEmpty(false)) + .insert(Filter::CanBeFull(false)) + .insert(Filter::CanHavePassword(false)) + .insert(Filter::IsSecured(true)) + .insert(Filter::HasTags(&["minecraft"])); + + let ips = query(Region::Europe, Some(search_filters)).unwrap(); + println!("Servers: {:?} \n Amount: {}", ips, ips.len()); +} diff --git a/src/lib.rs b/src/lib.rs index 0dbd300..8727951 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ pub mod errors; #[cfg(not(feature = "no_games"))] pub mod games; pub mod protocols; +pub mod services; mod bufferer; mod socket; @@ -30,3 +31,4 @@ mod utils; pub use errors::*; #[cfg(not(feature = "no_games"))] pub use games::*; +pub use services::*; diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 0000000..ba33024 --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1,4 @@ +//! Services that are currently implemented. + +/// Reference: [Master Server Query Protocol](https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol) +pub mod valve_master_server; diff --git a/src/services/valve_master_server/mod.rs b/src/services/valve_master_server/mod.rs new file mode 100644 index 0000000..908f8f6 --- /dev/null +++ b/src/services/valve_master_server/mod.rs @@ -0,0 +1,7 @@ +/// The implementation. +pub mod service; +/// All types used by the implementation. +pub mod types; + +pub use service::*; +pub use types::*; diff --git a/src/services/valve_master_server/service.rs b/src/services/valve_master_server/service.rs new file mode 100644 index 0000000..a7d3ec0 --- /dev/null +++ b/src/services/valve_master_server/service.rs @@ -0,0 +1,145 @@ +use crate::bufferer::{Bufferer, Endianess}; +use crate::socket::{Socket, UdpSocket}; +use crate::valve_master_server::{Region, SearchFilters}; +use crate::{GDError, GDResult}; +use std::net::Ipv4Addr; + +/// The default master ip, which is the one for Source. +pub const DEFAULT_MASTER_IP: &str = "hl2master.steampowered.com"; +/// The default master port. +pub const DEFAULT_MASTER_PORT: u16 = 27011; + +fn construct_payload(region: Region, filters: &Option, last_ip: &str, last_port: u16) -> Vec { + let filters_bytes: Vec = match filters { + None => vec![0x00], + Some(f) => f.to_bytes(), + }; + + let region_byte = &[region as u8]; + + [ + // Packet has to begin with the character '1' + &[0x31], + // The region byte is next + region_byte, + // The last fetched ip as a string + last_ip.as_bytes(), + // Followed by an ':' + &[b':'], + // And the port, as a string + last_port.to_string().as_bytes(), + // Which needs to end with a NULL byte + &[0x00], + // Then the filters + &filters_bytes, + ] + .concat() +} + +/// The implementation, use this if you want to keep the same socket. +pub struct ValveMasterServer { + socket: UdpSocket, +} + +impl ValveMasterServer { + /// Construct a new struct. + pub fn new(master_ip: &str, master_port: u16) -> GDResult { + let socket = UdpSocket::new(master_ip, master_port)?; + socket.apply_timeout(None)?; + + Ok(Self { socket }) + } + + /// Make just a single query, providing `0.0.0.0` as the last ip and `0` as + /// the last port will give the initial packet. + pub fn query_specific( + &mut self, + region: Region, + search_filters: &Option, + last_address_ip: &str, + last_address_port: u16, + ) -> GDResult> { + let payload = construct_payload(region, search_filters, last_address_ip, last_address_port); + println!("{:02X?}", payload); + self.socket.send(&payload)?; + + let received_data = self.socket.receive(Some(1400))?; + let mut buf = Bufferer::new_with_data(Endianess::Big, &received_data); + + if buf.get_u32()? != 4294967295 || buf.get_u16()? != 26122 { + return Err(GDError::PacketBad); + } + + let mut ips: Vec<(Ipv4Addr, u16)> = Vec::new(); + while buf.remaining_length() > 0 { + let ip = Ipv4Addr::new(buf.get_u8()?, buf.get_u8()?, buf.get_u8()?, buf.get_u8()?); + let port = buf.get_u16()?; + + ips.push((ip, port)); + } + + Ok(ips) + } + + /// Make a complete query. + pub fn query(&mut self, region: Region, search_filters: Option) -> GDResult> { + let mut ips: Vec<(Ipv4Addr, u16)> = Vec::new(); + + let mut exit_fetching = false; + let mut last_ip: String = "0.0.0.0".to_string(); + let mut last_port: u16 = 0; + + while !exit_fetching { + let new_ips = self.query_specific(region, &search_filters, last_ip.as_str(), last_port)?; + + match new_ips.last() { + None => exit_fetching = true, + Some((latest_ip, latest_port)) => { + let mut remove_last = false; + + let latest_ip_string = latest_ip.to_string(); + if latest_ip_string == "0.0.0.0" && *latest_port == 0 { + exit_fetching = true; + remove_last = true; + } else if latest_ip_string == last_ip && *latest_port == last_port { + exit_fetching = true; + } else { + last_ip = latest_ip_string; + last_port = *latest_port; + } + + ips.extend(new_ips); + if remove_last { + ips.pop(); + } + } + } + } + + Ok(ips) + } +} + +/// Take only the first response of (what would be a) complete query. This is +/// faster as it results in less packets being sent, received and processed but +/// yields less ips. +pub fn query_singular(region: Region, search_filters: Option) -> GDResult> { + let mut master_server = ValveMasterServer::new(DEFAULT_MASTER_IP, DEFAULT_MASTER_PORT)?; + + let mut ips = master_server.query_specific(region, &search_filters, "0.0.0.0", 0)?; + + if let Some((last_ip, last_port)) = ips.last() { + if last_ip.to_string() == "0.0.0.0" && *last_port == 0 { + ips.pop(); + } + } + + Ok(ips) +} + +/// Make a complete query. +pub fn query(region: Region, search_filters: Option) -> GDResult> { + let mut master_server = ValveMasterServer::new(DEFAULT_MASTER_IP, DEFAULT_MASTER_PORT)?; + + master_server.query(region, search_filters) +} diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs new file mode 100644 index 0000000..a2e78de --- /dev/null +++ b/src/services/valve_master_server/types.rs @@ -0,0 +1,237 @@ +/// A query filter. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Filter<'a> { + IsSecured(bool), + RunsMap(&'a str), + CanHavePassword(bool), + CanBeEmpty(bool), + IsEmpty(bool), + CanBeFull(bool), + RunsAppID(u32), + NotAppID(u32), + HasTags(&'a [&'a str]), + MatchName(&'a str), + MatchVersion(&'a str), + RestrictUniqueIP(bool), + OnAddress(&'a str), + Whitelisted(bool), + SpectatorProxy(bool), + IsDedicated(bool), + RunsLinux(bool), + HasGameDir(&'a str), +} + +fn bool_as_char_u8(b: bool) -> u8 { + match b { + true => b'1', + false => b'0', + } +} + +impl<'a> Filter<'a> { + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes: Vec = Vec::new(); + + match self { + Filter::IsSecured(secured) => { + bytes = "\\secure\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*secured)]); + } + Filter::RunsMap(map) => { + bytes = "\\map\\".as_bytes().to_vec(); + bytes.extend(map.as_bytes()); + } + Filter::CanHavePassword(password) => { + bytes = "\\password\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*password)]); + } + Filter::CanBeEmpty(empty) => { + bytes = "\\empty\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*empty)]); + } + Filter::CanBeFull(full) => { + bytes = "\\full\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*full)]); + } + Filter::RunsAppID(id) => { + bytes = "\\appid\\".as_bytes().to_vec(); + bytes.extend(id.to_string().as_bytes()); + } + Filter::HasTags(tags) => { + if !tags.is_empty() { + bytes = "\\gametype\\".as_bytes().to_vec(); + for tag in tags.iter() { + bytes.extend(tag.as_bytes()); + bytes.extend([b',']); + } + + bytes.pop(); + } + } + Filter::NotAppID(id) => { + bytes = "\\napp\\".as_bytes().to_vec(); + bytes.extend(id.to_string().as_bytes()); + } + Filter::IsEmpty(empty) => { + bytes = "\\noplayers\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*empty)]); + } + Filter::MatchName(name) => { + bytes = "\\name_match\\".as_bytes().to_vec(); + bytes.extend(name.as_bytes()); + } + Filter::MatchVersion(version) => { + bytes = "\\version_match\\".as_bytes().to_vec(); + bytes.extend(version.as_bytes()); + } + Filter::RestrictUniqueIP(unique) => { + bytes = "\\collapse_addr_hash\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*unique)]); + } + Filter::OnAddress(address) => { + bytes = "\\gameaddr\\".as_bytes().to_vec(); + bytes.extend(address.as_bytes()); + } + Filter::Whitelisted(whitelisted) => { + bytes = "\\white\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*whitelisted)]); + } + Filter::SpectatorProxy(condition) => { + bytes = "\\proxy\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*condition)]); + } + Filter::IsDedicated(dedicated) => { + bytes = "\\dedicated\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*dedicated)]); + } + Filter::RunsLinux(linux) => { + bytes = "\\linux\\".as_bytes().to_vec(); + bytes.extend([bool_as_char_u8(*linux)]); + } + Filter::HasGameDir(game_dir) => { + bytes = "\\gamedir\\".as_bytes().to_vec(); + bytes.extend(game_dir.as_bytes()); + } + } + + bytes + } +} + +/// Query search filters. +/// An example of constructing one: +/// ```rust +/// use gamedig::valve_master_server::{Filter, SearchFilters}; +/// +/// let search_filters = SearchFilters::new() +/// .insert(Filter::RunsAppID(440)) +/// .insert(Filter::CanHavePassword(true)); +/// ``` +/// This would query the servers that are (by App ID) 440 and that can contain +/// passwords. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct SearchFilters<'a> { + filters: Vec>, + nor_filters: Vec>, + nand_filters: Vec>, +} + +impl<'a> Default for SearchFilters<'a> { + fn default() -> Self { SearchFilters::new() } +} + +fn update_or_insert_vec<'a>(filter_list: Vec>, filter: Filter<'a>) -> Vec> { + let mut list = filter_list; + + let found_same_filter = list.iter_mut().find_map(|f| { + if std::mem::discriminant(f) == std::mem::discriminant(&filter) { + Some(f) + } else { + None + } + }); + + match found_same_filter { + None => list.push(filter), + Some(f) => *f = filter, + } + + list +} + +impl<'a> SearchFilters<'a> { + pub fn new() -> Self { + Self { + filters: Vec::new(), + nor_filters: Vec::new(), + nand_filters: Vec::new(), + } + } + + pub fn insert(self, filter: Filter<'a>) -> Self { + Self { + filters: update_or_insert_vec(self.filters, filter), + nand_filters: self.nand_filters, + nor_filters: self.nor_filters, + } + } + + pub fn insert_nand(self, filter: Filter<'a>) -> Self { + Self { + filters: self.filters, + nand_filters: self.nand_filters, + nor_filters: update_or_insert_vec(self.nor_filters, filter), + } + } + + pub fn insert_nor(self, filter: Filter<'a>) -> Self { + Self { + filters: self.filters, + nand_filters: update_or_insert_vec(self.nand_filters, filter), + nor_filters: self.nor_filters, + } + } + + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes: Vec = Vec::new(); + + // hmm, this is repetitive + for filter in &self.filters { + bytes.extend(filter.to_bytes()) + } + + if !self.nand_filters.is_empty() { + bytes.extend(b"\\nand\\".to_vec()); + bytes.extend(self.nand_filters.len().to_string().as_bytes()); + for filter in &self.nand_filters { + bytes.extend(filter.to_bytes()) + } + } + + if !self.nor_filters.is_empty() { + bytes.extend(b"\\nor\\".to_vec()); + bytes.extend(self.nor_filters.len().to_string().as_bytes()); + for filter in &self.nor_filters { + bytes.extend(filter.to_bytes()) + } + } + + bytes.extend([0x00]); + bytes + } +} + +/// The region that you want to query server for. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(u8)] +pub enum Region { + UsEast = 0x00, + UsWest = 0x01, + AmericaSouth = 0x02, + Europe = 0x03, + Asia = 0x04, + Australia = 0x05, + MiddleEast = 0x06, + Africa = 0x07, + Others = 0xFF, +} From c5fd58f794120b8de4ea092c19ac5702d41a22c4 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 28 Apr 2023 19:09:11 +0300 Subject: [PATCH 177/597] [Protocol] Some cargo clippy improvements for GS one and three --- src/protocols/gamespy/protocols/one/protocol.rs | 2 +- src/protocols/gamespy/protocols/three/protocol.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/protocols/gamespy/protocols/one/protocol.rs b/src/protocols/gamespy/protocols/one/protocol.rs index 5a6d87e..4d32fe1 100644 --- a/src/protocols/gamespy/protocols/one/protocol.rs +++ b/src/protocols/gamespy/protocols/one/protocol.rs @@ -219,7 +219,7 @@ pub fn query(address: &str, port: u16, timeout_settings: Option players, tournament: server_vars .remove("tournament") - .unwrap_or("true".to_string()) + .unwrap_or_else(|| "true".to_string()) .to_lowercase() .parse() .map_err(|_| GDError::TypeParse)?, diff --git a/src/protocols/gamespy/protocols/three/protocol.rs b/src/protocols/gamespy/protocols/three/protocol.rs index c834d97..fdd0305 100644 --- a/src/protocols/gamespy/protocols/three/protocol.rs +++ b/src/protocols/gamespy/protocols/three/protocol.rs @@ -18,7 +18,7 @@ struct RequestPacket { } impl RequestPacket { - fn to_bytes(self) -> Vec { + fn to_bytes(&self) -> Vec { let mut packet: Vec = Vec::with_capacity(7); packet.extend_from_slice(&self.header.to_be_bytes()); packet.push(self.kind); @@ -144,10 +144,10 @@ fn get_server_packets(address: &str, port: u16, timeout_settings: Option) -> GDResult<(HashMap, Vec)> { +fn data_to_map(packet: &[u8]) -> GDResult<(HashMap, Vec)> { let mut vars = HashMap::new(); - let mut buf = Bufferer::new_with_data(Endianess::Big, &packet); + let mut buf = Bufferer::new_with_data(Endianess::Big, packet); while buf.remaining_length() > 0 { let key = buf.get_string_utf8()?; if key.is_empty() { @@ -201,7 +201,7 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< } let field_split: Vec<&str> = field.split('_').collect(); - let field_name = field_split.get(0).ok_or(GDError::PacketBad)?; + let field_name = field_split.first().ok_or(GDError::PacketBad)?; if !["player", "score", "ping", "team", "deaths", "pid", "skill"].contains(field_name) { continue; } @@ -342,7 +342,7 @@ pub fn query(address: &str, port: u16, timeout_settings: Option teams, tournament: server_vars .remove("tournament") - .unwrap_or("true".to_string()) + .unwrap_or_else(|| "true".to_string()) .to_lowercase() .parse() .map_err(|_| GDError::TypeParse)?, From 780d42067ea570eabfdd84f5e76ab3b3cc5b7046 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Fri, 28 Apr 2023 19:49:52 +0300 Subject: [PATCH 178/597] [Protocol] Moved GS1 under gamespy::one instead of gamespy --- CHANGELOG.md | 1 + src/games/bf1942.rs | 7 +++---- src/games/ss.rs | 7 +++---- src/games/ut.rs | 7 +++---- src/protocols/gamespy/protocols/mod.rs | 3 --- src/protocols/gamespy/protocols/three/protocol.rs | 3 +-- 6 files changed, 11 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1146981..4275f3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Services: Protocols: - Valve: Request type enums have been renamed from all caps to starting-only uppercase, ex: `INFO` to `Info` - GameSpy 1: `players_minimum` is now an `Option` instead of an `u8` +- GameSpy 1: Is now under `protocols::gamespy::one` instead of `protocols::gamespy` # 0.2.1 - 03/03/2023 ### Changes: diff --git a/src/games/bf1942.rs b/src/games/bf1942.rs index b813694..632db72 100644 --- a/src/games/bf1942.rs +++ b/src/games/bf1942.rs @@ -1,7 +1,6 @@ -use crate::{ - protocols::gamespy::{self, Response}, - GDResult, -}; +use crate::protocols::gamespy; +use crate::protocols::gamespy::one::Response; +use crate::GDResult; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(23000), None) diff --git a/src/games/ss.rs b/src/games/ss.rs index 77d5cd9..63463d7 100644 --- a/src/games/ss.rs +++ b/src/games/ss.rs @@ -1,7 +1,6 @@ -use crate::{ - protocols::gamespy::{self, Response}, - GDResult, -}; +use crate::protocols::gamespy; +use crate::protocols::gamespy::one::Response; +use crate::GDResult; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(25601), None) diff --git a/src/games/ut.rs b/src/games/ut.rs index a7f4ff1..27bfe68 100644 --- a/src/games/ut.rs +++ b/src/games/ut.rs @@ -1,7 +1,6 @@ -use crate::{ - protocols::gamespy::{self, Response}, - GDResult, -}; +use crate::protocols::gamespy; +use crate::protocols::gamespy::one::Response; +use crate::GDResult; pub fn query(address: &str, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(7778), None) diff --git a/src/protocols/gamespy/protocols/mod.rs b/src/protocols/gamespy/protocols/mod.rs index c1d250c..c2a9530 100644 --- a/src/protocols/gamespy/protocols/mod.rs +++ b/src/protocols/gamespy/protocols/mod.rs @@ -1,5 +1,2 @@ pub mod one; pub mod three; - -pub use one::*; -pub use three::*; diff --git a/src/protocols/gamespy/protocols/three/protocol.rs b/src/protocols/gamespy/protocols/three/protocol.rs index fdd0305..1c78ada 100644 --- a/src/protocols/gamespy/protocols/three/protocol.rs +++ b/src/protocols/gamespy/protocols/three/protocol.rs @@ -1,7 +1,6 @@ use crate::bufferer::{Bufferer, Endianess}; use crate::protocols::gamespy::common::has_password; -use crate::protocols::gamespy::three::{Player, Response}; -use crate::protocols::gamespy::Team; +use crate::protocols::gamespy::three::{Player, Response, Team}; use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; use crate::{GDError, GDResult}; From e159cfebbd346cbd1da59b5e72bf135fde142229 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 29 Apr 2023 22:57:10 +0300 Subject: [PATCH 179/597] [Service] Valve Master Server change filter storage from Vec to a HashMap variant for better speed --- src/services/valve_master_server/service.rs | 1 - src/services/valve_master_server/types.rs | 57 +++++++++------------ 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/services/valve_master_server/service.rs b/src/services/valve_master_server/service.rs index a7d3ec0..169486d 100644 --- a/src/services/valve_master_server/service.rs +++ b/src/services/valve_master_server/service.rs @@ -60,7 +60,6 @@ impl ValveMasterServer { last_address_port: u16, ) -> GDResult> { let payload = construct_payload(region, search_filters, last_address_ip, last_address_port); - println!("{:02X?}", payload); self.socket.send(&payload)?; let received_data = self.socket.receive(Some(1400))?; diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs index a2e78de..9a16b7a 100644 --- a/src/services/valve_master_server/types.rs +++ b/src/services/valve_master_server/types.rs @@ -1,3 +1,6 @@ +use std::collections::HashMap; +use std::mem::Discriminant; + /// A query filter. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Filter<'a> { @@ -129,65 +132,55 @@ impl<'a> Filter<'a> { /// ``` /// This would query the servers that are (by App ID) 440 and that can contain /// passwords. -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SearchFilters<'a> { - filters: Vec>, - nor_filters: Vec>, - nand_filters: Vec>, + filters: HashMap>, Filter<'a>>, + nor_filters: HashMap>, Filter<'a>>, + nand_filters: HashMap>, Filter<'a>>, } impl<'a> Default for SearchFilters<'a> { fn default() -> Self { SearchFilters::new() } } -fn update_or_insert_vec<'a>(filter_list: Vec>, filter: Filter<'a>) -> Vec> { - let mut list = filter_list; - - let found_same_filter = list.iter_mut().find_map(|f| { - if std::mem::discriminant(f) == std::mem::discriminant(&filter) { - Some(f) - } else { - None - } - }); - - match found_same_filter { - None => list.push(filter), - Some(f) => *f = filter, - } - - list -} - impl<'a> SearchFilters<'a> { pub fn new() -> Self { Self { - filters: Vec::new(), - nor_filters: Vec::new(), - nand_filters: Vec::new(), + filters: HashMap::new(), + nor_filters: HashMap::new(), + nand_filters: HashMap::new(), } } pub fn insert(self, filter: Filter<'a>) -> Self { + let mut updated_fitler = self.filters; + updated_fitler.insert(std::mem::discriminant(&filter), filter); + Self { - filters: update_or_insert_vec(self.filters, filter), + filters: updated_fitler, nand_filters: self.nand_filters, nor_filters: self.nor_filters, } } pub fn insert_nand(self, filter: Filter<'a>) -> Self { + let mut updated_fitler = self.nor_filters; + updated_fitler.insert(std::mem::discriminant(&filter), filter); + Self { filters: self.filters, nand_filters: self.nand_filters, - nor_filters: update_or_insert_vec(self.nor_filters, filter), + nor_filters: updated_fitler, } } pub fn insert_nor(self, filter: Filter<'a>) -> Self { + let mut updated_fitler = self.nand_filters; + updated_fitler.insert(std::mem::discriminant(&filter), filter); + Self { filters: self.filters, - nand_filters: update_or_insert_vec(self.nand_filters, filter), + nand_filters: updated_fitler, nor_filters: self.nor_filters, } } @@ -196,14 +189,14 @@ impl<'a> SearchFilters<'a> { let mut bytes: Vec = Vec::new(); // hmm, this is repetitive - for filter in &self.filters { + for filter in self.filters.values() { bytes.extend(filter.to_bytes()) } if !self.nand_filters.is_empty() { bytes.extend(b"\\nand\\".to_vec()); bytes.extend(self.nand_filters.len().to_string().as_bytes()); - for filter in &self.nand_filters { + for filter in self.nand_filters.values() { bytes.extend(filter.to_bytes()) } } @@ -211,7 +204,7 @@ impl<'a> SearchFilters<'a> { if !self.nor_filters.is_empty() { bytes.extend(b"\\nor\\".to_vec()); bytes.extend(self.nor_filters.len().to_string().as_bytes()); - for filter in &self.nor_filters { + for filter in self.nor_filters.values() { bytes.extend(filter.to_bytes()) } } From 3b694815ccf8bd746118f71fde62be5758c42204 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 29 Apr 2023 23:03:56 +0300 Subject: [PATCH 180/597] [Service] Add Copy trait to VMS Filter --- src/services/valve_master_server/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs index 9a16b7a..85cfe28 100644 --- a/src/services/valve_master_server/types.rs +++ b/src/services/valve_master_server/types.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::mem::Discriminant; /// A query filter. -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Filter<'a> { IsSecured(bool), RunsMap(&'a str), From ed2934f3faa2819bb613a35313d1b8beebbc75f1 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 30 Apr 2023 00:20:24 +0300 Subject: [PATCH 181/597] [Service] Replace repetitive code with a function --- src/services/valve_master_server/types.rs | 32 +++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs index 85cfe28..c45fea1 100644 --- a/src/services/valve_master_server/types.rs +++ b/src/services/valve_master_server/types.rs @@ -185,29 +185,29 @@ impl<'a> SearchFilters<'a> { } } + fn special_filter_to_bytes(name: &str, filters: &HashMap, Filter>) -> Vec { + let mut bytes = Vec::new(); + + if !filters.is_empty() { + bytes.extend(name.as_bytes()); + bytes.extend(filters.len().to_string().as_bytes()); + for filter in filters.values() { + bytes.extend(filter.to_bytes()) + } + } + + bytes + } + pub(crate) fn to_bytes(&self) -> Vec { let mut bytes: Vec = Vec::new(); - // hmm, this is repetitive for filter in self.filters.values() { bytes.extend(filter.to_bytes()) } - if !self.nand_filters.is_empty() { - bytes.extend(b"\\nand\\".to_vec()); - bytes.extend(self.nand_filters.len().to_string().as_bytes()); - for filter in self.nand_filters.values() { - bytes.extend(filter.to_bytes()) - } - } - - if !self.nor_filters.is_empty() { - bytes.extend(b"\\nor\\".to_vec()); - bytes.extend(self.nor_filters.len().to_string().as_bytes()); - for filter in self.nor_filters.values() { - bytes.extend(filter.to_bytes()) - } - } + bytes.extend(SearchFilters::special_filter_to_bytes("nand", &self.nand_filters)); + bytes.extend(SearchFilters::special_filter_to_bytes("nor", &self.nor_filters)); bytes.extend([0x00]); bytes From 6c9f554751ab88e00631828e77e4239ff6729bad Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 30 Apr 2023 00:22:47 +0300 Subject: [PATCH 182/597] [Service] Make Filter to_bytes take self instead of &self --- src/services/valve_master_server/types.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs index c45fea1..805e106 100644 --- a/src/services/valve_master_server/types.rs +++ b/src/services/valve_master_server/types.rs @@ -32,13 +32,13 @@ fn bool_as_char_u8(b: bool) -> u8 { } impl<'a> Filter<'a> { - pub(crate) fn to_bytes(&self) -> Vec { + pub(crate) fn to_bytes(self) -> Vec { let mut bytes: Vec = Vec::new(); match self { Filter::IsSecured(secured) => { bytes = "\\secure\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*secured)]); + bytes.extend([bool_as_char_u8(secured)]); } Filter::RunsMap(map) => { bytes = "\\map\\".as_bytes().to_vec(); @@ -46,15 +46,15 @@ impl<'a> Filter<'a> { } Filter::CanHavePassword(password) => { bytes = "\\password\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*password)]); + bytes.extend([bool_as_char_u8(password)]); } Filter::CanBeEmpty(empty) => { bytes = "\\empty\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*empty)]); + bytes.extend([bool_as_char_u8(empty)]); } Filter::CanBeFull(full) => { bytes = "\\full\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*full)]); + bytes.extend([bool_as_char_u8(full)]); } Filter::RunsAppID(id) => { bytes = "\\appid\\".as_bytes().to_vec(); @@ -77,7 +77,7 @@ impl<'a> Filter<'a> { } Filter::IsEmpty(empty) => { bytes = "\\noplayers\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*empty)]); + bytes.extend([bool_as_char_u8(empty)]); } Filter::MatchName(name) => { bytes = "\\name_match\\".as_bytes().to_vec(); @@ -89,7 +89,7 @@ impl<'a> Filter<'a> { } Filter::RestrictUniqueIP(unique) => { bytes = "\\collapse_addr_hash\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*unique)]); + bytes.extend([bool_as_char_u8(unique)]); } Filter::OnAddress(address) => { bytes = "\\gameaddr\\".as_bytes().to_vec(); @@ -97,19 +97,19 @@ impl<'a> Filter<'a> { } Filter::Whitelisted(whitelisted) => { bytes = "\\white\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*whitelisted)]); + bytes.extend([bool_as_char_u8(whitelisted)]); } Filter::SpectatorProxy(condition) => { bytes = "\\proxy\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*condition)]); + bytes.extend([bool_as_char_u8(condition)]); } Filter::IsDedicated(dedicated) => { bytes = "\\dedicated\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*dedicated)]); + bytes.extend([bool_as_char_u8(dedicated)]); } Filter::RunsLinux(linux) => { bytes = "\\linux\\".as_bytes().to_vec(); - bytes.extend([bool_as_char_u8(*linux)]); + bytes.extend([bool_as_char_u8(linux)]); } Filter::HasGameDir(game_dir) => { bytes = "\\gamedir\\".as_bytes().to_vec(); From 4c4b9d6b45f9a960ab4e987c96ee8fd86ca5cf24 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 30 Apr 2023 00:34:04 +0300 Subject: [PATCH 183/597] [Crate] Edit serde feature to clarify serialization/deserialization for response types --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8727951..87d573b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ //! Enabled by default: None //! //! `no_games` - disables the included games support. -//! `serde` - enables json serialization/deserialization for all types +//! `serde` - enables json serialization/deserialization for all response types pub mod errors; #[cfg(not(feature = "no_games"))] From 9f22a4eadf2ce3bc3cdb760e4b61948dd88a022d Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 30 Apr 2023 00:42:01 +0300 Subject: [PATCH 184/597] [Crate] Add no_services feature flag --- CHANGELOG.md | 1 + Cargo.toml | 1 + src/lib.rs | 7 +++++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4275f3f..60886b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Games: Services: - [Valve Master Server Query](https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol) support. +- Added feature `no_services` which disables the supported services. ### Breaking: Protocols: diff --git a/Cargo.toml b/Cargo.toml index 88a247c..9e531c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ rust-version = "1.56.1" [features] default = [] no_games = [] +no_services = [] serde = ["dep:serde", "serde/derive"] [dependencies] diff --git a/src/lib.rs b/src/lib.rs index 87d573b..1ac2932 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,13 +15,15 @@ //! # Crate features: //! Enabled by default: None //! -//! `no_games` - disables the included games support. -//! `serde` - enables json serialization/deserialization for all response types +//! `serde` - enables json serialization/deserialization for all response types.
+//! `no_games` - disables the included games support.
+//! `no_services` - disables the included services support. pub mod errors; #[cfg(not(feature = "no_games"))] pub mod games; pub mod protocols; +#[cfg(not(feature = "no_services"))] pub mod services; mod bufferer; @@ -31,4 +33,5 @@ mod utils; pub use errors::*; #[cfg(not(feature = "no_games"))] pub use games::*; +#[cfg(not(feature = "no_services"))] pub use services::*; From 8abb6578002b80f3fd803a6df499ec2712bdc044 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 30 Apr 2023 00:50:51 +0300 Subject: [PATCH 185/597] [Service] Update docs. --- src/services/valve_master_server/types.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs index 805e106..4a3d2d9 100644 --- a/src/services/valve_master_server/types.rs +++ b/src/services/valve_master_server/types.rs @@ -15,7 +15,9 @@ pub enum Filter<'a> { HasTags(&'a [&'a str]), MatchName(&'a str), MatchVersion(&'a str), + /// Restrict to only a server if an IP hosts (on different ports) multiple servers. RestrictUniqueIP(bool), + /// Query for servers on a specific address. OnAddress(&'a str), Whitelisted(bool), SpectatorProxy(bool), @@ -128,10 +130,10 @@ impl<'a> Filter<'a> { /// /// let search_filters = SearchFilters::new() /// .insert(Filter::RunsAppID(440)) -/// .insert(Filter::CanHavePassword(true)); +/// .insert(Filter::IsEmpty(false)) +/// .insert(Filter::CanHavePassword(false)); /// ``` -/// This would query the servers that are (by App ID) 440 and that can contain -/// passwords. +/// This will construct filters that search for servers that can't have a password, are not empty and run App ID 440. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SearchFilters<'a> { filters: HashMap>, Filter<'a>>, From 3ef599056a43a207135711a0422b1569d902d2aa Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 30 Apr 2023 01:20:16 +0300 Subject: [PATCH 186/597] [Protocol] GS3 if no player/team data is gathered, dont try to create a struct --- src/protocols/gamespy/protocols/three/protocol.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/protocols/gamespy/protocols/three/protocol.rs b/src/protocols/gamespy/protocols/three/protocol.rs index 1c78ada..08cbf6d 100644 --- a/src/protocols/gamespy/protocols/three/protocol.rs +++ b/src/protocols/gamespy/protocols/three/protocol.rs @@ -248,6 +248,10 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< let mut players: Vec = Vec::new(); for player_data in players_data { + if player_data.is_empty() { + continue; + } + players.push(Player { name: player_data .get("player") @@ -283,6 +287,10 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< let mut teams: Vec = Vec::new(); for team_data in teams_data { + if team_data.is_empty() { + continue; + } + teams.push(Team { name: team_data.get("team").ok_or(GDError::PacketBad)?.to_string(), score: team_data From 33e8f43cb80dc43b4c54b2a129c0a04da7b08498 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 30 Apr 2023 01:26:12 +0300 Subject: [PATCH 187/597] [Games] Add Crysis Wars support. --- CHANGELOG.md | 1 + GAMES.md | 1 + examples/master_querant.rs | 51 ++------------------------------------ src/games/cw.rs | 7 ++++++ src/games/mod.rs | 2 ++ 5 files changed, 13 insertions(+), 49 deletions(-) create mode 100644 src/games/cw.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 60886b5..05a614a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Protocols: Games: - [Serious Sam](https://www.gog.com/game/serious_sam_the_first_encounter) support. - [Frontlines: Fuel of War](https://store.steampowered.com/app/9460/Frontlines_Fuel_of_War/) support. +- [Crysis Wars](https://steamcommunity.com/app/17340) support. Services: - [Valve Master Server Query](https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol) support. diff --git a/GAMES.md b/GAMES.md index c0f6769..8ed4f73 100644 --- a/GAMES.md +++ b/GAMES.md @@ -49,6 +49,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Battlefield 1942 | BF1942 | GameSpy 1 | Query port is 23000. | | Serious Sam | SS | GameSpy 1 | Query Port offset: 1. | | Frontlines: Fuel of War | FFOW | Valve Protocol (Proprietary) | Query Port offset: 2. | +| Crysis Wars | CW | GameSpy 3 | | ## Planned to add support: _ diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 1bbb0a9..57247bd 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -2,55 +2,7 @@ use gamedig::protocols::gamespy; use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::Engine; -use gamedig::{ - aliens, - aoc, - arma2oa, - ase, - asrd, - avorion, - bat1944, - bb2, - bf1942, - bm, - bo, - ccure, - cosu, - cs, - cscz, - csgo, - css, - dod, - dods, - doi, - dst, - ffow, - gm, - hl2dm, - hldms, - ins, - insmic, - inss, - l4d, - l4d2, - mc, - ohd, - onset, - pz, - ror2, - rust, - sc, - sdtd, - ss, - tf, - tf2, - tfc, - ts, - unturned, - ut, - vr, - GDResult, -}; +use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bf1942, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, ffow, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, ss, tf, tf2, tfc, ts, unturned, ut, vr, GDResult, cw}; use std::env; fn main() -> GDResult<()> { @@ -169,6 +121,7 @@ fn main() -> GDResult<()> { "_gamespy3" => println!("{:#?}", gamespy::three::query(ip, port.unwrap(), None)), "_gamespy3_vars" => println!("{:#?}", gamespy::three::query_vars(ip, port.unwrap(), None)), "ffow" => println!("{:#?}", ffow::query(ip, port)), + "cw" => println!("{:#?}", cw::query(ip, port)), _ => panic!("Undefined game: {}", args[1]), }; diff --git a/src/games/cw.rs b/src/games/cw.rs new file mode 100644 index 0000000..4c25e97 --- /dev/null +++ b/src/games/cw.rs @@ -0,0 +1,7 @@ +use crate::protocols::gamespy; +use crate::protocols::gamespy::three::Response; +use crate::GDResult; + +pub fn query(address: &str, port: Option) -> GDResult { + gamespy::three::query(address, port.unwrap_or(64100), None) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 338e89f..66ace49 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -92,3 +92,5 @@ pub mod unturned; pub mod ut; /// V Rising pub mod vr; +/// Crysis Wars +pub mod cw; From 726bfd429f2786884a8b2fdaa8781e7862ef6af9 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 1 May 2023 20:27:39 +0300 Subject: [PATCH 188/597] [Crate] Bump version to 0.2.2 --- CHANGELOG.md | 9 ++++++++- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a614a..0d7ba3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,18 @@ Who knows what the future holds... - # 0.X.Y - DD/MM/2023 ### Changes: +To be made... + +### Breaking: +Nothing, yet... + +# 0.2.2 - 01/05/2023 +### Changes: Crate: - General optimizations thanks to [cargo clippy](https://github.com/rust-lang/rust-clippy) and [@cainthebest](https://github.com/cainthebest). - Added feature `serde` which enables json serialization/deserialization for all types (by [@cainthebest](https://github.com/cainthebest)). +- Documentation improvements. Protocols: - GameSpy 1: Add key `admin` as a possible variable for `admin_name`. diff --git a/Cargo.toml b/Cargo.toml index 9e531c3..54442db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamedig" -version = "0.2.1" +version = "0.2.2" edition = "2021" authors = [ "CosminPerRam [https://github.com/CosminPerRam]", From fc52f3fe91eec58e201c239d7b677bcd5aaa73f2 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 8 May 2023 02:08:22 +0300 Subject: [PATCH 189/597] [Protocol] Add derives and serde derives to GatheringSettings --- CHANGELOG.md | 4 +++- src/protocols/valve/types.rs | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d7ba3a..3243676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ Who knows what the future holds... # 0.X.Y - DD/MM/2023 ### Changes: -To be made... +Protocols: +- Valve: +1. Added standard and serde derives to `GatheringSettings`. ### Breaking: Nothing, yet... diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index 82edeb2..39d9658 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -382,6 +382,8 @@ impl Engine { } /// What data to gather, purely used only with the query function. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct GatheringSettings { pub players: bool, pub rules: bool, From a17f5ad4d2e34091e233a32061b91d4fac2a5f8b Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 8 May 2023 14:59:55 +0300 Subject: [PATCH 190/597] [Service] Add merge methods on SearchFilters --- CHANGELOG.md | 3 + src/services/valve_master_server/types.rs | 73 +++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3243676..0ce5ccd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Protocols: - Valve: 1. Added standard and serde derives to `GatheringSettings`. +Services: +1. Added methods to merge two search filters together. + ### Breaking: Nothing, yet... diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs index 4a3d2d9..b63a68e 100644 --- a/src/services/valve_master_server/types.rs +++ b/src/services/valve_master_server/types.rs @@ -154,6 +154,28 @@ impl<'a> SearchFilters<'a> { } } + pub fn merge_all(self, others: SearchFilters<'a>) -> Self { + let mut updated = self.merge_normals(&others); + updated = updated.merge_nors(&others); + updated = updated.merge_nands(&others); + + updated + } + + pub fn merge_normals(self, others: &SearchFilters<'a>) -> Self { + let mut updated_fitler = self.filters; + + for (filter_key, filter_value) in &others.filters { + updated_fitler.insert(*filter_key, *filter_value); + } + + Self { + filters: updated_fitler, + nand_filters: self.nand_filters, + nor_filters: self.nor_filters, + } + } + pub fn insert(self, filter: Filter<'a>) -> Self { let mut updated_fitler = self.filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -165,6 +187,20 @@ impl<'a> SearchFilters<'a> { } } + pub fn merge_nands(self, others: &SearchFilters<'a>) -> Self { + let mut updated_fitler = self.nand_filters; + + for (filter_key, filter_value) in &others.nand_filters { + updated_fitler.insert(*filter_key, *filter_value); + } + + Self { + filters: self.filters, + nand_filters: updated_fitler, + nor_filters: self.nor_filters, + } + } + pub fn insert_nand(self, filter: Filter<'a>) -> Self { let mut updated_fitler = self.nor_filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -176,6 +212,20 @@ impl<'a> SearchFilters<'a> { } } + pub fn merge_nors(self, others: &SearchFilters<'a>) -> Self { + let mut updated_fitler = self.nor_filters; + + for (filter_key, filter_value) in &others.nor_filters { + updated_fitler.insert(*filter_key, *filter_value); + } + + Self { + filters: self.filters, + nand_filters: self.nand_filters, + nor_filters: updated_fitler, + } + } + pub fn insert_nor(self, filter: Filter<'a>) -> Self { let mut updated_fitler = self.nand_filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -230,3 +280,26 @@ pub enum Region { Africa = 0x07, Others = 0xFF, } + +#[cfg(test)] +mod tests { + use crate::valve_master_server::{Filter, SearchFilters}; + + #[test] + fn merge_normals_test() { + let filters_a = SearchFilters::new() + .insert(Filter::IsSecured(true)); + + let filters_b = SearchFilters::new() + .insert(Filter::CanBeFull(true)) + .insert(Filter::IsSecured(false)); + + let combined = filters_a.merge_all(filters_b); + + let composed = SearchFilters::new() + .insert(Filter::IsSecured(false)) + .insert(Filter::CanBeFull(true)); + + assert_eq!(combined, composed) + } +} From a8e2b51dbb33e55196b93ed262a85ea826ed0d92 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 8 May 2023 14:59:55 +0300 Subject: [PATCH 191/597] Revert "[Service] Add merge methods on SearchFilters" This reverts commit a17f5ad4d2e34091e233a32061b91d4fac2a5f8b. --- CHANGELOG.md | 3 - src/services/valve_master_server/types.rs | 73 ----------------------- 2 files changed, 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce5ccd..3243676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,6 @@ Protocols: - Valve: 1. Added standard and serde derives to `GatheringSettings`. -Services: -1. Added methods to merge two search filters together. - ### Breaking: Nothing, yet... diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs index b63a68e..4a3d2d9 100644 --- a/src/services/valve_master_server/types.rs +++ b/src/services/valve_master_server/types.rs @@ -154,28 +154,6 @@ impl<'a> SearchFilters<'a> { } } - pub fn merge_all(self, others: SearchFilters<'a>) -> Self { - let mut updated = self.merge_normals(&others); - updated = updated.merge_nors(&others); - updated = updated.merge_nands(&others); - - updated - } - - pub fn merge_normals(self, others: &SearchFilters<'a>) -> Self { - let mut updated_fitler = self.filters; - - for (filter_key, filter_value) in &others.filters { - updated_fitler.insert(*filter_key, *filter_value); - } - - Self { - filters: updated_fitler, - nand_filters: self.nand_filters, - nor_filters: self.nor_filters, - } - } - pub fn insert(self, filter: Filter<'a>) -> Self { let mut updated_fitler = self.filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -187,20 +165,6 @@ impl<'a> SearchFilters<'a> { } } - pub fn merge_nands(self, others: &SearchFilters<'a>) -> Self { - let mut updated_fitler = self.nand_filters; - - for (filter_key, filter_value) in &others.nand_filters { - updated_fitler.insert(*filter_key, *filter_value); - } - - Self { - filters: self.filters, - nand_filters: updated_fitler, - nor_filters: self.nor_filters, - } - } - pub fn insert_nand(self, filter: Filter<'a>) -> Self { let mut updated_fitler = self.nor_filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -212,20 +176,6 @@ impl<'a> SearchFilters<'a> { } } - pub fn merge_nors(self, others: &SearchFilters<'a>) -> Self { - let mut updated_fitler = self.nor_filters; - - for (filter_key, filter_value) in &others.nor_filters { - updated_fitler.insert(*filter_key, *filter_value); - } - - Self { - filters: self.filters, - nand_filters: self.nand_filters, - nor_filters: updated_fitler, - } - } - pub fn insert_nor(self, filter: Filter<'a>) -> Self { let mut updated_fitler = self.nand_filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -280,26 +230,3 @@ pub enum Region { Africa = 0x07, Others = 0xFF, } - -#[cfg(test)] -mod tests { - use crate::valve_master_server::{Filter, SearchFilters}; - - #[test] - fn merge_normals_test() { - let filters_a = SearchFilters::new() - .insert(Filter::IsSecured(true)); - - let filters_b = SearchFilters::new() - .insert(Filter::CanBeFull(true)) - .insert(Filter::IsSecured(false)); - - let combined = filters_a.merge_all(filters_b); - - let composed = SearchFilters::new() - .insert(Filter::IsSecured(false)) - .insert(Filter::CanBeFull(true)); - - assert_eq!(combined, composed) - } -} From f8437804696f9635c3314bbcbd9ede95b41bf9a2 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 8 May 2023 15:31:38 +0300 Subject: [PATCH 192/597] [Service] Removed Filters and SearchFilters lifetimes and changed str instances to String --- CHANGELOG.md | 4 ++- src/services/valve_master_server/types.rs | 42 +++++++++++------------ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3243676..f65ad09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ Protocols: 1. Added standard and serde derives to `GatheringSettings`. ### Breaking: -Nothing, yet... +Services: +- Valve Master Query: +1. Removed Filter and SearchFilters lifetimes and changed `&'a str` to `String` and `&'a [&'a str]` to `Vec` # 0.2.2 - 01/05/2023 ### Changes: diff --git a/src/services/valve_master_server/types.rs b/src/services/valve_master_server/types.rs index 4a3d2d9..0bcd672 100644 --- a/src/services/valve_master_server/types.rs +++ b/src/services/valve_master_server/types.rs @@ -2,39 +2,39 @@ use std::collections::HashMap; use std::mem::Discriminant; /// A query filter. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Filter<'a> { +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Filter { IsSecured(bool), - RunsMap(&'a str), + RunsMap(String), CanHavePassword(bool), CanBeEmpty(bool), IsEmpty(bool), CanBeFull(bool), RunsAppID(u32), NotAppID(u32), - HasTags(&'a [&'a str]), - MatchName(&'a str), - MatchVersion(&'a str), + HasTags(Vec), + MatchName(String), + MatchVersion(String), /// Restrict to only a server if an IP hosts (on different ports) multiple servers. RestrictUniqueIP(bool), /// Query for servers on a specific address. - OnAddress(&'a str), + OnAddress(String), Whitelisted(bool), SpectatorProxy(bool), IsDedicated(bool), RunsLinux(bool), - HasGameDir(&'a str), + HasGameDir(String), } -fn bool_as_char_u8(b: bool) -> u8 { +fn bool_as_char_u8(b: &bool) -> u8 { match b { true => b'1', false => b'0', } } -impl<'a> Filter<'a> { - pub(crate) fn to_bytes(self) -> Vec { +impl Filter { + pub(crate) fn to_bytes(&self) -> Vec { let mut bytes: Vec = Vec::new(); match self { @@ -135,17 +135,17 @@ impl<'a> Filter<'a> { /// ``` /// This will construct filters that search for servers that can't have a password, are not empty and run App ID 440. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct SearchFilters<'a> { - filters: HashMap>, Filter<'a>>, - nor_filters: HashMap>, Filter<'a>>, - nand_filters: HashMap>, Filter<'a>>, +pub struct SearchFilters { + filters: HashMap, Filter>, + nor_filters: HashMap, Filter>, + nand_filters: HashMap, Filter>, } -impl<'a> Default for SearchFilters<'a> { +impl Default for SearchFilters { fn default() -> Self { SearchFilters::new() } } -impl<'a> SearchFilters<'a> { +impl SearchFilters { pub fn new() -> Self { Self { filters: HashMap::new(), @@ -154,7 +154,7 @@ impl<'a> SearchFilters<'a> { } } - pub fn insert(self, filter: Filter<'a>) -> Self { + pub fn insert(self, filter: Filter) -> Self { let mut updated_fitler = self.filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -165,7 +165,7 @@ impl<'a> SearchFilters<'a> { } } - pub fn insert_nand(self, filter: Filter<'a>) -> Self { + pub fn insert_nand(self, filter: Filter) -> Self { let mut updated_fitler = self.nor_filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -176,7 +176,7 @@ impl<'a> SearchFilters<'a> { } } - pub fn insert_nor(self, filter: Filter<'a>) -> Self { + pub fn insert_nor(self, filter: Filter) -> Self { let mut updated_fitler = self.nand_filters; updated_fitler.insert(std::mem::discriminant(&filter), filter); @@ -194,7 +194,7 @@ impl<'a> SearchFilters<'a> { bytes.extend(name.as_bytes()); bytes.extend(filters.len().to_string().as_bytes()); for filter in filters.values() { - bytes.extend(filter.to_bytes()) + bytes.extend(filter.to_bytes()); } } From a69896f73712f5cf7d9f23da00aa854bec567057 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 8 May 2023 15:34:59 +0300 Subject: [PATCH 193/597] [Service] Fix tests --- examples/valve_master_server_query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/valve_master_server_query.rs b/examples/valve_master_server_query.rs index 438f3bb..7b33ac1 100644 --- a/examples/valve_master_server_query.rs +++ b/examples/valve_master_server_query.rs @@ -7,7 +7,7 @@ fn main() { .insert(Filter::CanBeFull(false)) .insert(Filter::CanHavePassword(false)) .insert(Filter::IsSecured(true)) - .insert(Filter::HasTags(&["minecraft"])); + .insert(Filter::HasTags(vec!["minecraft".to_string()])); let ips = query(Region::Europe, Some(search_filters)).unwrap(); println!("Servers: {:?} \n Amount: {}", ips, ips.len()); From e620398615753b1bc46a41999bd03f129ace0bb1 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 27 May 2023 00:41:41 +0300 Subject: [PATCH 194/597] [Crate] Changed all address &str to &Ipv4Addr --- CHANGELOG.md | 2 ++ README.md | 4 +++- examples/master_querant.rs | 3 ++- examples/minecraft.rs | 2 +- examples/tf2.rs | 4 +++- src/games/aliens.rs | 3 ++- src/games/aoc.rs | 3 ++- src/games/arma2oa.rs | 3 ++- src/games/ase.rs | 3 ++- src/games/asrd.rs | 3 ++- src/games/avorion.rs | 3 ++- src/games/bat1944.rs | 3 ++- src/games/bb2.rs | 3 ++- src/games/bf1942.rs | 3 ++- src/games/bm.rs | 3 ++- src/games/bo.rs | 3 ++- src/games/ccure.rs | 3 ++- src/games/cosu.rs | 3 ++- src/games/cs.rs | 3 ++- src/games/cscz.rs | 3 ++- src/games/csgo.rs | 3 ++- src/games/css.rs | 3 ++- src/games/cw.rs | 3 ++- src/games/dod.rs | 3 ++- src/games/dods.rs | 3 ++- src/games/doi.rs | 3 ++- src/games/dst.rs | 3 ++- src/games/ffow.rs | 5 +++-- src/games/gm.rs | 3 ++- src/games/hl2dm.rs | 3 ++- src/games/hldms.rs | 3 ++- src/games/ins.rs | 3 ++- src/games/insmic.rs | 3 ++- src/games/inss.rs | 3 ++- src/games/l4d.rs | 3 ++- src/games/l4d2.rs | 3 ++- src/games/mc.rs | 11 +++++----- src/games/ohd.rs | 3 ++- src/games/onset.rs | 3 ++- src/games/pz.rs | 3 ++- src/games/ror2.rs | 3 ++- src/games/rust.rs | 3 ++- src/games/sc.rs | 3 ++- src/games/sdtd.rs | 3 ++- src/games/ss.rs | 3 ++- src/games/tf.rs | 3 ++- src/games/tf2.rs | 3 ++- src/games/tfc.rs | 3 ++- src/games/ts.rs | 3 ++- src/games/unturned.rs | 3 ++- src/games/ut.rs | 3 ++- src/games/vr.rs | 3 ++- src/lib.rs | 2 +- .../gamespy/protocols/one/protocol.rs | 7 +++--- .../gamespy/protocols/three/protocol.rs | 9 ++++---- src/protocols/minecraft/protocol/bedrock.rs | 5 +++-- src/protocols/minecraft/protocol/java.rs | 5 +++-- .../minecraft/protocol/legacy_bv1_8.rs | 5 +++-- .../minecraft/protocol/legacy_v1_4.rs | 5 +++-- .../minecraft/protocol/legacy_v1_6.rs | 5 +++-- src/protocols/minecraft/protocol/mod.rs | 11 +++++----- src/protocols/valve/protocol.rs | 7 +++--- src/services/valve_master_server/service.rs | 8 +++---- src/socket.rs | 22 ++++++++++++++----- src/utils.rs | 7 ++++-- 65 files changed, 171 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f65ad09..7c3f879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ Protocols: 1. Added standard and serde derives to `GatheringSettings`. ### Breaking: +- Every function that used `address: &str` has been changed to `address: &Ipv4Addr`. + Services: - Valve Master Query: 1. Removed Filter and SearchFilters lifetimes and changed `&'a str` to `String` and `&'a [&'a str]` to `Vec` diff --git a/README.md b/README.md index a35e905..87d7f30 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ Pick a game/service/protocol (check the [GAMES](GAMES.md), [SERVICES](SERVICES.m use gamedig::games::tf2; fn main() { - let response = tf2::query("127.0.0.1", None); // None is the default port (which is 27015), could also be Some(27015) + let response = tf2::query(&"127.0.0.1".parse().unwrap(), None); + // None is the default port (which is 27015), could also be Some(27015) + match response { // Result type, must check what it is... Err(error) => println!("Couldn't query, error: {}", error), Ok(r) => println!("{:#?}", r) diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 57247bd..51e96b5 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -4,6 +4,7 @@ use gamedig::protocols::valve; use gamedig::protocols::valve::Engine; use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bf1942, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, ffow, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, ss, tf, tf2, tfc, ts, unturned, ut, vr, GDResult, cw}; use std::env; +use std::net::Ipv4Addr; fn main() -> GDResult<()> { let args: Vec = env::args().collect(); @@ -19,7 +20,7 @@ fn main() -> GDResult<()> { return Ok(()); } - let ip = args[2].as_str(); + let ip = &args[2].as_str().parse::().unwrap(); let port = match args.len() == 4 { false => { if args[1].starts_with("_") { diff --git a/examples/minecraft.rs b/examples/minecraft.rs index a61809a..1bfcf9f 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -3,7 +3,7 @@ use gamedig::games::mc; fn main() { // or Some(), None is the default protocol port (which is 25565 for java // and 19132 for bedrock) - let response = mc::query("127.0.0.1", None); + let response = mc::query(&"127.0.0.1".parse().unwrap(), None); match response { Err(error) => println!("Couldn't query, error: {}", error), diff --git a/examples/tf2.rs b/examples/tf2.rs index a4167d0..9f05813 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -1,7 +1,9 @@ use gamedig::games::tf2; fn main() { - let response = tf2::query("127.0.0.1", None); // or Some(27015), None is the default protocol port (which is 27015) + let response = tf2::query(&"127.0.0.1".parse().unwrap(), None); + // or Some(27015), None is the default protocol port (which is 27015) + match response { // Result type, must check what it is... Err(error) => println!("Couldn't query, error: {}", error), diff --git a/src/games/aliens.rs b/src/games/aliens.rs index 2144186..6a3ce2b 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/aoc.rs b/src/games/aoc.rs index c02d8e6..7a451a9 100644 --- a/src/games/aoc.rs +++ b/src/games/aoc.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/arma2oa.rs b/src/games/arma2oa.rs index 4c01c06..92863e0 100644 --- a/src/games/arma2oa.rs +++ b/src/games/arma2oa.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(2304), diff --git a/src/games/ase.rs b/src/games/ase.rs index 7b05fa3..55edf6d 100644 --- a/src/games/ase.rs +++ b/src/games/ase.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 57d6a70..38fa573 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/avorion.rs b/src/games/avorion.rs index b0c5ee2..e9236b6 100644 --- a/src/games/avorion.rs +++ b/src/games/avorion.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27020), diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs index 1336176..f211d75 100644 --- a/src/games/bat1944.rs +++ b/src/games/bat1944.rs @@ -1,10 +1,11 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDError::TypeParse, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let mut valve_response = valve::query( address, port.unwrap_or(7780), diff --git a/src/games/bb2.rs b/src/games/bb2.rs index 7ccb040..b4b18e4 100644 --- a/src/games/bb2.rs +++ b/src/games/bb2.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/bf1942.rs b/src/games/bf1942.rs index 632db72..c0a934b 100644 --- a/src/games/bf1942.rs +++ b/src/games/bf1942.rs @@ -1,7 +1,8 @@ +use std::net::Ipv4Addr; use crate::protocols::gamespy; use crate::protocols::gamespy::one::Response; use crate::GDResult; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(23000), None) } diff --git a/src/games/bm.rs b/src/games/bm.rs index 7f99a96..c31e8e9 100644 --- a/src/games/bm.rs +++ b/src/games/bm.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/bo.rs b/src/games/bo.rs index 7576f81..4136720 100644 --- a/src/games/bo.rs +++ b/src/games/bo.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/ccure.rs b/src/games/ccure.rs index 23713bf..0acdc81 100644 --- a/src/games/ccure.rs +++ b/src/games/ccure.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/cosu.rs b/src/games/cosu.rs index 079e7c1..2018a90 100644 --- a/src/games/cosu.rs +++ b/src/games/cosu.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27004), diff --git a/src/games/cs.rs b/src/games/cs.rs index 1a926bc..a7cb8a0 100644 --- a/src/games/cs.rs +++ b/src/games/cs.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/cscz.rs b/src/games/cscz.rs index 19b83dc..82feb30 100644 --- a/src/games/cscz.rs +++ b/src/games/cscz.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 68dfef2..11b2df8 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/css.rs b/src/games/css.rs index 8d29825..1d43591 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/cw.rs b/src/games/cw.rs index 4c25e97..74a45e9 100644 --- a/src/games/cw.rs +++ b/src/games/cw.rs @@ -1,7 +1,8 @@ +use std::net::Ipv4Addr; use crate::protocols::gamespy; use crate::protocols::gamespy::three::Response; use crate::GDResult; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { gamespy::three::query(address, port.unwrap_or(64100), None) } diff --git a/src/games/dod.rs b/src/games/dod.rs index 1eeb355..b106ad9 100644 --- a/src/games/dod.rs +++ b/src/games/dod.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/dods.rs b/src/games/dods.rs index f0f6394..d585815 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/doi.rs b/src/games/doi.rs index f314a5c..fc85cee 100644 --- a/src/games/doi.rs +++ b/src/games/doi.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/dst.rs b/src/games/dst.rs index 218a461..2951173 100644 --- a/src/games/dst.rs +++ b/src/games/dst.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/ffow.rs b/src/games/ffow.rs index 3f7c1c2..a5face9 100644 --- a/src/games/ffow.rs +++ b/src/games/ffow.rs @@ -1,3 +1,4 @@ +use std::net::Ipv4Addr; use crate::protocols::types::TimeoutSettings; use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol}; use crate::GDResult; @@ -42,11 +43,11 @@ pub struct Response { pub time_left: u16, } -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { query_with_timeout(address, port, TimeoutSettings::default()) } -pub fn query_with_timeout(address: &str, port: Option, timeout_settings: TimeoutSettings) -> GDResult { +pub fn query_with_timeout(address: &Ipv4Addr, port: Option, timeout_settings: TimeoutSettings) -> GDResult { let mut client = ValveProtocol::new(address, port.unwrap_or(5478), Some(timeout_settings))?; let mut buffer = client.get_request_data( &Engine::GoldSrc(true), diff --git a/src/games/gm.rs b/src/games/gm.rs index 7fe0e70..429986a 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index ecbf631..0e0bddb 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/hldms.rs b/src/games/hldms.rs index 5cbad74..77754e7 100644 --- a/src/games/hldms.rs +++ b/src/games/hldms.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/ins.rs b/src/games/ins.rs index bf4e9c4..1a99041 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/insmic.rs b/src/games/insmic.rs index 8f3bb33..a548450 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/inss.rs b/src/games/inss.rs index ad7f73e..f008adb 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27131), diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 3e2d56d..1b8d761 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index 524b496..bbfdc5d 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/mc.rs b/src/games/mc.rs index 84f6f3e..35d330a 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,3 +1,4 @@ +use std::net::Ipv4Addr; use crate::{ protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup}, GDError, @@ -6,7 +7,7 @@ use crate::{ /// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy /// (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { if let Ok(response) = query_java(address, port) { return Ok(response); } @@ -23,22 +24,22 @@ pub fn query(address: &str, port: Option) -> GDResult { } /// Query a Java Server. -pub fn query_java(address: &str, port: Option) -> GDResult { +pub fn query_java(address: &Ipv4Addr, port: Option) -> GDResult { minecraft::query_java(address, port_or_java_default(port), None) } /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: Option) -> GDResult { +pub fn query_legacy(address: &Ipv4Addr, port: Option) -> GDResult { minecraft::query_legacy(address, port_or_java_default(port), None) } /// Query a specific (Java) Legacy Server. -pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { +pub fn query_legacy_specific(group: LegacyGroup, address: &Ipv4Addr, port: Option) -> GDResult { minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) } /// Query a Bedrock Server. -pub fn query_bedrock(address: &str, port: Option) -> GDResult { +pub fn query_bedrock(address: &Ipv4Addr, port: Option) -> GDResult { minecraft::query_bedrock(address, port_or_bedrock_default(port), None) } diff --git a/src/games/ohd.rs b/src/games/ohd.rs index f12234e..271b316 100644 --- a/src/games/ohd.rs +++ b/src/games/ohd.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27005), diff --git a/src/games/onset.rs b/src/games/onset.rs index f73c073..f29ccd6 100644 --- a/src/games/onset.rs +++ b/src/games/onset.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(7776), diff --git a/src/games/pz.rs b/src/games/pz.rs index 14cdcfe..1466c13 100644 --- a/src/games/pz.rs +++ b/src/games/pz.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(16261), diff --git a/src/games/ror2.rs b/src/games/ror2.rs index e219b92..e3ca4c2 100644 --- a/src/games/ror2.rs +++ b/src/games/ror2.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/rust.rs b/src/games/rust.rs index 1482bfd..21277e1 100644 --- a/src/games/rust.rs +++ b/src/games/rust.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/sc.rs b/src/games/sc.rs index a7b2215..9811941 100644 --- a/src/games/sc.rs +++ b/src/games/sc.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/sdtd.rs b/src/games/sdtd.rs index 7dd5cc6..dd5ee93 100644 --- a/src/games/sdtd.rs +++ b/src/games/sdtd.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(26900), diff --git a/src/games/ss.rs b/src/games/ss.rs index 63463d7..57fd80f 100644 --- a/src/games/ss.rs +++ b/src/games/ss.rs @@ -1,7 +1,8 @@ +use std::net::Ipv4Addr; use crate::protocols::gamespy; use crate::protocols::gamespy::one::Response; use crate::GDResult; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(25601), None) } diff --git a/src/games/tf.rs b/src/games/tf.rs index dbf9b93..064c32b 100644 --- a/src/games/tf.rs +++ b/src/games/tf.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 4920bd2..2be6994 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/tfc.rs b/src/games/tfc.rs index a85f97c..0b966b1 100644 --- a/src/games/tfc.rs +++ b/src/games/tfc.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/ts.rs b/src/games/ts.rs index 4e6eb3c..1cdce9b 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,3 +1,4 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, get_optional_extracted_data, Server, ServerPlayer, SteamApp}, GDResult, @@ -93,7 +94,7 @@ impl Response { } } -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/unturned.rs b/src/games/unturned.rs index c6e071f..1c5afda 100644 --- a/src/games/unturned.rs +++ b/src/games/unturned.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/ut.rs b/src/games/ut.rs index 27bfe68..3f96537 100644 --- a/src/games/ut.rs +++ b/src/games/ut.rs @@ -1,7 +1,8 @@ +use std::net::Ipv4Addr; use crate::protocols::gamespy; use crate::protocols::gamespy::one::Response; use crate::GDResult; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(7778), None) } diff --git a/src/games/vr.rs b/src/games/vr.rs index b391f32..31523e5 100644 --- a/src/games/vr.rs +++ b/src/games/vr.rs @@ -1,9 +1,10 @@ +use std::net::Ipv4Addr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &str, port: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/lib.rs b/src/lib.rs index 1ac2932..3254b53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! ``` //! use gamedig::games::tf2; //! -//! let response = tf2::query("127.0.0.1", None); // None is the default port (which is 27015), could also be Some(27015) +//! let response = tf2::query(&"127.0.0.1".parse().unwrap(), None); // None is the default port (which is 27015), could also be Some(27015) //! match response { // Result type, must check what it is... //! Err(error) => println!("Couldn't query, error: {}", error), //! Ok(r) => println!("{:#?}", r) diff --git a/src/protocols/gamespy/protocols/one/protocol.rs b/src/protocols/gamespy/protocols/one/protocol.rs index 4d32fe1..b688f81 100644 --- a/src/protocols/gamespy/protocols/one/protocol.rs +++ b/src/protocols/gamespy/protocols/one/protocol.rs @@ -11,9 +11,10 @@ use crate::{ use crate::protocols::gamespy::common::has_password; use std::collections::HashMap; +use std::net::Ipv4Addr; fn get_server_values( - address: &str, + address: &Ipv4Addr, port: u16, timeout_settings: Option, ) -> GDResult> { @@ -177,7 +178,7 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u /// If there are parsing problems using the `query` function, you can directly /// get the server's values using this function. pub fn query_vars( - address: &str, + address: &Ipv4Addr, port: u16, timeout_settings: Option, ) -> GDResult> { @@ -187,7 +188,7 @@ pub fn query_vars( /// Query a server by providing the address, the port and timeout settings. /// Providing None to the timeout settings results in using the default values. /// (TimeoutSettings::[default](TimeoutSettings::default)). -pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let mut server_vars = query_vars(address, port, timeout_settings)?; let players_maximum = server_vars diff --git a/src/protocols/gamespy/protocols/three/protocol.rs b/src/protocols/gamespy/protocols/three/protocol.rs index 08cbf6d..fb8cfe3 100644 --- a/src/protocols/gamespy/protocols/three/protocol.rs +++ b/src/protocols/gamespy/protocols/three/protocol.rs @@ -5,6 +5,7 @@ use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; use crate::{GDError, GDResult}; use std::collections::HashMap; +use std::net::Ipv4Addr; const THIS_SESSION_ID: u32 = 1; @@ -42,7 +43,7 @@ struct GameSpy3 { const PACKET_SIZE: usize = 2048; impl GameSpy3 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let socket = UdpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -103,7 +104,7 @@ impl GameSpy3 { } } -fn get_server_packets(address: &str, port: u16, timeout_settings: Option) -> GDResult>> { +fn get_server_packets(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult>> { let mut gs3 = GameSpy3::new(address, port, timeout_settings)?; let challenge = gs3.make_initial_handshake()?; @@ -164,7 +165,7 @@ fn data_to_map(packet: &[u8]) -> GDResult<(HashMap, Vec)> { /// If there are parsing problems using the `query` function, you can directly /// get the server's values using this function. pub fn query_vars( - address: &str, + address: &Ipv4Addr, port: u16, timeout_settings: Option, ) -> GDResult> { @@ -307,7 +308,7 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< /// Query a server by providing the address, the port and timeout settings. /// Providing None to the timeout settings results in using the default values. /// (TimeoutSettings::[default](TimeoutSettings::default)). -pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let packets = get_server_packets(address, port, timeout_settings)?; let (mut server_vars, remaining_data) = data_to_map(packets.get(0).ok_or(GDError::PacketBad)?)?; diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index 51899f0..6fa3632 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -1,6 +1,7 @@ // This file has code that has been documented by the NodeJS GameDig library // (MIT) from https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js +use std::net::Ipv4Addr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -18,7 +19,7 @@ pub struct Bedrock { } impl Bedrock { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let socket = UdpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -92,7 +93,7 @@ impl Bedrock { }) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { Bedrock::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index 8d55d02..6e9b5f5 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -1,3 +1,4 @@ +use std::net::Ipv4Addr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -30,7 +31,7 @@ pub struct Java { } impl Java { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let socket = TcpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -138,7 +139,7 @@ impl Java { }) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { Java::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index e977e44..9e742dd 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -1,3 +1,4 @@ +use std::net::Ipv4Addr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -15,7 +16,7 @@ pub struct LegacyBV1_8 { } impl LegacyBV1_8 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let socket = TcpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -59,7 +60,7 @@ impl LegacyBV1_8 { }) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { LegacyBV1_8::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index aed055d..ff79f57 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -1,3 +1,4 @@ +use std::net::Ipv4Addr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -15,7 +16,7 @@ pub struct LegacyV1_4 { } impl LegacyV1_4 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let socket = TcpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -63,7 +64,7 @@ impl LegacyV1_4 { }) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { LegacyV1_4::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index f901b79..1ff0545 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -1,3 +1,4 @@ +use std::net::Ipv4Addr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -15,7 +16,7 @@ pub struct LegacyV1_6 { } impl LegacyV1_6 { - fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let socket = TcpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -92,7 +93,7 @@ impl LegacyV1_6 { LegacyV1_6::get_response(&mut buffer) } - pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { LegacyV1_6::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 293c739..2911ab1 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,3 +1,4 @@ +use std::net::Ipv4Addr; use crate::{ protocols::minecraft::{ protocol::{ @@ -24,7 +25,7 @@ mod legacy_v1_6; /// Queries a Minecraft server with all the protocol variants one by one (Java /// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { if let Ok(response) = query_java(address, port, timeout_settings.clone()) { return Ok(response); } @@ -41,12 +42,12 @@ pub fn query(address: &str, port: u16, timeout_settings: Option } /// Query a Java Server. -pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query_java(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { Java::query(address, port, timeout_settings) } /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query_legacy(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { return Ok(response); } @@ -65,7 +66,7 @@ pub fn query_legacy(address: &str, port: u16, timeout_settings: Option, ) -> GDResult { @@ -77,6 +78,6 @@ pub fn query_legacy_specific( } /// Query a Bedrock Server. -pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option) -> GDResult { +pub fn query_bedrock(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { Bedrock::query(address, port, timeout_settings) } diff --git a/src/protocols/valve/protocol.rs b/src/protocols/valve/protocol.rs index 87847ed..fa23c73 100644 --- a/src/protocols/valve/protocol.rs +++ b/src/protocols/valve/protocol.rs @@ -30,6 +30,7 @@ use bzip2_rs::decoder::Decoder; use crate::protocols::valve::Packet; use std::collections::HashMap; +use std::net::Ipv4Addr; #[derive(Debug)] #[allow(dead_code)] //remove this later on @@ -124,7 +125,7 @@ pub(crate) struct ValveProtocol { static PACKET_SIZE: usize = 6144; impl ValveProtocol { - pub fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + pub fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { let socket = UdpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -411,7 +412,7 @@ impl ValveProtocol { /// (GatherSettings::[default](GatheringSettings::default), /// TimeoutSettings::[default](TimeoutSettings::default)). pub fn query( - address: &str, + address: &Ipv4Addr, port: u16, engine: Engine, gather_settings: Option, @@ -428,7 +429,7 @@ pub fn query( } fn get_response( - address: &str, + address: &Ipv4Addr, port: u16, engine: Engine, gather_settings: GatheringSettings, diff --git a/src/services/valve_master_server/service.rs b/src/services/valve_master_server/service.rs index 169486d..30652a1 100644 --- a/src/services/valve_master_server/service.rs +++ b/src/services/valve_master_server/service.rs @@ -5,7 +5,7 @@ use crate::{GDError, GDResult}; use std::net::Ipv4Addr; /// The default master ip, which is the one for Source. -pub const DEFAULT_MASTER_IP: &str = "hl2master.steampowered.com"; +pub const DEFAULT_MASTER_IP: Ipv4Addr = Ipv4Addr::new(208, 64, 201, 194); // hl2master.steampowered.com /// The default master port. pub const DEFAULT_MASTER_PORT: u16 = 27011; @@ -43,7 +43,7 @@ pub struct ValveMasterServer { impl ValveMasterServer { /// Construct a new struct. - pub fn new(master_ip: &str, master_port: u16) -> GDResult { + pub fn new(master_ip: &Ipv4Addr, master_port: u16) -> GDResult { let socket = UdpSocket::new(master_ip, master_port)?; socket.apply_timeout(None)?; @@ -123,7 +123,7 @@ impl ValveMasterServer { /// faster as it results in less packets being sent, received and processed but /// yields less ips. pub fn query_singular(region: Region, search_filters: Option) -> GDResult> { - let mut master_server = ValveMasterServer::new(DEFAULT_MASTER_IP, DEFAULT_MASTER_PORT)?; + let mut master_server = ValveMasterServer::new(&DEFAULT_MASTER_IP, DEFAULT_MASTER_PORT)?; let mut ips = master_server.query_specific(region, &search_filters, "0.0.0.0", 0)?; @@ -138,7 +138,7 @@ pub fn query_singular(region: Region, search_filters: Option) -> /// Make a complete query. pub fn query(region: Region, search_filters: Option) -> GDResult> { - let mut master_server = ValveMasterServer::new(DEFAULT_MASTER_IP, DEFAULT_MASTER_PORT)?; + let mut master_server = ValveMasterServer::new(&DEFAULT_MASTER_IP, DEFAULT_MASTER_PORT)?; master_server.query(region, search_filters) } diff --git a/src/socket.rs b/src/socket.rs index 37e78b6..abef48a 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -9,11 +9,12 @@ use std::{ io::{Read, Write}, net, }; +use std::net::Ipv4Addr; const DEFAULT_PACKET_SIZE: usize = 1024; pub trait Socket { - fn new(address: &str, port: u16) -> GDResult + fn new(address: &Ipv4Addr, port: u16) -> GDResult where Self: Sized; fn apply_timeout(&self, timeout_settings: Option) -> GDResult<()>; @@ -27,7 +28,7 @@ pub struct TcpSocket { } impl Socket for TcpSocket { - fn new(address: &str, port: u16) -> GDResult { + fn new(address: &Ipv4Addr, port: u16) -> GDResult { let complete_address = address_and_port_as_string(address, port); Ok(Self { @@ -64,7 +65,7 @@ pub struct UdpSocket { } impl Socket for UdpSocket { - fn new(address: &str, port: u16) -> GDResult { + fn new(address: &Ipv4Addr, port: u16) -> GDResult { let complete_address = address_and_port_as_string(address, port); let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|_| SocketBind)?; @@ -99,6 +100,7 @@ impl Socket for UdpSocket { #[cfg(test)] mod tests { + use std::net::IpAddr; use std::thread; use super::*; @@ -115,8 +117,13 @@ mod tests { stream.write(&buf).unwrap(); }); + let address = match bound_address.ip() { + IpAddr::V4(a) => a, + _ => panic!("address not ipv4!") + }; + // Create a TCP socket and send a message to the server - let mut socket = TcpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); + let mut socket = TcpSocket::new(&address, bound_address.port()).unwrap(); let message = b"hello, world!"; socket.send(message).unwrap(); @@ -146,8 +153,13 @@ mod tests { socket.send_to(&buf, src_addr).unwrap(); }); + let address = match bound_address.ip() { + IpAddr::V4(a) => a, + _ => panic!("address not ipv4!") + }; + // Create a UDP socket and send a message to the server - let mut socket = UdpSocket::new(&*bound_address.ip().to_string(), bound_address.port()).unwrap(); + let mut socket = UdpSocket::new(&address, bound_address.port()).unwrap(); let message = b"hello, world!"; socket.send(message).unwrap(); diff --git a/src/utils.rs b/src/utils.rs index 42a0033..21bdc5a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,7 @@ use crate::{ }; use std::cmp::Ordering; +use std::net::Ipv4Addr; pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { match size.cmp(&expected) { @@ -13,16 +14,18 @@ pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> { } } -pub fn address_and_port_as_string(address: &str, port: u16) -> String { format!("{}:{}", address, port) } +pub fn address_and_port_as_string(address: &Ipv4Addr, port: u16) -> String { format!("{}:{}", address, port) } pub fn u8_lower_upper(n: u8) -> (u8, u8) { (n & 15, n >> 4) } #[cfg(test)] mod tests { + use std::net::Ipv4Addr; + #[test] fn address_and_port_as_string() { assert_eq!( - super::address_and_port_as_string("192.168.0.1", 27015), + super::address_and_port_as_string(&Ipv4Addr::new(192, 168, 0, 1), 27015), "192.168.0.1:27015" ); } From 3f654e0dfd96b0185b984f2afafd9cf78f7b6eba Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Mon, 29 May 2023 08:10:21 +0000 Subject: [PATCH 195/597] [Protocol] Enable the use of Ipv6 addresses (#41) Replace usages of Ipv4Addr with IpAddr which allows the use of either Ipv4 or Ipv6. This patch essentially consists of running: "sed -i 's/Ipv4Addr/IpAddr/g' src/**/*.rs examples/*" and fixing the errors. --- examples/master_querant.rs | 4 ++-- src/games/aliens.rs | 4 ++-- src/games/aoc.rs | 4 ++-- src/games/arma2oa.rs | 4 ++-- src/games/ase.rs | 4 ++-- src/games/asrd.rs | 4 ++-- src/games/avorion.rs | 4 ++-- src/games/bat1944.rs | 4 ++-- src/games/bb2.rs | 4 ++-- src/games/bf1942.rs | 4 ++-- src/games/bm.rs | 4 ++-- src/games/bo.rs | 4 ++-- src/games/ccure.rs | 4 ++-- src/games/cosu.rs | 4 ++-- src/games/cs.rs | 4 ++-- src/games/cscz.rs | 4 ++-- src/games/csgo.rs | 4 ++-- src/games/css.rs | 4 ++-- src/games/cw.rs | 4 ++-- src/games/dod.rs | 4 ++-- src/games/dods.rs | 4 ++-- src/games/doi.rs | 4 ++-- src/games/dst.rs | 4 ++-- src/games/ffow.rs | 6 +++--- src/games/gm.rs | 4 ++-- src/games/hl2dm.rs | 4 ++-- src/games/hldms.rs | 4 ++-- src/games/ins.rs | 4 ++-- src/games/insmic.rs | 4 ++-- src/games/inss.rs | 4 ++-- src/games/l4d.rs | 4 ++-- src/games/l4d2.rs | 4 ++-- src/games/mc.rs | 12 +++++------ src/games/ohd.rs | 4 ++-- src/games/onset.rs | 4 ++-- src/games/pz.rs | 4 ++-- src/games/ror2.rs | 4 ++-- src/games/rust.rs | 4 ++-- src/games/sc.rs | 4 ++-- src/games/sdtd.rs | 4 ++-- src/games/ss.rs | 4 ++-- src/games/tf.rs | 4 ++-- src/games/tf2.rs | 4 ++-- src/games/tfc.rs | 4 ++-- src/games/ts.rs | 4 ++-- src/games/unturned.rs | 4 ++-- src/games/ut.rs | 4 ++-- src/games/vr.rs | 4 ++-- .../gamespy/protocols/one/protocol.rs | 8 ++++---- .../gamespy/protocols/three/protocol.rs | 10 +++++----- src/protocols/minecraft/protocol/bedrock.rs | 6 +++--- src/protocols/minecraft/protocol/java.rs | 6 +++--- .../minecraft/protocol/legacy_bv1_8.rs | 6 +++--- .../minecraft/protocol/legacy_v1_4.rs | 6 +++--- .../minecraft/protocol/legacy_v1_6.rs | 6 +++--- src/protocols/minecraft/protocol/mod.rs | 12 +++++------ src/protocols/valve/protocol.rs | 8 ++++---- src/services/valve_master_server/service.rs | 20 +++++++++---------- src/socket.rs | 19 ++++++------------ src/utils.rs | 8 ++++---- 60 files changed, 155 insertions(+), 162 deletions(-) diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 51e96b5..f15f0b3 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -4,7 +4,7 @@ use gamedig::protocols::valve; use gamedig::protocols::valve::Engine; use gamedig::{aliens, aoc, arma2oa, ase, asrd, avorion, bat1944, bb2, bf1942, bm, bo, ccure, cosu, cs, cscz, csgo, css, dod, dods, doi, dst, ffow, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, ohd, onset, pz, ror2, rust, sc, sdtd, ss, tf, tf2, tfc, ts, unturned, ut, vr, GDResult, cw}; use std::env; -use std::net::Ipv4Addr; +use std::net::IpAddr; fn main() -> GDResult<()> { let args: Vec = env::args().collect(); @@ -20,7 +20,7 @@ fn main() -> GDResult<()> { return Ok(()); } - let ip = &args[2].as_str().parse::().unwrap(); + let ip = &args[2].as_str().parse::().unwrap(); let port = match args.len() == 4 { false => { if args[1].starts_with("_") { diff --git a/src/games/aliens.rs b/src/games/aliens.rs index 6a3ce2b..38b8771 100644 --- a/src/games/aliens.rs +++ b/src/games/aliens.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/aoc.rs b/src/games/aoc.rs index 7a451a9..6324de6 100644 --- a/src/games/aoc.rs +++ b/src/games/aoc.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/arma2oa.rs b/src/games/arma2oa.rs index 92863e0..5fc692b 100644 --- a/src/games/arma2oa.rs +++ b/src/games/arma2oa.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(2304), diff --git a/src/games/ase.rs b/src/games/ase.rs index 55edf6d..41734f2 100644 --- a/src/games/ase.rs +++ b/src/games/ase.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/asrd.rs b/src/games/asrd.rs index 38fa573..a911bdd 100644 --- a/src/games/asrd.rs +++ b/src/games/asrd.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/avorion.rs b/src/games/avorion.rs index e9236b6..5defd51 100644 --- a/src/games/avorion.rs +++ b/src/games/avorion.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27020), diff --git a/src/games/bat1944.rs b/src/games/bat1944.rs index f211d75..fd02d4a 100644 --- a/src/games/bat1944.rs +++ b/src/games/bat1944.rs @@ -1,11 +1,11 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDError::TypeParse, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let mut valve_response = valve::query( address, port.unwrap_or(7780), diff --git a/src/games/bb2.rs b/src/games/bb2.rs index b4b18e4..8b417f0 100644 --- a/src/games/bb2.rs +++ b/src/games/bb2.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/bf1942.rs b/src/games/bf1942.rs index c0a934b..1eacd83 100644 --- a/src/games/bf1942.rs +++ b/src/games/bf1942.rs @@ -1,8 +1,8 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::protocols::gamespy; use crate::protocols::gamespy::one::Response; use crate::GDResult; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(23000), None) } diff --git a/src/games/bm.rs b/src/games/bm.rs index c31e8e9..2585a41 100644 --- a/src/games/bm.rs +++ b/src/games/bm.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/bo.rs b/src/games/bo.rs index 4136720..a35e02e 100644 --- a/src/games/bo.rs +++ b/src/games/bo.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/ccure.rs b/src/games/ccure.rs index 0acdc81..9d06f4f 100644 --- a/src/games/ccure.rs +++ b/src/games/ccure.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/cosu.rs b/src/games/cosu.rs index 2018a90..cecd25a 100644 --- a/src/games/cosu.rs +++ b/src/games/cosu.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27004), diff --git a/src/games/cs.rs b/src/games/cs.rs index a7cb8a0..e13ef13 100644 --- a/src/games/cs.rs +++ b/src/games/cs.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/cscz.rs b/src/games/cscz.rs index 82feb30..61a7075 100644 --- a/src/games/cscz.rs +++ b/src/games/cscz.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/csgo.rs b/src/games/csgo.rs index 11b2df8..d92eb7c 100644 --- a/src/games/csgo.rs +++ b/src/games/csgo.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/css.rs b/src/games/css.rs index 1d43591..d93ee37 100644 --- a/src/games/css.rs +++ b/src/games/css.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/cw.rs b/src/games/cw.rs index 74a45e9..4ce41f9 100644 --- a/src/games/cw.rs +++ b/src/games/cw.rs @@ -1,8 +1,8 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::protocols::gamespy; use crate::protocols::gamespy::three::Response; use crate::GDResult; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { gamespy::three::query(address, port.unwrap_or(64100), None) } diff --git a/src/games/dod.rs b/src/games/dod.rs index b106ad9..02273b4 100644 --- a/src/games/dod.rs +++ b/src/games/dod.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/dods.rs b/src/games/dods.rs index d585815..cba01ba 100644 --- a/src/games/dods.rs +++ b/src/games/dods.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/doi.rs b/src/games/doi.rs index fc85cee..4d5e4ed 100644 --- a/src/games/doi.rs +++ b/src/games/doi.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/dst.rs b/src/games/dst.rs index 2951173..7833477 100644 --- a/src/games/dst.rs +++ b/src/games/dst.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/ffow.rs b/src/games/ffow.rs index a5face9..d0c51b0 100644 --- a/src/games/ffow.rs +++ b/src/games/ffow.rs @@ -1,4 +1,4 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::protocols::types::TimeoutSettings; use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol}; use crate::GDResult; @@ -43,11 +43,11 @@ pub struct Response { pub time_left: u16, } -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { query_with_timeout(address, port, TimeoutSettings::default()) } -pub fn query_with_timeout(address: &Ipv4Addr, port: Option, timeout_settings: TimeoutSettings) -> GDResult { +pub fn query_with_timeout(address: &IpAddr, port: Option, timeout_settings: TimeoutSettings) -> GDResult { let mut client = ValveProtocol::new(address, port.unwrap_or(5478), Some(timeout_settings))?; let mut buffer = client.get_request_data( &Engine::GoldSrc(true), diff --git a/src/games/gm.rs b/src/games/gm.rs index 429986a..78ce488 100644 --- a/src/games/gm.rs +++ b/src/games/gm.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/hl2dm.rs b/src/games/hl2dm.rs index 0e0bddb..7608b62 100644 --- a/src/games/hl2dm.rs +++ b/src/games/hl2dm.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/hldms.rs b/src/games/hldms.rs index 77754e7..776f0c4 100644 --- a/src/games/hldms.rs +++ b/src/games/hldms.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/ins.rs b/src/games/ins.rs index 1a99041..be76fba 100644 --- a/src/games/ins.rs +++ b/src/games/ins.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/insmic.rs b/src/games/insmic.rs index a548450..d6f6edf 100644 --- a/src/games/insmic.rs +++ b/src/games/insmic.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/inss.rs b/src/games/inss.rs index f008adb..a39e1d6 100644 --- a/src/games/inss.rs +++ b/src/games/inss.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27131), diff --git a/src/games/l4d.rs b/src/games/l4d.rs index 1b8d761..b0a45f8 100644 --- a/src/games/l4d.rs +++ b/src/games/l4d.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs index bbfdc5d..67934f3 100644 --- a/src/games/l4d2.rs +++ b/src/games/l4d2.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/mc.rs b/src/games/mc.rs index 35d330a..9a22b76 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,4 +1,4 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup}, GDError, @@ -7,7 +7,7 @@ use crate::{ /// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy /// (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { if let Ok(response) = query_java(address, port) { return Ok(response); } @@ -24,22 +24,22 @@ pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { } /// Query a Java Server. -pub fn query_java(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query_java(address: &IpAddr, port: Option) -> GDResult { minecraft::query_java(address, port_or_java_default(port), None) } /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query_legacy(address: &IpAddr, port: Option) -> GDResult { minecraft::query_legacy(address, port_or_java_default(port), None) } /// Query a specific (Java) Legacy Server. -pub fn query_legacy_specific(group: LegacyGroup, address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query_legacy_specific(group: LegacyGroup, address: &IpAddr, port: Option) -> GDResult { minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) } /// Query a Bedrock Server. -pub fn query_bedrock(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query_bedrock(address: &IpAddr, port: Option) -> GDResult { minecraft::query_bedrock(address, port_or_bedrock_default(port), None) } diff --git a/src/games/ohd.rs b/src/games/ohd.rs index 271b316..a94f97b 100644 --- a/src/games/ohd.rs +++ b/src/games/ohd.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27005), diff --git a/src/games/onset.rs b/src/games/onset.rs index f29ccd6..d7c652e 100644 --- a/src/games/onset.rs +++ b/src/games/onset.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(7776), diff --git a/src/games/pz.rs b/src/games/pz.rs index 1466c13..8aab755 100644 --- a/src/games/pz.rs +++ b/src/games/pz.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(16261), diff --git a/src/games/ror2.rs b/src/games/ror2.rs index e3ca4c2..0afb89f 100644 --- a/src/games/ror2.rs +++ b/src/games/ror2.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/rust.rs b/src/games/rust.rs index 21277e1..2dcb893 100644 --- a/src/games/rust.rs +++ b/src/games/rust.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/sc.rs b/src/games/sc.rs index 9811941..d8418aa 100644 --- a/src/games/sc.rs +++ b/src/games/sc.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/sdtd.rs b/src/games/sdtd.rs index dd5ee93..cc4eb02 100644 --- a/src/games/sdtd.rs +++ b/src/games/sdtd.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(26900), diff --git a/src/games/ss.rs b/src/games/ss.rs index 57fd80f..8cdcb3c 100644 --- a/src/games/ss.rs +++ b/src/games/ss.rs @@ -1,8 +1,8 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::protocols::gamespy; use crate::protocols::gamespy::one::Response; use crate::GDResult; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(25601), None) } diff --git a/src/games/tf.rs b/src/games/tf.rs index 064c32b..62d6830 100644 --- a/src/games/tf.rs +++ b/src/games/tf.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 2be6994..bbca879 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/tfc.rs b/src/games/tfc.rs index 0b966b1..154b05f 100644 --- a/src/games/tfc.rs +++ b/src/games/tfc.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/ts.rs b/src/games/ts.rs index 1cdce9b..ebdb30c 100644 --- a/src/games/ts.rs +++ b/src/games/ts.rs @@ -1,4 +1,4 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, get_optional_extracted_data, Server, ServerPlayer, SteamApp}, GDResult, @@ -94,7 +94,7 @@ impl Response { } } -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/unturned.rs b/src/games/unturned.rs index 1c5afda..827a039 100644 --- a/src/games/unturned.rs +++ b/src/games/unturned.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27015), diff --git a/src/games/ut.rs b/src/games/ut.rs index 3f96537..e7ddc89 100644 --- a/src/games/ut.rs +++ b/src/games/ut.rs @@ -1,8 +1,8 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::protocols::gamespy; use crate::protocols::gamespy::one::Response; use crate::GDResult; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { gamespy::one::query(address, port.unwrap_or(7778), None) } diff --git a/src/games/vr.rs b/src/games/vr.rs index 31523e5..89ac7b7 100644 --- a/src/games/vr.rs +++ b/src/games/vr.rs @@ -1,10 +1,10 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::valve::{self, game, SteamApp}, GDResult, }; -pub fn query(address: &Ipv4Addr, port: Option) -> GDResult { +pub fn query(address: &IpAddr, port: Option) -> GDResult { let valve_response = valve::query( address, port.unwrap_or(27016), diff --git a/src/protocols/gamespy/protocols/one/protocol.rs b/src/protocols/gamespy/protocols/one/protocol.rs index b688f81..e377d58 100644 --- a/src/protocols/gamespy/protocols/one/protocol.rs +++ b/src/protocols/gamespy/protocols/one/protocol.rs @@ -11,10 +11,10 @@ use crate::{ use crate::protocols::gamespy::common::has_password; use std::collections::HashMap; -use std::net::Ipv4Addr; +use std::net::IpAddr; fn get_server_values( - address: &Ipv4Addr, + address: &IpAddr, port: u16, timeout_settings: Option, ) -> GDResult> { @@ -178,7 +178,7 @@ fn extract_players(server_vars: &mut HashMap, players_maximum: u /// If there are parsing problems using the `query` function, you can directly /// get the server's values using this function. pub fn query_vars( - address: &Ipv4Addr, + address: &IpAddr, port: u16, timeout_settings: Option, ) -> GDResult> { @@ -188,7 +188,7 @@ pub fn query_vars( /// Query a server by providing the address, the port and timeout settings. /// Providing None to the timeout settings results in using the default values. /// (TimeoutSettings::[default](TimeoutSettings::default)). -pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { +pub fn query(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { let mut server_vars = query_vars(address, port, timeout_settings)?; let players_maximum = server_vars diff --git a/src/protocols/gamespy/protocols/three/protocol.rs b/src/protocols/gamespy/protocols/three/protocol.rs index fb8cfe3..2cd6afc 100644 --- a/src/protocols/gamespy/protocols/three/protocol.rs +++ b/src/protocols/gamespy/protocols/three/protocol.rs @@ -5,7 +5,7 @@ use crate::protocols::types::TimeoutSettings; use crate::socket::{Socket, UdpSocket}; use crate::{GDError, GDResult}; use std::collections::HashMap; -use std::net::Ipv4Addr; +use std::net::IpAddr; const THIS_SESSION_ID: u32 = 1; @@ -43,7 +43,7 @@ struct GameSpy3 { const PACKET_SIZE: usize = 2048; impl GameSpy3 { - fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { let socket = UdpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -104,7 +104,7 @@ impl GameSpy3 { } } -fn get_server_packets(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult>> { +fn get_server_packets(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult>> { let mut gs3 = GameSpy3::new(address, port, timeout_settings)?; let challenge = gs3.make_initial_handshake()?; @@ -165,7 +165,7 @@ fn data_to_map(packet: &[u8]) -> GDResult<(HashMap, Vec)> { /// If there are parsing problems using the `query` function, you can directly /// get the server's values using this function. pub fn query_vars( - address: &Ipv4Addr, + address: &IpAddr, port: u16, timeout_settings: Option, ) -> GDResult> { @@ -308,7 +308,7 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< /// Query a server by providing the address, the port and timeout settings. /// Providing None to the timeout settings results in using the default values. /// (TimeoutSettings::[default](TimeoutSettings::default)). -pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { +pub fn query(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { let packets = get_server_packets(address, port, timeout_settings)?; let (mut server_vars, remaining_data) = data_to_map(packets.get(0).ok_or(GDError::PacketBad)?)?; diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs index 6fa3632..371bb05 100644 --- a/src/protocols/minecraft/protocol/bedrock.rs +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -1,7 +1,7 @@ // This file has code that has been documented by the NodeJS GameDig library // (MIT) from https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -19,7 +19,7 @@ pub struct Bedrock { } impl Bedrock { - fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { let socket = UdpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -93,7 +93,7 @@ impl Bedrock { }) } - pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { Bedrock::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index 6e9b5f5..f547bfd 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -1,4 +1,4 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -31,7 +31,7 @@ pub struct Java { } impl Java { - fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { let socket = TcpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -139,7 +139,7 @@ impl Java { }) } - pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { Java::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_bv1_8.rs b/src/protocols/minecraft/protocol/legacy_bv1_8.rs index 9e742dd..bb2a045 100644 --- a/src/protocols/minecraft/protocol/legacy_bv1_8.rs +++ b/src/protocols/minecraft/protocol/legacy_bv1_8.rs @@ -1,4 +1,4 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -16,7 +16,7 @@ pub struct LegacyBV1_8 { } impl LegacyBV1_8 { - fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { let socket = TcpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -60,7 +60,7 @@ impl LegacyBV1_8 { }) } - pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { LegacyBV1_8::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_v1_4.rs b/src/protocols/minecraft/protocol/legacy_v1_4.rs index ff79f57..02a837e 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_4.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_4.rs @@ -1,4 +1,4 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -16,7 +16,7 @@ pub struct LegacyV1_4 { } impl LegacyV1_4 { - fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { let socket = TcpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -64,7 +64,7 @@ impl LegacyV1_4 { }) } - pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { LegacyV1_4::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/legacy_v1_6.rs b/src/protocols/minecraft/protocol/legacy_v1_6.rs index 1ff0545..c976bd0 100644 --- a/src/protocols/minecraft/protocol/legacy_v1_6.rs +++ b/src/protocols/minecraft/protocol/legacy_v1_6.rs @@ -1,4 +1,4 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ bufferer::{Bufferer, Endianess}, protocols::{ @@ -16,7 +16,7 @@ pub struct LegacyV1_6 { } impl LegacyV1_6 { - fn new(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + fn new(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { let socket = TcpSocket::new(address, port)?; socket.apply_timeout(timeout_settings)?; @@ -93,7 +93,7 @@ impl LegacyV1_6 { LegacyV1_6::get_response(&mut buffer) } - pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { + pub fn query(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { LegacyV1_6::new(address, port, timeout_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 2911ab1..a424b51 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,4 +1,4 @@ -use std::net::Ipv4Addr; +use std::net::IpAddr; use crate::{ protocols::minecraft::{ protocol::{ @@ -25,7 +25,7 @@ mod legacy_v1_6; /// Queries a Minecraft server with all the protocol variants one by one (Java /// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). -pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { +pub fn query(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { if let Ok(response) = query_java(address, port, timeout_settings.clone()) { return Ok(response); } @@ -42,12 +42,12 @@ pub fn query(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { +pub fn query_java(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { Java::query(address, port, timeout_settings) } /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &Ipv4Addr, port: u16, timeout_settings: Option) -> GDResult { +pub fn query_legacy(address: &IpAddr, port: u16, timeout_settings: Option) -> GDResult { if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { return Ok(response); } @@ -66,7 +66,7 @@ pub fn query_legacy(address: &Ipv4Addr, port: u16, timeout_settings: Option