mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
refator: copy cli into mono
This commit is contained in:
parent
66ae3c296e
commit
80f6b87991
63 changed files with 244 additions and 34 deletions
46
crates/lib/src/games/battalion1944.rs
Normal file
46
crates/lib/src/games/battalion1944.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use crate::{
|
||||
protocols::valve::{self, game, SteamApp},
|
||||
GDErrorKind::TypeParse,
|
||||
GDResult,
|
||||
};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<game::Response> {
|
||||
let mut valve_response = valve::query(
|
||||
&SocketAddr::new(*address, port.unwrap_or(7780)),
|
||||
SteamApp::BATTALION1944.as_engine(),
|
||||
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(|e| TypeParse.context(e))?;
|
||||
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(|e| TypeParse.context(e))?;
|
||||
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_mode = bat_gamemode.clone();
|
||||
rules.remove("bat_gamemode_s");
|
||||
}
|
||||
|
||||
rules.remove("bat_map_s");
|
||||
}
|
||||
|
||||
Ok(game::Response::new_from_valve_response(valve_response))
|
||||
}
|
||||
92
crates/lib/src/games/definitions.rs
Normal file
92
crates/lib/src/games/definitions.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
//! Static definitions of currently supported games
|
||||
|
||||
use crate::protocols::{
|
||||
gamespy::GameSpyVersion,
|
||||
minecraft::{LegacyGroup, Server},
|
||||
quake::QuakeVersion,
|
||||
valve::SteamApp,
|
||||
Protocol,
|
||||
};
|
||||
use crate::Game;
|
||||
|
||||
use crate::protocols::types::ProprietaryProtocol;
|
||||
use phf::{phf_map, Map};
|
||||
|
||||
macro_rules! game {
|
||||
($name: literal, $default_port: literal, $protocol: expr) => {
|
||||
Game {
|
||||
name: $name,
|
||||
default_port: $default_port,
|
||||
protocol: $protocol,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Map of all currently supported games
|
||||
pub static GAMES: Map<&'static str, Game> = phf_map! {
|
||||
// Query with all minecraft protocols
|
||||
"minecraft" => game!("Minecraft", 25565, Protocol::Minecraft(None)),
|
||||
// Query with specific minecraft protocols
|
||||
"minecraftbedrock" => game!("Minecraft (bedrock)", 19132, Protocol::Minecraft(Some(Server::Bedrock))),
|
||||
"minecraftpocket" => game!("Minecraft (pocket)", 19132, Protocol::Minecraft(Some(Server::Bedrock))),
|
||||
"minecraftjava" => game!("Minecraft (java)", 25565, Protocol::Minecraft(Some(Server::Java))),
|
||||
"minecraftlegacy16" => game!("Minecraft (legacy v1.6)", 25565, Protocol::Minecraft(Some(Server::Legacy(LegacyGroup::V1_6)))),
|
||||
"minecraftlegacy15" => game!("Minecraft (legacy v1.4-1.5)", 25565, Protocol::Minecraft(Some(Server::Legacy(LegacyGroup::V1_5)))),
|
||||
"minecraftlegacy13" => game!("Minecraft (legacy vB1.8-1.3)", 25565, Protocol::Minecraft(Some(Server::Legacy(LegacyGroup::V1_3)))),
|
||||
"alienswarm" => game!("Alien Swarm", 27015, Protocol::Valve(SteamApp::ALIENSWARM)),
|
||||
"aoc" => game!("Age of Chivalry", 27015, Protocol::Valve(SteamApp::AOC)),
|
||||
"a2oa" => game!("ARMA 2: Operation Arrowhead", 2304, Protocol::Valve(SteamApp::A2OA)),
|
||||
"ase" => game!("ARK: Survival Evolved", 27015, Protocol::Valve(SteamApp::ASE)),
|
||||
"asrd" => game!("Alien Swarm: Reactive Drop", 2304, Protocol::Valve(SteamApp::ASRD)),
|
||||
"avorion" => game!("Avorion", 27020, Protocol::Valve(SteamApp::AVORION)),
|
||||
"barotrauma" => game!("Barotrauma", 27016, Protocol::Valve(SteamApp::BAROTRAUMA)),
|
||||
"battalion1944" => game!("Battalion 1944", 7780, Protocol::Valve(SteamApp::BATTALION1944)),
|
||||
"brainbread2" => game!("BrainBread 2", 27015, Protocol::Valve(SteamApp::BRAINBREAD2)),
|
||||
"battlefield1942" => game!("Battlefield 1942", 23000, Protocol::Gamespy(GameSpyVersion::One)),
|
||||
"blackmesa" => game!("Black Mesa", 27015, Protocol::Valve(SteamApp::BLACKMESA)),
|
||||
"ballisticoverkill" => game!("Ballistic Overkill", 27016, Protocol::Valve(SteamApp::BALLISTICOVERKILL)),
|
||||
"codenamecure" => game!("Codename CURE", 27015, Protocol::Valve(SteamApp::CODENAMECURE)),
|
||||
"colonysurvival" => game!("Colony Survival", 27004, Protocol::Valve(SteamApp::COLONYSURVIVAL)),
|
||||
"counterstrike" => game!("Counter-Strike", 27015, Protocol::Valve(SteamApp::COUNTERSTRIKE)),
|
||||
"cscz" => game!("Counter Strike: Condition Zero", 27015, Protocol::Valve(SteamApp::CSCZ)),
|
||||
"csgo" => game!("Counter-Strike: Global Offensive", 27015, Protocol::Valve(SteamApp::CSGO)),
|
||||
"css" => game!("Counter-Strike: Source", 27015, Protocol::Valve(SteamApp::CSS)),
|
||||
"creativerse" => game!("Creativerse", 26901, Protocol::Valve(SteamApp::CREATIVERSE)),
|
||||
"crysiswars" => game!("Crysis Wars", 64100, Protocol::Gamespy(GameSpyVersion::Three)),
|
||||
"dod" => game!("Day of Defeat", 27015, Protocol::Valve(SteamApp::DOD)),
|
||||
"dods" => game!("Day of Defeat: Source", 27015, Protocol::Valve(SteamApp::DODS)),
|
||||
"doi" => game!("Day of Infamy", 27015, Protocol::Valve(SteamApp::DOI)),
|
||||
"dst" => game!("Don't Starve Together", 27016, Protocol::Valve(SteamApp::DST)),
|
||||
"ffow" => game!("Frontlines: Fuel of War", 5478, Protocol::PROPRIETARY(ProprietaryProtocol::FFOW)),
|
||||
"garrysmod" => game!("Garry's Mod", 27016, Protocol::Valve(SteamApp::GARRYSMOD)),
|
||||
"hl2d" => game!("Half-Life 2 Deathmatch", 27015, Protocol::Valve(SteamApp::HL2D)),
|
||||
"hce" => game!("Halo: Combat Evolved", 2302, Protocol::Gamespy(GameSpyVersion::Two)),
|
||||
"hlds" => game!("Half-Life Deathmatch: Source", 27015, Protocol::Valve(SteamApp::HLDS)),
|
||||
"hll" => game!("Hell Let Loose", 26420, Protocol::Valve(SteamApp::HLL)),
|
||||
"insurgency" => game!("Insurgency", 27015, Protocol::Valve(SteamApp::INSURGENCY)),
|
||||
"imic" => game!("Insurgency: Modern Infantry Combat", 27015, Protocol::Valve(SteamApp::IMIC)),
|
||||
"insurgencysandstorm" => game!("Insurgency: Sandstorm", 27131, Protocol::Valve(SteamApp::INSURGENCYSANDSTORM)),
|
||||
"left4dead" => game!("Left 4 Dead", 27015, Protocol::Valve(SteamApp::LEFT4DEAD)),
|
||||
"left4dead2" => game!("Left 4 Dead 2", 27015, Protocol::Valve(SteamApp::LEFT4DEAD2)),
|
||||
"ohd" => game!("Operation: Harsh Doorstop", 27005, Protocol::Valve(SteamApp::OHD)),
|
||||
"onset" => game!("Onset", 7776, Protocol::Valve(SteamApp::ONSET)),
|
||||
"projectzomboid" => game!("Project Zomboid", 16261, Protocol::Valve(SteamApp::PROJECTZOMBOID)),
|
||||
"quake1" => game!("Quake 1", 27500, Protocol::Quake(QuakeVersion::One)),
|
||||
"quake2" => game!("Quake 2", 27910, Protocol::Quake(QuakeVersion::Two)),
|
||||
"quake3" => game!("Quake 3: Arena", 27960, Protocol::Quake(QuakeVersion::Three)),
|
||||
"ror2" => game!("Risk of Rain 2", 27016, Protocol::Valve(SteamApp::ROR2)),
|
||||
"rust" => game!("Rust", 27015, Protocol::Valve(SteamApp::RUST)),
|
||||
"sco" => game!("Sven Co-op", 27015, Protocol::Valve(SteamApp::SCO)),
|
||||
"7d2d" => game!("7 Days To Die", 26900, Protocol::Valve(SteamApp::SD2D)),
|
||||
"sof2" => game!("Soldier of Fortune 2", 20100, Protocol::Quake(QuakeVersion::Three)),
|
||||
"serioussam" => game!("Serious Sam", 25601, Protocol::Gamespy(GameSpyVersion::One)),
|
||||
"theforest" => game!("The Forest", 27016, Protocol::Valve(SteamApp::THEFOREST)),
|
||||
"teamfortress2" => game!("Team Fortress 2", 27015, Protocol::Valve(SteamApp::TEAMFORTRESS2)),
|
||||
"tfc" => game!("Team Fortress Classic", 27015, Protocol::Valve(SteamApp::TFC)),
|
||||
"theship" => game!("The Ship", 27015, Protocol::PROPRIETARY(ProprietaryProtocol::TheShip)),
|
||||
"unturned" => game!("Unturned", 27015, Protocol::Valve(SteamApp::UNTURNED)),
|
||||
"unrealtournament" => game!("Unreal Tournament", 7778, Protocol::Gamespy(GameSpyVersion::One)),
|
||||
"vrising" => game!("V Rising", 27016, Protocol::Valve(SteamApp::VRISING)),
|
||||
"jc2m" => game!("Just Cause 2: Multiplayer", 7777, Protocol::PROPRIETARY(ProprietaryProtocol::JC2M)),
|
||||
"warsow" => game!("Warsow", 44400, Protocol::Quake(QuakeVersion::Three)),
|
||||
};
|
||||
119
crates/lib/src/games/ffow.rs
Normal file
119
crates/lib/src/games/ffow.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::types::{CommonResponse, TimeoutSettings};
|
||||
use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol};
|
||||
use crate::protocols::GenericResponse;
|
||||
use crate::GDResult;
|
||||
use byteorder::LittleEndian;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
/// The query response.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Response {
|
||||
/// Protocol used by the server.
|
||||
pub protocol_version: u8,
|
||||
/// Name of the server.
|
||||
pub name: String,
|
||||
/// Map name.
|
||||
pub active_mod: String,
|
||||
/// Running game mode.
|
||||
pub game_mode: String,
|
||||
/// The version that the server is running on.
|
||||
pub game_version: String,
|
||||
/// Description of the server.
|
||||
pub description: String,
|
||||
/// Current map.
|
||||
pub map: String,
|
||||
/// Number of players on the server.
|
||||
pub players_online: u8,
|
||||
/// Maximum number of players the server reports it can hold.
|
||||
pub players_maximum: u8,
|
||||
/// Dedicated, NonDedicated or SourceTV
|
||||
pub server_type: Server,
|
||||
/// The Operating System that the server is on.
|
||||
pub environment_type: Environment,
|
||||
/// Indicates whether the server requires a password.
|
||||
pub has_password: bool,
|
||||
/// Indicates whether the server uses VAC.
|
||||
pub vac_secured: bool,
|
||||
/// Current round index.
|
||||
pub round: u8,
|
||||
/// Maximum amount of rounds.
|
||||
pub rounds_maximum: u8,
|
||||
/// Time left for the current round in seconds.
|
||||
pub time_left: u16,
|
||||
}
|
||||
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::FFOW(self) }
|
||||
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.game_version) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u32 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u32 { self.players_online.into() }
|
||||
}
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response> {
|
||||
let mut client = ValveProtocol::new(
|
||||
&SocketAddr::new(*address, port.unwrap_or(5478)),
|
||||
timeout_settings,
|
||||
)?;
|
||||
let data = client.get_request_data(
|
||||
&Engine::GoldSrc(true),
|
||||
0,
|
||||
0x46,
|
||||
String::from("LSQ").into_bytes(),
|
||||
)?;
|
||||
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
let protocol_version = buffer.read::<u8>()?;
|
||||
let name = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let map = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let active_mod = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let game_mode = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let description = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let game_version = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
buffer.move_cursor(2)?;
|
||||
let players_online = buffer.read::<u8>()?;
|
||||
let players_maximum = buffer.read::<u8>()?;
|
||||
let server_type = Server::from_gldsrc(buffer.read::<u8>()?)?;
|
||||
let environment_type = Environment::from_gldsrc(buffer.read::<u8>()?)?;
|
||||
let has_password = buffer.read::<u8>()? == 1;
|
||||
let vac_secured = buffer.read::<u8>()? == 1;
|
||||
buffer.move_cursor(1)?; //average fps
|
||||
let round = buffer.read::<u8>()?;
|
||||
let rounds_maximum = buffer.read::<u8>()?;
|
||||
let time_left = buffer.read::<u16>()?;
|
||||
|
||||
Ok(Response {
|
||||
protocol_version,
|
||||
name,
|
||||
active_mod,
|
||||
game_mode,
|
||||
game_version,
|
||||
description,
|
||||
map,
|
||||
players_online,
|
||||
players_maximum,
|
||||
server_type,
|
||||
environment_type,
|
||||
has_password,
|
||||
vac_secured,
|
||||
round,
|
||||
rounds_maximum,
|
||||
time_left,
|
||||
})
|
||||
}
|
||||
9
crates/lib/src/games/gamespy.rs
Normal file
9
crates/lib/src/games/gamespy.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//! Gamespy game query modules
|
||||
|
||||
use crate::protocols::gamespy::game_query_mod;
|
||||
|
||||
game_query_mod!(battlefield1942, "Battlefield 1942", one, 23000);
|
||||
game_query_mod!(crysiswars, "Crysis Wars", three, 64100);
|
||||
game_query_mod!(hce, "Halo: Combat Evolved", two, 2302);
|
||||
game_query_mod!(serioussam, "Serious Sam", one, 25601);
|
||||
game_query_mod!(unrealtournament, "Unreal Tournament", one, 7778);
|
||||
129
crates/lib/src/games/jc2m.rs
Normal file
129
crates/lib/src/games/jc2m.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::gamespy::common::has_password;
|
||||
use crate::protocols::gamespy::three::{data_to_map, GameSpy3};
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings};
|
||||
use crate::protocols::GenericResponse;
|
||||
use crate::GDErrorKind::{PacketBad, TypeParse};
|
||||
use crate::{GDErrorKind, GDResult};
|
||||
use byteorder::BigEndian;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Player {
|
||||
name: String,
|
||||
steam_id: String,
|
||||
ping: u16,
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::JCMP2(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Response {
|
||||
game_version: String,
|
||||
description: String,
|
||||
name: String,
|
||||
has_password: bool,
|
||||
players: Vec<Player>,
|
||||
players_maximum: u32,
|
||||
players_online: u32,
|
||||
}
|
||||
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::JC2M(self) }
|
||||
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.game_version) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u32 { self.players_maximum }
|
||||
fn players_online(&self) -> u32 { self.players_online }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn crate::protocols::types::CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_players_and_teams(packet: &[u8]) -> GDResult<Vec<Player>> {
|
||||
let mut buf = Buffer::<BigEndian>::new(packet);
|
||||
|
||||
let count = buf.read::<u16>()?;
|
||||
let mut players = Vec::with_capacity(count as usize);
|
||||
|
||||
while buf.remaining_length() != 0 {
|
||||
players.push(Player {
|
||||
name: buf.read_string::<Utf8Decoder>(None)?,
|
||||
steam_id: buf.read_string::<Utf8Decoder>(None)?,
|
||||
ping: buf.read::<u16>()?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(players)
|
||||
}
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response> {
|
||||
let mut client = GameSpy3::new_custom(
|
||||
&SocketAddr::new(*address, port.unwrap_or(7777)),
|
||||
timeout_settings,
|
||||
[0xFF, 0xFF, 0xFF, 0x02],
|
||||
true,
|
||||
)?;
|
||||
|
||||
let packets = client.get_server_packets()?;
|
||||
let data = packets
|
||||
.get(0)
|
||||
.ok_or(PacketBad.context("First packet missing"))?;
|
||||
|
||||
let (mut server_vars, remaining_data) = data_to_map(data)?;
|
||||
let players = parse_players_and_teams(&remaining_data)?;
|
||||
|
||||
let players_maximum = server_vars
|
||||
.remove("maxplayers")
|
||||
.ok_or(PacketBad.context("Server variables missing maxplayers"))?
|
||||
.parse()
|
||||
.map_err(|e| TypeParse.context(e))?;
|
||||
let players_online = match server_vars.remove("numplayers") {
|
||||
None => players.len(),
|
||||
Some(v) => {
|
||||
let reported_players = v.parse().map_err(|e| TypeParse.context(e))?;
|
||||
match reported_players < players.len() {
|
||||
true => players.len(),
|
||||
false => reported_players,
|
||||
}
|
||||
}
|
||||
} as u32;
|
||||
|
||||
Ok(Response {
|
||||
game_version: server_vars
|
||||
.remove("version")
|
||||
.ok_or(GDErrorKind::PacketBad)?,
|
||||
description: server_vars
|
||||
.remove("description")
|
||||
.ok_or(GDErrorKind::PacketBad)?,
|
||||
name: server_vars
|
||||
.remove("hostname")
|
||||
.ok_or(GDErrorKind::PacketBad)?,
|
||||
has_password: has_password(&mut server_vars)?,
|
||||
players,
|
||||
players_maximum,
|
||||
players_online,
|
||||
})
|
||||
}
|
||||
64
crates/lib/src/games/minecraft.rs
Normal file
64
crates/lib/src/games/minecraft.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use crate::protocols::minecraft::RequestSettings;
|
||||
use crate::{
|
||||
protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup},
|
||||
GDErrorKind,
|
||||
GDResult,
|
||||
};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
/// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy
|
||||
/// (1.6 -> 1.4 -> Beta 1.8)).
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<JavaResponse> {
|
||||
if let Ok(response) = query_java(address, port, None) {
|
||||
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(GDErrorKind::AutoQuery.into())
|
||||
}
|
||||
|
||||
/// Query a Java Server.
|
||||
pub fn query_java(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
request_settings: Option<RequestSettings>,
|
||||
) -> GDResult<JavaResponse> {
|
||||
minecraft::query_java(
|
||||
&SocketAddr::new(*address, port_or_java_default(port)),
|
||||
None,
|
||||
request_settings,
|
||||
)
|
||||
}
|
||||
|
||||
/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8).
|
||||
pub fn query_legacy(address: &IpAddr, port: Option<u16>) -> GDResult<JavaResponse> {
|
||||
minecraft::query_legacy(&SocketAddr::new(*address, port_or_java_default(port)), None)
|
||||
}
|
||||
|
||||
/// Query a specific (Java) Legacy Server.
|
||||
pub fn query_legacy_specific(group: LegacyGroup, address: &IpAddr, port: Option<u16>) -> GDResult<JavaResponse> {
|
||||
minecraft::query_legacy_specific(
|
||||
group,
|
||||
&SocketAddr::new(*address, port_or_java_default(port)),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Query a Bedrock Server.
|
||||
pub fn query_bedrock(address: &IpAddr, port: Option<u16>) -> GDResult<BedrockResponse> {
|
||||
minecraft::query_bedrock(
|
||||
&SocketAddr::new(*address, port_or_bedrock_default(port)),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn port_or_java_default(port: Option<u16>) -> u16 { port.unwrap_or(25565) }
|
||||
|
||||
fn port_or_bedrock_default(port: Option<u16>) -> u16 { port.unwrap_or(19132) }
|
||||
138
crates/lib/src/games/mod.rs
Normal file
138
crates/lib/src/games/mod.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
//! Currently supported games.
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod gamespy;
|
||||
pub mod quake;
|
||||
pub mod valve;
|
||||
|
||||
pub use gamespy::*;
|
||||
pub use quake::*;
|
||||
pub use valve::*;
|
||||
|
||||
/// Battalion 1944
|
||||
pub mod battalion1944;
|
||||
/// Frontlines: Fuel of War
|
||||
pub mod ffow;
|
||||
/// Just Cause 2: Multiplayer
|
||||
pub mod jc2m;
|
||||
/// Minecraft
|
||||
pub mod minecraft;
|
||||
/// The Ship
|
||||
pub mod theship;
|
||||
|
||||
use crate::protocols::gamespy::GameSpyVersion;
|
||||
use crate::protocols::quake::QuakeVersion;
|
||||
use crate::protocols::types::{CommonResponse, ExtraRequestSettings, ProprietaryProtocol, TimeoutSettings};
|
||||
use crate::protocols::{self, Protocol};
|
||||
use crate::GDResult;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
/// Definition of a game
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Game {
|
||||
/// Full name of the game
|
||||
pub name: &'static str,
|
||||
/// Default port used by game
|
||||
pub default_port: u16,
|
||||
/// The protocol the game's query uses
|
||||
pub protocol: Protocol,
|
||||
}
|
||||
|
||||
#[cfg(feature = "game_defs")]
|
||||
mod definitions;
|
||||
|
||||
#[cfg(feature = "game_defs")]
|
||||
pub use definitions::GAMES;
|
||||
|
||||
/// Make a query given a game definition
|
||||
#[inline]
|
||||
pub fn query(game: &Game, address: &IpAddr, port: Option<u16>) -> GDResult<Box<dyn CommonResponse>> {
|
||||
query_with_timeout_and_extra_settings(game, address, port, None, None)
|
||||
}
|
||||
|
||||
/// Make a query given a game definition and timeout settings
|
||||
#[inline]
|
||||
pub fn query_with_timeout(
|
||||
game: &Game,
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Box<dyn CommonResponse>> {
|
||||
query_with_timeout_and_extra_settings(game, address, port, timeout_settings, None)
|
||||
}
|
||||
|
||||
/// Make a query given a game definition, timeout settings, and extra settings
|
||||
pub fn query_with_timeout_and_extra_settings(
|
||||
game: &Game,
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
extra_settings: Option<ExtraRequestSettings>,
|
||||
) -> GDResult<Box<dyn CommonResponse>> {
|
||||
let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port));
|
||||
Ok(match &game.protocol {
|
||||
Protocol::Valve(steam_app) => {
|
||||
protocols::valve::query(
|
||||
&socket_addr,
|
||||
steam_app.as_engine(),
|
||||
extra_settings.map(ExtraRequestSettings::into),
|
||||
timeout_settings,
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
Protocol::Minecraft(version) => {
|
||||
match version {
|
||||
Some(protocols::minecraft::Server::Java) => {
|
||||
protocols::minecraft::query_java(
|
||||
&socket_addr,
|
||||
timeout_settings,
|
||||
extra_settings.map(ExtraRequestSettings::into),
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
Some(protocols::minecraft::Server::Bedrock) => {
|
||||
protocols::minecraft::query_bedrock(&socket_addr, timeout_settings).map(Box::new)?
|
||||
}
|
||||
Some(protocols::minecraft::Server::Legacy(group)) => {
|
||||
protocols::minecraft::query_legacy_specific(*group, &socket_addr, timeout_settings).map(Box::new)?
|
||||
}
|
||||
None => {
|
||||
protocols::minecraft::query(
|
||||
&socket_addr,
|
||||
timeout_settings,
|
||||
extra_settings.map(ExtraRequestSettings::into),
|
||||
)
|
||||
.map(Box::new)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Protocol::Gamespy(version) => {
|
||||
match version {
|
||||
GameSpyVersion::One => protocols::gamespy::one::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
GameSpyVersion::Two => protocols::gamespy::two::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
GameSpyVersion::Three => {
|
||||
protocols::gamespy::three::query(&socket_addr, timeout_settings).map(Box::new)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Protocol::Quake(version) => {
|
||||
match version {
|
||||
QuakeVersion::One => protocols::quake::one::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
QuakeVersion::Two => protocols::quake::two::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
QuakeVersion::Three => protocols::quake::three::query(&socket_addr, timeout_settings).map(Box::new)?,
|
||||
}
|
||||
}
|
||||
Protocol::PROPRIETARY(protocol) => {
|
||||
match protocol {
|
||||
ProprietaryProtocol::TheShip => {
|
||||
theship::query_with_timeout(address, port, timeout_settings).map(Box::new)?
|
||||
}
|
||||
ProprietaryProtocol::FFOW => ffow::query_with_timeout(address, port, timeout_settings).map(Box::new)?,
|
||||
ProprietaryProtocol::JC2M => jc2m::query_with_timeout(address, port, timeout_settings).map(Box::new)?,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
9
crates/lib/src/games/quake.rs
Normal file
9
crates/lib/src/games/quake.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//! Quake game query modules
|
||||
|
||||
use crate::protocols::quake::game_query_mod;
|
||||
|
||||
game_query_mod!(quake1, "Quake 1", one, 27500);
|
||||
game_query_mod!(quake2, "Quake 2", two, 27910);
|
||||
game_query_mod!(quake3, "Quake 3: Arena", three, 27960);
|
||||
game_query_mod!(sof2, "Soldier of Fortune 2", three, 20100);
|
||||
game_query_mod!(warsow, "Warsow", three, 44400);
|
||||
145
crates/lib/src/games/theship.rs
Normal file
145
crates/lib/src/games/theship.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
use crate::{
|
||||
protocols::{
|
||||
types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings},
|
||||
valve::{self, get_optional_extracted_data, Server, ServerPlayer, SteamApp},
|
||||
GenericResponse,
|
||||
},
|
||||
GDErrorKind::PacketBad,
|
||||
GDResult,
|
||||
};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
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: i32,
|
||||
pub duration: f32,
|
||||
pub deaths: u32,
|
||||
pub money: u32,
|
||||
}
|
||||
|
||||
impl TheShipPlayer {
|
||||
pub fn new_from_valve_player(player: &ServerPlayer) -> GDResult<Self> {
|
||||
Ok(Self {
|
||||
name: player.name.clone(),
|
||||
score: player.score,
|
||||
duration: player.duration,
|
||||
deaths: player.deaths.ok_or(PacketBad)?,
|
||||
money: player.money.ok_or(PacketBad)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonPlayer for TheShipPlayer {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::TheShip(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
fn score(&self) -> Option<i32> { Some(self.score) }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Response {
|
||||
pub protocol_version: u8,
|
||||
pub name: String,
|
||||
pub map: String,
|
||||
pub game_mode: String,
|
||||
pub game_version: String,
|
||||
pub players: Vec<TheShipPlayer>,
|
||||
pub players_online: u8,
|
||||
pub players_maximum: u8,
|
||||
pub players_bots: u8,
|
||||
pub server_type: Server,
|
||||
pub has_password: bool,
|
||||
pub vac_secured: bool,
|
||||
pub port: Option<u16>,
|
||||
pub steam_id: Option<u64>,
|
||||
pub tv_port: Option<u16>,
|
||||
pub tv_name: Option<String>,
|
||||
pub keywords: Option<String>,
|
||||
pub rules: HashMap<String, String>,
|
||||
pub mode: u8,
|
||||
pub witnesses: u8,
|
||||
pub duration: u8,
|
||||
}
|
||||
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::TheShip(self) }
|
||||
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) }
|
||||
fn players_maximum(&self) -> u32 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u32 { self.players_online.into() }
|
||||
fn players_bots(&self) -> Option<u32> { Some(self.players_bots.into()) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn new_from_valve_response(response: valve::Response) -> GDResult<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.ok_or(PacketBad)?;
|
||||
|
||||
Ok(Self {
|
||||
protocol_version: response.info.protocol_version,
|
||||
name: response.info.name,
|
||||
map: response.info.map,
|
||||
game_mode: response.info.game_mode,
|
||||
game_version: response.info.game_version,
|
||||
players_online: response.info.players_online,
|
||||
players: response
|
||||
.players
|
||||
.ok_or(PacketBad)?
|
||||
.iter()
|
||||
.map(TheShipPlayer::new_from_valve_player)
|
||||
.collect::<GDResult<Vec<TheShipPlayer>>>()?,
|
||||
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,
|
||||
port,
|
||||
steam_id,
|
||||
tv_port,
|
||||
tv_name,
|
||||
keywords,
|
||||
rules: response.rules.ok_or(PacketBad)?,
|
||||
mode: the_unwrapped_ship.mode,
|
||||
witnesses: the_unwrapped_ship.witnesses,
|
||||
duration: the_unwrapped_ship.duration,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> { query_with_timeout(address, port, None) }
|
||||
|
||||
pub fn query_with_timeout(
|
||||
address: &IpAddr,
|
||||
port: Option<u16>,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response> {
|
||||
let valve_response = valve::query(
|
||||
&SocketAddr::new(*address, port.unwrap_or(27015)),
|
||||
SteamApp::THESHIP.as_engine(),
|
||||
None,
|
||||
timeout_settings,
|
||||
)?;
|
||||
|
||||
Response::new_from_valve_response(valve_response)
|
||||
}
|
||||
56
crates/lib/src/games/valve.rs
Normal file
56
crates/lib/src/games/valve.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
//! Valve game query modules
|
||||
|
||||
use crate::protocols::valve::game_query_mod;
|
||||
|
||||
game_query_mod!(a2oa, "ARMA 2: Operation Arrowhead", A2OA, 2304);
|
||||
game_query_mod!(alienswarm, "Alien Swarm", ALIENSWARM, 27015);
|
||||
game_query_mod!(aoc, "Age of Chivalry", AOC, 27015);
|
||||
game_query_mod!(ase, "ARK: Survival Evolved", ASE, 27015);
|
||||
game_query_mod!(asrd, "Alien Swarm: Reactive Drop", ASRD, 2304);
|
||||
game_query_mod!(avorion, "Avorion", AVORION, 27020);
|
||||
game_query_mod!(
|
||||
ballisticoverkill,
|
||||
"Ballistic Overkill",
|
||||
BALLISTICOVERKILL,
|
||||
27016
|
||||
);
|
||||
game_query_mod!(barotrauma, "Barotrauma", BAROTRAUMA, 27016);
|
||||
game_query_mod!(blackmesa, "Black Mesa", BLACKMESA, 27015);
|
||||
game_query_mod!(brainbread2, "BrainBread 2", BRAINBREAD2, 27015);
|
||||
game_query_mod!(codenamecure, "Codename CURE", CODENAMECURE, 27015);
|
||||
game_query_mod!(colonysurvival, "Colony Survival", COLONYSURVIVAL, 27004);
|
||||
game_query_mod!(counterstrike, "Counter-Strike", COUNTERSTRIKE, 27015);
|
||||
game_query_mod!(creativerse, "Creativerse", CREATIVERSE, 26901);
|
||||
game_query_mod!(cscz, "Counter Strike: Condition Zero", CSCZ, 27015);
|
||||
game_query_mod!(csgo, "Counter-Strike: Global Offensive", CSGO, 27015);
|
||||
game_query_mod!(css, "Counter-Strike: Source", CSS, 27015);
|
||||
game_query_mod!(dod, "Day of Defeat", DOD, 27015);
|
||||
game_query_mod!(dods, "Day of Defeat: Source", DODS, 27015);
|
||||
game_query_mod!(doi, "Day of Infamy", DOI, 27015);
|
||||
game_query_mod!(dst, "Don't Starve Together", DST, 27016);
|
||||
game_query_mod!(garrysmod, "Garry's Mod", GARRYSMOD, 27016);
|
||||
game_query_mod!(hl2d, "Half-Life 2 Deathmatch", HL2D, 27015);
|
||||
game_query_mod!(hlds, "Half-Life Deathmatch: Source", HLDS, 27015);
|
||||
game_query_mod!(hll, "Hell Let Loose", HLL, 26420);
|
||||
game_query_mod!(imic, "Insurgency: Modern Infantry Combat", IMIC, 27015);
|
||||
game_query_mod!(insurgency, "Insurgency", INSURGENCY, 27015);
|
||||
game_query_mod!(
|
||||
insurgencysandstorm,
|
||||
"Insurgency: Sandstorm",
|
||||
INSURGENCYSANDSTORM,
|
||||
27131
|
||||
);
|
||||
game_query_mod!(left4dead, "Left 4 Dead", LEFT4DEAD, 27015);
|
||||
game_query_mod!(left4dead2, "Left 4 Dead 2", LEFT4DEAD2, 27015);
|
||||
game_query_mod!(ohd, "Operation: Harsh Doorstop", OHD, 27005);
|
||||
game_query_mod!(onset, "Onset", ONSET, 7776);
|
||||
game_query_mod!(projectzomboid, "Project Zomboid", PROJECTZOMBOID, 16261);
|
||||
game_query_mod!(ror2, "Risk of Rain 2", ROR2, 27016);
|
||||
game_query_mod!(rust, "Rust", RUST, 27015);
|
||||
game_query_mod!(sco, "Sven Co-op", SCO, 27015);
|
||||
game_query_mod!(sd2d, "7 Days To Die", SD2D, 26900);
|
||||
game_query_mod!(teamfortress2, "Team Fortress 2", TEAMFORTRESS2, 27015);
|
||||
game_query_mod!(tfc, "Team Fortress Classic", TFC, 27015);
|
||||
game_query_mod!(theforest, "The Forest", THEFOREST, 27016);
|
||||
game_query_mod!(unturned, "Unturned", UNTURNED, 27015);
|
||||
game_query_mod!(vrising, "V Rising", VRISING, 27016);
|
||||
Loading…
Add table
Add a link
Reference in a new issue