>` | `Vec` | `Vec` | | `Vec` | `Vec` |
-| tournament | | `bool` | | `bool` | | | | | | | | |
-| unused_entries | | `Hashmap` | | `HashMap` | | | | `HashMap` | | | | |
-| teams | | | `Vec` | `Vec` | | | | | | | | |
-| protocol_version | | | | | `i32` | `String` | `u8` | | | `u8` | `u8` | |
-| server_type | | | | | `Server` | `Server` | `Server` | | | | `Server` | |
-| rules | | | | | | | `Option>` | | `HashMap>` | | `HashMap` | |
-| environment_type | | | | | | | `Environment` | | | `Environment` | | |
-| vac_secured | | | | | | | `bool` | | | `bool` | `bool` | |
-| map_title | | `Option` | | | | | | | | | | |
-| admin_contact | | `Option` | | | | | | | | | | |
-| admin_name | | `Option` | | | | | | | | | | |
-| favicon | | | | | `Option` | | | | | | | |
-| previews_chat | | | | | `Option` | | | | | | | |
-| enforces_secure_chat | | | | | `Option` | | | | | | | |
-| edition | | | | | | `String` | | | | | | |
-| id | | | | | | `String` | | | `String` | | | |
-| the_ship | | | | | | | `Option` | | | | | |
-| is_mod | | | | | | | `bool` | | | | | |
-| extra_data | | | | | | | `Option` | | | | | |
-| mod_data | | | | | | | `Option` | | | | | |
-| folder | | | | | | | `String` | | | | | |
-| appid | | | | | | | `u32` | | | | | |
-| active_mod | | | | | | | | | | `String` | | |
-| round | | | | | | | | | | `u8` | | |
-| rounds_maximum | | | | | | | | | | `u8` | | |
-| time_left | | | | | | | | | | `u16` | | |
-| port | | | | | | | | | `u32` | | `Option` | |
-| steam_id | | | | | | | | | | | `Option` | |
-| tv_port | | | | | | | | | | | `Option` | |
-| tv_name | | | | | | | | | | | `Option` | |
-| keywords | | | | | | | | | | | `Option` | |
-| mode | | | | | | | | | | | `u8` | |
-| witnesses | | | | | | | | | | | `u8` | |
-| duration | | | | | | | | | | | `u8` | |
-| query_port | | | | | | | | | `u32` | | | |
-| ip | | | | | | | | | `String` | | | |
-| mutators | | | | | | | | | `HashSet` | | | |
+| Field | Generic | GameSpy(1) | GameSpy(2) | GameSpy(3) | Minecraft(Java) | Minecraft(Bedrock) | Valve | Quake | Unreal2 | Proprietary: FFOW | Proprietary: TheShip | Proprietary: JC2MP | Proprietary: Savage 2 |
+|----------------------|----------|------------|------------|------------|-----------------|--------------------|---------------|-----------|------------|-------------------|----------------------|--------------------|-----------------------|
+| name | `Option` | `String` | `String` | `String` | | `String` | `String` | `String` | `String` | `String` | `String` | `String` | `String` |
+| description | `Option` | | | | `String` | | | | | `String` | | `String` | |
+| game_mode | `Option` | `String` | | `String` | | `Option` | `String` | | `String` | `String` | `String` | | `String` |
+| game_version | `Option` | `String` | | `String` | `String` | | `String` | `String` | | `String` | `String` | `String` | |
+| map | `Option` | `String` | `String` | `String` | | `Option` | `String` | `String` | `String` | `String` | `String` | | `String` |
+| players_maxmimum | `u32` | `u32` | `u32` | `u32` | `u32` | `u32` | `u8` | `u8` | `u32` | `u8` | `u8` | `u32` | `u8` |
+| players_online | `u32` | `u32` | `u32` | `u32` | `u32` | `u32` | `u8` | `u8` | `u32` | `u8` | `u8` | `u32` | `u8` |
+| players_bots | `Option` | | | | | | `u8` | | | | `u8` | | |
+| has_password | `Option` | `bool` | `bool` | `bool` | | | `bool` | | | `bool` | `bool` | `bool` | |
+| players_minimum | | `Option` | `Option` | `Option` | | | | | | | | | `u8` |
+| players | | `Vec` | `Vec` | `Vec` | `Option>` | | `Option>` | `Vec ` | `Vec` | | `Vec` | `Vec` | |
+| tournament | | `bool` | | `bool` | | | | | | | | | |
+| unused_entries | | `Hashmap` | | `HashMap` | | | | `HashMap` | | | | | |
+| teams | | | `Vec` | `Vec` | | | | | | | | | |
+| protocol_version | | | | | `i32` | `String` | `u8` | | | `u8` | `u8` | | `String` |
+| server_type | | | | | `Server` | `Server` | `Server` | | | | `Server` | | |
+| rules | | | | | | | `Option>` | | `HashMap>` | | `HashMap` | | |
+| environment_type | | | | | | | `Environment` | | | `Environment` | | | |
+| vac_secured | | | | | | | `bool` | | | `bool` | `bool` | | |
+| map_title | | `Option` | | | | | | | | | | | |
+| admin_contact | | `Option` | | | | | | | | | | | |
+| admin_name | | `Option` | | | | | | | | | | | |
+| favicon | | | | | `Option` | | | | | | | | |
+| previews_chat | | | | | `Option` | | | | | | | | |
+| enforces_secure_chat | | | | | `Option` | | | | | | | | |
+| edition | | | | | | `String` | | | | | | | |
+| id | | | | | | `String` | | | `String` | | | | |
+| the_ship | | | | | | | `Option` | | | | | | |
+| is_mod | | | | | | | `bool` | | | | | | |
+| extra_data | | | | | | | `Option` | | | | | | |
+| mod_data | | | | | | | `Option` | | | | | | |
+| folder | | | | | | | `String` | | | | | | |
+| appid | | | | | | | `u32` | | | | | | |
+| active_mod | | | | | | | | | | `String` | | | |
+| round | | | | | | | | | | `u8` | | | |
+| rounds_maximum | | | | | | | | | | `u8` | | | |
+| time_left | | | | | | | | | | `u16` | | | |
+| port | | | | | | | | | `u32` | | `Option` | | |
+| steam_id | | | | | | | | | | | `Option` | | |
+| tv_port | | | | | | | | | | | `Option` | | |
+| tv_name | | | | | | | | | | | `Option` | | |
+| keywords | | | | | | | | | | | `Option` | | |
+| mode | | | | | | | | | | | `u8` | | |
+| witnesses | | | | | | | | | | | `u8` | | |
+| duration | | | | | | | | | | | `u8` | | |
+| query_port | | | | | | | | | `u32` | | | | |
+| ip | | | | | | | | | `String` | | | | |
+| mutators | | | | | | | | | `HashSet` | | | | |
+| next_map | | | | | | | | | | | | | `String` |
+| location | | | | | | | | | | | | | `String` |
+| level_minimum | | | | | | | | | | | | | `String` |
+| time | | | | | | | | | | | | | `String` |
diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs
index 18e033b..e157f99 100644
--- a/crates/lib/src/games/definitions.rs
+++ b/crates/lib/src/games/definitions.rs
@@ -88,6 +88,7 @@ pub static GAMES: Map<&'static str, Game> = phf_map! {
"q3a" => game!("Quake 3 Arena", 27960, Protocol::Quake(QuakeVersion::Three)),
"ror2" => game!("Risk of Rain 2", 27016, Protocol::Valve(Engine::new(632_360))),
"rust" => game!("Rust", 27015, Protocol::Valve(Engine::new(252_490))),
+ "savage2" => game!("Savage 2", 11235, Protocol::PROPRIETARY(ProprietaryProtocol::Savage2)),
"sco" => game!("Sven Co-op", 27015, Protocol::Valve(Engine::new_gold_src(false))),
"sdtd" => game!("7 Days to Die", 26900, Protocol::Valve(Engine::new(251_570))),
"sof2" => game!("Soldier of Fortune 2", 20100, Protocol::Quake(QuakeVersion::Three)),
diff --git a/crates/lib/src/games/mod.rs b/crates/lib/src/games/mod.rs
index e8e5641..add1f19 100644
--- a/crates/lib/src/games/mod.rs
+++ b/crates/lib/src/games/mod.rs
@@ -21,6 +21,8 @@ pub mod ffow;
pub mod jc2m;
/// Minecraft
pub mod minecraft;
+/// Savage 2
+pub mod savage2;
/// The Ship
pub mod theship;
@@ -117,6 +119,9 @@ pub fn query_with_timeout_and_extra_settings(
}
Protocol::PROPRIETARY(protocol) => {
match protocol {
+ ProprietaryProtocol::Savage2 => {
+ savage2::query_with_timeout(address, port, timeout_settings).map(Box::new)?
+ }
ProprietaryProtocol::TheShip => {
theship::query_with_timeout(address, port, timeout_settings).map(Box::new)?
}
diff --git a/crates/lib/src/games/savage2/mod.rs b/crates/lib/src/games/savage2/mod.rs
new file mode 100644
index 0000000..88d57f2
--- /dev/null
+++ b/crates/lib/src/games/savage2/mod.rs
@@ -0,0 +1,8 @@
+/// The implementation.
+/// Reference: [Node-GameGig](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js)
+pub mod protocol;
+/// All types used by the implementation.
+pub mod types;
+
+pub use protocol::*;
+pub use types::*;
diff --git a/crates/lib/src/games/savage2/protocol.rs b/crates/lib/src/games/savage2/protocol.rs
new file mode 100644
index 0000000..dbb1eb8
--- /dev/null
+++ b/crates/lib/src/games/savage2/protocol.rs
@@ -0,0 +1,37 @@
+use crate::buffer::{Buffer, Utf8Decoder};
+use crate::games::savage2::types::Response;
+use crate::protocols::types::TimeoutSettings;
+use crate::socket::{Socket, UdpSocket};
+use crate::GDResult;
+use byteorder::LittleEndian;
+use std::net::{IpAddr, SocketAddr};
+
+pub fn query(address: &IpAddr, port: Option) -> GDResult { query_with_timeout(address, port, None) }
+
+pub fn query_with_timeout(
+ address: &IpAddr,
+ port: Option,
+ timeout_settings: Option,
+) -> GDResult {
+ let addr = &SocketAddr::new(*address, port.unwrap_or(11235));
+ let mut socket = UdpSocket::new(addr, &timeout_settings)?;
+ socket.send(&[0x01])?;
+ let data = socket.receive(None)?;
+ let mut buffer = Buffer::::new(&data);
+
+ buffer.move_cursor(12)?;
+
+ Ok(Response {
+ name: buffer.read_string::(None)?,
+ players_online: buffer.read::()?,
+ players_maximum: buffer.read::()?,
+ time: buffer.read_string::(None)?,
+ map: buffer.read_string::(None)?,
+ next_map: buffer.read_string::(None)?,
+ location: buffer.read_string::(None)?,
+ players_minimum: buffer.read::()?,
+ game_mode: buffer.read_string::(None)?,
+ protocol_version: buffer.read_string::(None)?,
+ level_minimum: buffer.read::()?,
+ })
+}
diff --git a/crates/lib/src/games/savage2/types.rs b/crates/lib/src/games/savage2/types.rs
new file mode 100644
index 0000000..d72068b
--- /dev/null
+++ b/crates/lib/src/games/savage2/types.rs
@@ -0,0 +1,30 @@
+use crate::protocols::types::CommonResponse;
+use crate::protocols::GenericResponse;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[derive(Debug, Clone, PartialEq)]
+pub struct Response {
+ pub name: String,
+ pub players_online: u8,
+ pub players_maximum: u8,
+ pub players_minimum: u8,
+ pub time: String,
+ pub map: String,
+ pub next_map: String,
+ pub location: String,
+ pub game_mode: String,
+ pub protocol_version: String,
+ pub level_minimum: u8,
+}
+
+impl CommonResponse for Response {
+ fn as_original(&self) -> GenericResponse { GenericResponse::Savage2(self) }
+
+ fn name(&self) -> Option<&str> { Some(&self.name) }
+ fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) }
+ fn map(&self) -> Option<&str> { Some(&self.map) }
+ fn players_maximum(&self) -> u32 { self.players_maximum.into() }
+ fn players_online(&self) -> u32 { self.players_online.into() }
+}
diff --git a/crates/lib/src/protocols/types.rs b/crates/lib/src/protocols/types.rs
index 4249d5c..9bd4b53 100644
--- a/crates/lib/src/protocols/types.rs
+++ b/crates/lib/src/protocols/types.rs
@@ -18,6 +18,7 @@ pub enum ProprietaryProtocol {
Minecraft(Option),
FFOW,
JC2M,
+ Savage2,
}
/// Enumeration of all valid protocol types
@@ -48,6 +49,8 @@ pub enum GenericResponse<'a> {
FFOW(&'a crate::games::ffow::Response),
#[cfg(feature = "games")]
JC2M(&'a crate::games::jc2m::Response),
+ #[cfg(feature = "games")]
+ Savage2(&'a crate::games::savage2::Response),
}
/// All player types