feat: add savage 2 support (#169)

* feat: savage 2 support

* fix: add savage 2 to definitions

* chore: run rustfmt

* fix: config serde use

* fix: remove needless borrow

* docs: add savage 2 to protocols.md
This commit is contained in:
CosminPerRam 2023-12-10 19:39:26 +02:00 committed by GitHub
parent dd037daa04
commit af8e1e9b1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 148 additions and 57 deletions

View file

@ -9,6 +9,7 @@ Games:
- [Conan Exiles](https://store.steampowered.com/app/440900/Conan_Exiles/) support.
- [Post Scriptum](https://store.steampowered.com/app/736220/Post_Scriptum/) support.
- [Squad](https://store.steampowered.com/app/393380/Squad/) support.
- [Savage 2](https://savage2.net/) support.
- Added a valve protocol query example.
Protocols:

View file

@ -73,6 +73,7 @@ Beware of the `Notes` column, as it contains information about query port offset
| Unreal Tournament 2004 | UT2004 | Unreal2 | Query port offset: 1 |
| Post Scriptum | POSTSCRIPTUM | Valve | |
| Squad | SQUAD | Valve | |
| Savage 2 | SAVAGE2 | Proprietary | |
## Planned to add support:
_

View file

@ -1,13 +1,14 @@
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) <br> 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) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy2.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. |
| Quake | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake3.js) | |
| Unreal2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig) | Sometimes servers send strings that node-gamedig would treat as latin1 that are UTF-8 encoded, when this happens the remove color code breaks because latin1 decodes the colour sequences differently. Some games provide additional info at the end of the server info packet, this is not currently handled (see the node implementation). Some games use a bot player to denote the team names, this is not currently handled. |
| 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) <br> 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) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy2.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. |
| Quake | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake3.js) | |
| Unreal2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig) | Sometimes servers send strings that node-gamedig would treat as latin1 that are UTF-8 encoded, when this happens the remove color code breaks because latin1 decodes the colour sequences differently. Some games provide additional info at the end of the server info packet, this is not currently handled (see the node implementation). Some games use a bot player to denote the team names, this is not currently handled. |
| Savage 2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js) | |
## Planned to add support:
_

View file

