[Game] Add JC2MP support. (#54)

* [Game] Add JC2MP support.

* [Game] Add game to changelog and games

* [Games] Add generic support to JC2MP.

* [Game] Add players_maximum and players_online
This commit is contained in:
CosminPerRam 2023-06-19 22:10:49 +03:00 committed by GitHub
parent cb9384e474
commit c3e2d948e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 236 additions and 91 deletions

View file

@ -81,4 +81,5 @@ pub static GAMES: Map<&'static str, Game> = phf_map! {
"unturned" => game!("Unturned", 27015, Protocol::Valve(SteamApp::UNTURNED)),
"ut" => game!("Unreal Tournament", 7778, Protocol::Gamespy(GameSpyVersion::One)),
"vr" => game!("V Rising", 27016, Protocol::Valve(SteamApp::VR)),
"jc2mp" => game!("Just Cause 2: Multiplayer", 7777, Protocol::JC2MP),
};

106
src/games/jc2mp.rs Normal file
View file

@ -0,0 +1,106 @@
use crate::bufferer::{Bufferer, Endianess};
use crate::protocols::gamespy::common::has_password;
use crate::protocols::gamespy::three::{data_to_map, GameSpy3};
use crate::protocols::types::SpecificResponse;
use crate::protocols::GenericResponse;
use crate::{GDError, GDResult};
#[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,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Response {
version: String,
description: String,
name: String,
has_password: bool,
players: Vec<Player>,
players_maximum: usize,
players_online: usize,
}
impl From<Response> for GenericResponse {
fn from(r: Response) -> Self {
Self {
name: Some(r.name),
description: Some(r.description),
game: None,
game_version: Some(r.version),
map: None,
players_maximum: r.players_maximum as u64,
players_online: r.players_online as u64,
players_bots: None,
has_password: Some(r.has_password),
inner: SpecificResponse::JC2MP,
}
}
}
fn parse_players_and_teams(packet: Vec<u8>) -> GDResult<Vec<Player>> {
let mut buf = Bufferer::new_with_data(Endianess::Big, &packet);
let count = buf.get_u16()?;
let mut players = Vec::with_capacity(count as usize);
while buf.remaining_length() > 0 {
players.push(Player {
name: buf.get_string_utf8()?,
steam_id: buf.get_string_utf8()?,
ping: buf.get_u16()?,
})
}
Ok(players)
}
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> {
let mut client = GameSpy3::new_custom(
&SocketAddr::new(*address, port.unwrap_or(7777)),
None,
[0xFF, 0xFF, 0xFF, 0x02],
true,
)?;
let packets = client.get_server_packets()?;
let data = packets.get(0).ok_or(GDError::PacketBad)?;
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(GDError::PacketBad)?
.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 {
version: server_vars.remove("version").ok_or(GDError::PacketBad)?,
description: server_vars
.remove("description")
.ok_or(GDError::PacketBad)?,
name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?,
has_password: has_password(&mut server_vars)?,
players,
players_maximum,
players_online,
})
}

View file

@ -65,6 +65,8 @@ pub mod ins;
pub mod insmic;
/// Insurgency: Sandstorm
pub mod inss;
/// Just Cause 2: Multiplayer
pub mod jc2mp;
/// Left 4 Dead
pub mod l4d;
/// Left 4 Dead 2
@ -171,5 +173,6 @@ pub fn query(game: &Game, address: &IpAddr, port: Option<u16>) -> GDResult<proto
}
Protocol::TheShip => ts::query(address, port).map(|r| r.into())?,
Protocol::FFOW => ffow::query(address, port).map(|r| r.into())?,
Protocol::JC2MP => jc2mp::query(address, port).map(|r| r.into())?,
})
}