@ -5,53 +5,57 @@ In the case that a field that performs the same function exists in the current c
# Response table
| Field | Generic | GameSpy(1) | GameSpy(2) | GameSpy(3) | Minecraft(Java) | Minecraft(Bedrock) | Valve | Quake | Unreal2 | Proprietary: FFOW | Proprietary: TheShip | Proprietary: JC2MP |
|----------------------|------------------|---------------------------|---------------|---------------------------|-----------------------|--------------------|-----------------------------------|---------------------------|--------------------------------|-------------------|---------------------------|--------------------|
| name | `Option<String>` | `String` | `String` | `String` | | `String` | `String` | `String` | `String` | `String` | `String` | `String` |
| description | `Option<String>` | | | | `String` | | | | | `String` | | `String` |
| game_mode | `Option<String>` | `String` | | `String` | | `Option<GameMode>` | `String` | | `String` | `String` | `String` | |
| game_version | `Option<String>` | `String` | | `String` | `String` | | `String` | `String` | | `String` | `String` | `String` |
| map | `Option<String>` | `String` | `String` | `String` | | `Option<String>` | `String` | `String` | `String` | `String` | `String` | |
| players_maxmimum | `u32` | `u32` | `u32` | `u32` | `u32` | `u32` | `u8` | `u8` | `u32` | `u8` | `u8` | `u32` |
| players_online | `u32` | `u32` | `u32` | `u32` | `u32` | `u32` | `u8` | `u8` | `u32` | `u8` | `u8` | `u32` |
| players_bots | `Option<u32>` | | | | | | `u8` | | | | `u8` | |
| has_password | `Option<bool>` | `bool` | `bool` | `bool` | | | `bool` | | | `bool` | `bool` | `bool` |
| players_minimum | | `Option<u8>` | `Option<u8>` | `Option<u8>` | | | | | | | | |
| players | | `Vec<Player>` | `Vec<Player>` | `Vec<Player>` | `Option<Vec<Player>>` | | `Option<Vec<ServerPlayer>>` | `Vec<P>` | `Vec<Player>` | | `Vec<TheShipPlayer>` | `Vec<Player>` |
| tournament | | `bool` | | `bool` | | | | | | | | |
| unused_entries | | `Hashmap<String, String>` | | `HashMap<String, String>` | | | | `HashMap<String, String>` | | | | |
| teams | | | `Vec<Team>` | `Vec<Team>` | | | | | | | | |
| protocol_version | | | | | `i32` | `String` | `u8` | | | `u8` | `u8` | |
| server_type | | | | | `Server` | `Server` | `Server` | | | | `Server` | |
| rules | | | | | | | `Option<HashMap<String, String>>` | | `HashMap<String, Vec<String>>` | | `HashMap<String, String>` | |
| environment_type | | | | | | | `Environment` | | | `Environment` | | |
| vac_secured | | | | | | | `bool` | | | `bool` | `bool` | |
| map_title | | `Option<String>` | | | | | | | | | | |
| admin_contact | | `Option<String>` | | | | | | | | | | |
| admin_name | | `Option<String>` | | | | | | | | | | |
| favicon | | | | | `Option<String>` | | | | | | | |
| previews_chat | | | | | `Option<bool>` | | | | | | | |
| enforces_secure_chat | | | | | `Option<bool>` | | | | | | | |
| edition | | | | | | `String` | | | | | | |
| id | | | | | | `String` | | | `String` | | | |
| the_ship | | | | | | | `Option<TheShip>` | | | | | |
| is_mod | | | | | | | `bool` | | | | | |
| extra_data | | | | | | | `Option<ExtraData>` | | | | | |
| mod_data | | | | | | | `Option<ModData>` | | | | | |
| folder | | | | | | | `String` | | | | | |
| appid | | | | | | | `u32` | | | | | |
| active_mod | | | | | | | | | | `String` | | |
| round | | | | | | | | | | `u8` | | |
| rounds_maximum | | | | | | | | | | `u8` | | |
| time_left | | | | | | | | | | `u16` | | |
| port | | | | | | | | | `u32` | | `Option<u16>` | |
| steam_id | | | | | | | | | | | `Option<u64>` | |
| tv_port | | | | | | | | | | | `Option<u16>` | |
| tv_name | | | | | | | | | | | `Option<String>` | |
| keywords | | | | | | | | | | | `Option<String>` | |
| mode | | | | | | | | | | | `u8` | |
| witnesses | | | | | | | | | | | `u8` | |
| duration | | | | | | | | | | | `u8` | |
| query_port | | | | | | | | | `u32` | | | |
| ip | | | | | | | | | `String` | | | |
| mutators | | | | | | | | | `HashSet<String>` | | | |
| 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` |

View file

@ -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)),

View file

@ -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)?
}

View file

@ -0,0 +1,8 @@
/// The implementation.
/// Reference: [Node-GameGig](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js)
pub mod protocol;
/// All types used by the implementation.
pub mod types;
pub use protocol::*;
pub use types::*;

View file

@ -0,0 +1,37 @@
use crate::buffer::{Buffer, Utf8Decoder};
use crate::games::savage2::types::Response;
use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, UdpSocket};
use crate::GDResult;
use byteorder::LittleEndian;
use std::net::{IpAddr, SocketAddr};
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
pub fn query_with_timeout(
address: &IpAddr,
port: Option<u16>,
timeout_settings: Option<TimeoutSettings>,
) -> GDResult<Response> {
let addr = &SocketAddr::new(*address, port.unwrap_or(11235));
let mut socket = UdpSocket::new(addr, &timeout_settings)?;
socket.send(&[0x01])?;
let data = socket.receive(None)?;
let mut buffer = Buffer::<LittleEndian>::new(&data);
buffer.move_cursor(12)?;
Ok(Response {
name: buffer.read_string::<Utf8Decoder>(None)?,
players_online: buffer.read::<u8>()?,
players_maximum: buffer.read::<u8>()?,
time: buffer.read_string::<Utf8Decoder>(None)?,
map: buffer.read_string::<Utf8Decoder>(None)?,
next_map: buffer.read_string::<Utf8Decoder>(None)?,
location: buffer.read_string::<Utf8Decoder>(None)?,
players_minimum: buffer.read::<u8>()?,
game_mode: buffer.read_string::<Utf8Decoder>(None)?,
protocol_version: buffer.read_string::<Utf8Decoder>(None)?,
level_minimum: buffer.read::<u8>()?,
})
}

View file

@ -0,0 +1,30 @@
use crate::protocols::types::CommonResponse;
use crate::protocols::GenericResponse;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
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() }
}

View file

@ -18,6 +18,7 @@ pub enum ProprietaryProtocol {
Minecraft(Option<minecraft::types::Server>),
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